文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

ProtoBuf动态拆分Gradle Module源码分析

2023-07-05 07:08

关注

本文小编为大家详细介绍“ProtoBuf动态拆分Gradle Module源码分析”,内容详细,步骤清晰,细节处理妥当,希望这篇“ProtoBuf动态拆分Gradle Module源码分析”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。

预期

当前安卓的所有proto都生成在一个module中,但是其实业务同学需要的并不是一个大杂烩, 只需要其中他们所关心的proto生成的类则足以。所以我们希望能将这样一个大杂烩的仓库打散,拆解成多个module

ProtoBuf动态拆分Gradle Module源码分析

buf.yaml

Protobuf是Protocol Buffers的简称,它是Google公司开发的一种数据描述语言,用于描述一种轻便高效的结构化数据存储格式,并于2008年对外开源。Protobuf可以用于结构化数据串行化,或者说序列化。它的设计非常适用于在网络通讯中的数据载体,很适合做数据存储或 RPC 数据交换格式,它序列化出来的数据量少再加上以 K-V 的方式来存储数据,对消息的版本兼容性非常强,可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。开发者可以通过Protobuf附带的工具生成代码并实现将结构化数据序列化的功能。

在我司proto相关的都是由后端大佬们来维护的,然后这个协议仓库会被android/ios/后端/前端 依赖之后生成对应的代码,然后直接使用。

而proto文件中允许导入对于其他proto文件的依赖,所以这就导致了想要把几个proto转化成一个java-library工程,还需要考虑依赖问题。所以由 我们的后端来定义了一个buf.yaml的数据格式。

version: v1name: buf.xxx.co/xxx/xxxxxxdeps:  - buf.xxxxx.co/google/protobufbuild:  excludes:    - settingbreaking:  use:    - FILElint:  use:    - DEFAULT

name代表了这个工程的名字,deps则表示了他依赖的proto的工程名。基于这份yaml内容,我们就可以大概确定一个proto工程编译需要的基础条件。然后我们只需要一个工具或者插件来帮助我们生成对应的工程就够了。

模板工程

现在我们基本已经有了一个单一的proto工程的输入模型了,工程名依赖的工程还有对应文件夹下的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("aarch74") == 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("aarch74") == 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转化成工程名,去除掉一些无法识别的字符。

我们生成的模板工程如下:

ProtoBuf动态拆分Gradle Module源码分析

其中proto.version会记录proto内的gitsha值还有文件的lastModified时间,如果输入发生变更则会重新进行一次文件拷贝操作,避免重复覆盖的风险。

input.txt则包含了所有proto文件路径,方便我们进行开发调试。

deps 转化

由于proto之间存在依赖,没有依赖则会导致无法将proto转化成java。所以这里我讲buf.yaml中读取出的deps转化成了一个depend.txt.

com.xxxx.api:google-protobuf:7.7.7

depend.txt内会逐行写入当前模块的依赖,我们会对name进行一次转化,变成一个可读的gradle工程名。其中7.7.7的版本只是一个缺省而已,并没有实际的价值。

多线程操作

这里我们出现了一点点的性能问题, 如果可以gradle插件中尽量多使用点多线程,尤其是这种需要io的操作中。

这里我通过ForkJoinPool,这个是ExecutorService的实现类。其中submit方法中会返回一个ForkJoinTask,我们可以将获取gitsha值和lastModified放在这个中。之后把所有的ForkJoinTask放到一个数组中。

fun await() {     forkJoins.forEach {         it.join()     } }

然后最后暴露一个await方法,来做到所有的获取方法完成之后再继续向下执行。

另外则就是壳module的生成,我们也放在了子线程内执行。我们这次使用了线程池的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)

这里有个面试经常出现的考点,多线程操作Hashmap,之后我在测试环节随机出现了生成工程和include不匹配的问题。所以最后我更换了ConcurrentHashMap就没有出现这个问题了。

加载壳Module

这部分就和源码编译插件基本是一样的写法。

projects.forEach { (s, file) ->              settings.include(":${s}")              settings.project(":${s}").projectDir = File(file)          }

把工程插入settings 即可。

读到这里,这篇“ProtoBuf动态拆分Gradle Module源码分析”文章已经介绍完毕,想要掌握这篇文章的知识点还需要大家自己动手实践使用过才能领会,如果想了解更多相关内容的文章,欢迎关注编程网行业资讯频道。

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯