ProtoBuf動態(tài)拆分Gradle?Module解析
預期
當前安卓的所有proto都生成在一個module中,但是其實業(yè)務(wù)同學需要的并不是一個大雜燴, 只需要其中他們所關(guān)心的proto生成的類則足以。所以我們希望能將這樣一個大雜燴的倉庫打散,拆解成多個module。

buf.yaml
Protobuf是Protocol Buffers的簡稱,它是Google公司開發(fā)的一種數(shù)據(jù)描述語言,用于描述一種輕便高效的結(jié)構(gòu)化數(shù)據(jù)存儲格式,并于2008年對外開源。Protobuf可以用于結(jié)構(gòu)化數(shù)據(jù)串行化,或者說序列化。它的設(shè)計非常適用于在網(wǎng)絡(luò)通訊中的數(shù)據(jù)載體,很適合做數(shù)據(jù)存儲或 RPC 數(shù)據(jù)交換格式,它序列化出來的數(shù)據(jù)量少再加上以 K-V 的方式來存儲數(shù)據(jù),對消息的版本兼容性非常強,可用于通訊協(xié)議、數(shù)據(jù)存儲等領(lǐng)域的語言無關(guān)、平臺無關(guān)、可擴展的序列化結(jié)構(gòu)數(shù)據(jù)格式。開發(fā)者可以通過Protobuf附帶的工具生成代碼并實現(xiàn)將結(jié)構(gòu)化數(shù)據(jù)序列化的功能。
在我司proto相關(guān)的都是由后端大佬們來維護的,然后這個協(xié)議倉庫會被android/ios/后端/前端 依賴之后生成對應(yīng)的代碼,然后直接使用。
而proto文件中允許導入對于其他proto文件的依賴,所以這就導致了想要把幾個proto轉(zhuǎn)化成一個java-library工程,還需要考慮依賴問題。所以由 我們的后端來定義了一個buf.yaml的數(shù)據(jù)格式。
version: v1
name: buf.xxx.co/xxx/xxxxxx
deps:
- buf.xxxxx.co/google/protobuf
build:
excludes:
- setting
breaking:
use:
- FILE
lint:
use:
- DEFAULT
name代表了這個工程的名字,deps則表示了他依賴的proto的工程名?;谶@份yaml內(nèi)容,我們就可以大概確定一個proto工程編譯需要的基礎(chǔ)條件。然后我們只需要一個工具或者插件來幫助我們生成對應(yīng)的工程就夠了。
模板工程
現(xiàn)在我們基本已經(jīng)有了一個單一的proto工程的輸入模型了,工程名依賴的工程還有對應(yīng)文件夾下的proto文件。然后我們就可以基于這部分輸入的模型,生成出第一個模板工程。
plugins {
id 'java-library'
id 'org.jetbrains.kotlin.jvm'
id 'com.google.protobuf'
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
sourceSets {
def dirs = new ArrayList<String>()
dirs.add("src/main/proto")
main.proto.srcDirs = dirs
}
protobuf {
protoc {
if (System.getProperty("os.arch").compareTo("aarch64") == 0) {
artifact = "com.google.protobuf:protoc:$version_protobuf_protoc:osx-x86_64"
} else {
artifact = "com.google.protobuf:protoc:$version_protobuf_protoc"
}
}
plugins {
grpc {
if (System.getProperty("os.arch").compareTo("aarch64") == 0) {
artifact = 'io.grpc:protoc-gen-grpc-java:1.36.1:osx-x86_64'
} else {
artifact = 'io.grpc:protoc-gen-grpc-java:1.36.1'
}
}
}
generateProtoTasks {
all().each { task ->
task.generateDescriptorSet = true
task.builtins {
// In most cases you don't need the full Java output
// if you use the lite output.
java {
}
}
task.plugins {
grpc { option 'lite' }
}
}
}
}
afterEvaluate {
project.tasks.findByName("compileJava").dependsOn(tasks.findByName("generateProto"))
project.tasks.findByName("compileKotlin").dependsOn(tasks.findByName("generateProto"))
}
dependencies {
implementation "org.glassfish:javax.annotation:10.0-b28"
def grpcJava = '1.36.1'
compileOnly "io.grpc:grpc-protobuf-lite:${grpcJava}"
compileOnly "io.grpc:grpc-stub:${grpcJava}"
compileOnly "io.grpc:grpc-core:${grpcJava}"
File file = new File(projectDir, "depend.txt")
if (!file.exists()) {
return
}
def lines = file.readLines()
if (lines.isEmpty()) {
return
}
lines.forEach {
logger.lifecycle("project:" + name + " implementation: " + it)
implementation(it)
}
}
如果需要將proto編譯成java代碼,就需要依賴于com.google.protobuf插件,依賴于上面的build.gradle基本就可以將一個proto輸入編譯成一個jar工程。
另外我們需要把所有的proto文件拷貝到這個殼工程的src/main/proto文件夾下,最后我們會將buf.yaml中的name: buf.xxx.co/xxx/xxxxxx的/xxx/xxxxxx轉(zhuǎn)化成工程名,去除掉一些無法識別的字符。
我們生成的模板工程如下:

其中proto.version會記錄proto內(nèi)的gitsha值還有文件的lastModified時間,如果輸入發(fā)生變更則會重新進行一次文件拷貝操作,避免重復覆蓋的風險。
input.txt則包含了所有proto文件路徑,方便我們進行開發(fā)調(diào)試。
deps 轉(zhuǎn)化
由于proto之間存在依賴,沒有依賴則會導致無法將proto轉(zhuǎn)化成java。所以這里我講buf.yaml中讀取出的deps轉(zhuǎn)化成了一個depend.txt.
com.xxxx.api:google-protobuf:7.7.7
depend.txt內(nèi)會逐行寫入當前模塊的依賴,我們會對name進行一次轉(zhuǎn)化,變成一個可讀的gradle工程名。其中7.7.7的版本只是一個缺省而已,并沒有實際的價值。
多線程操作
這里我們出現(xiàn)了一點點的性能問題, 如果可以gradle插件中盡量多使用點多線程,尤其是這種需要io的操作中。
這里我通過ForkJoinPool,這個是ExecutorService的實現(xiàn)類。其中submit方法中會返回一個ForkJoinTask,我們可以將獲取gitsha值和lastModified放在這個中。之后把所有的ForkJoinTask放到一個數(shù)組中。
fun await() {
forkJoins.forEach {
it.join()
}
}
然后最后暴露一個await方法,來做到所有的獲取方法完成之后再繼續(xù)向下執(zhí)行。
另外則就是殼module的生成,我們也放在了子線程內(nèi)執(zhí)行。我們這次使用了線程池的invokeAll方法。
protoFileWalk.hashMap.forEach { (_, pbBufYaml) ->
callables.add(Callable<Void> {
val root = FileUtils.getRootProjectDir(settings.gradle)
try {
val file = pbBufYaml.copyLib(File(root, "bapi"))
projects[pbBufYaml.projectName()] = file.absolutePath ?: ""
} catch (e: Exception) {
e.printStackTrace()
e.message.log()
}
null
})
}
executor.invokeAll(callables)
這里有個面試經(jīng)常出現(xiàn)的考點,多線程操作Hashmap,之后我在測試環(huán)節(jié)隨機出現(xiàn)了生成工程和include不匹配的問題。所以最后我更換了ConcurrentHashMap就沒有出現(xiàn)這個問題了。
加載殼Module
這部分就和源碼編譯插件基本是一樣的寫法。
projects.forEach { (s, file) ->
settings.include(":${s}")
settings.project(":${s}").projectDir = File(file)
}
把工程插入settings 即可。
結(jié)尾
這部分方案這樣也就大概完成了一半,剩下的一半我們需要逐一把生層業(yè)務(wù)的依賴進行一次變更,這樣就可以做到依賴最小化,然后也可以去除掉一部分無用的代碼塊。
以上就是ProtoBuf動態(tài)拆分Gradle Module解析的詳細內(nèi)容,更多關(guān)于ProtoBuf拆分Gradle Module的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android中利用動態(tài)加載實現(xiàn)手機淘寶的節(jié)日特效
這篇文章主要介紹了Android中利用動態(tài)加載實現(xiàn)手機淘寶的節(jié)日特效,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2016-12-12
Android實現(xiàn)狀態(tài)欄(statusbar)漸變效果的示例
本篇文章主要介紹了Android實現(xiàn)狀態(tài)欄(statusbar)漸變效果的示例,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-09-09
Android自定義ViewGroup實現(xiàn)淘寶商品詳情頁
這篇文章主要為大家詳細介紹了Android自定義ViewGroup實現(xiàn)淘寶商品詳情頁,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-10-10
Android開發(fā)中ImageView的scaletype屬性用法分析
這篇文章主要介紹了Android開發(fā)中ImageView的scaletype屬性用法,分析了scaletype屬性參數(shù)的常見功能并結(jié)合實例形式給出了具體的使用方法,需要的朋友可以參考下2016-08-08
Retrofit 創(chuàng)建網(wǎng)絡(luò)請求接口實例過程
這篇文章主要為大家介紹了Retrofit 創(chuàng)建網(wǎng)絡(luò)請求接口實例過程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-12-12
Android應(yīng)用中通過Layout_weight屬性用ListView實現(xiàn)表格
這篇文章主要介紹了Android應(yīng)用中通過Layout_weight屬性用ListView實現(xiàn)表格的方法,文中對Layout_weight屬性先有一個較為詳細的解釋,需要的朋友可以參考下2016-04-04

