Android厂商推送Plugin化

背景

由于要把项目内的推送能力提供给别的业务接入,当前已经接入了FCM(谷歌推送),HMS,小米,Vivo,OPPO,极光等好多平台的推送。但是业务接入可能只需要其中的几种而已,抛开SDK设计的一部分,光光从接入成本上来说其实就比较复杂了,下面是问题的汇总啊。

  1. 要先设置很多AppKey之类的。
  2. 要设置些类似applicationId之类的pleaceHolder
  3. FCM和HMS现在是通过plugin的方式来接入的,多个plugin会让开发迷惑行为
  4. 要动态设置很多推送策略,根据不同的厂商决定当前的推送策略等。
  5. 如何将aar变更成源码依赖。

说实话,只要写的越多那么可能发生问题的地方也就越多,特别是推送业务本身就存在很多不确定性(厂商抽风啥的)。

如何解决这些问题

我们先把推送plugin配置在'com.android.application'下面,这一段可以通过Plugin内的定义。通过PluginExtension把一些动态配置的变更成参数传入Plugin中,然后通过这些配置来完成我们所需要的推送业务聚合。

举个例子,A app只需要HMS和极光,则只需要配置其中两项,并不会引入其他推送的代码,而B app则需要所有的,则会根据这些配置引入所有的仓库,和动态生成配置文件。

这种方式有什么好处呢?首先它更直观,配置的地方会更少,同时避免了由开发去定义这些配置策略,这样对于使用方来说,他们只需要完成简单的初始化配置就可以使用这些推送业务了。

buildSrc + setting

给大家安利下这个模式,buildSrc的模式下,我们可以不需要推本地的jar就可以直接调试plugin插件,这个就解决了plugin插件非常不好调试的问题。

include ':plugin'
project(":plugin").projectDir = new File( "../plugin")
复制代码

但是buildSrc其实也有点小问题,就是这个东西不是特别方便我们去发布plugin项目。这个时候安利大家一个小姿势了。buildSrc下面其实可以使用setting.gradle,这个时候我们就可以把plugin的module引入,这样就可以同时兼顾buildSrc的快速调试,同时又可以很方便的发布项目了。

安利下我大佬的一个Demo项目,有兴趣的可以参考下这个。

小贴士 对于插件的调试 可以在./gradlew taskName -s 这样把异常日志打印出来

如果不会调试Gradle的同学可以学习下这个,也可以方便你学习Gradle Plugin的源代码,传送门只Gradle plugin debug

buildTypes resValue

我们有时候在写build.gradle的时候,会在buildTypes中增加一些resValue "string", "AppName", "app1"类似的东西。

我们最后会在build/generated/下面生成一个资源文件gradleResValue.xml,它会在在编译时会被合并到项目资源文件内。那么我们如何在plugin内如何使用这个呢?

                        val android = project.extensions.getByName("android") as BaseAppModuleExtension
                        val config = android.defaultConfig
                        config.resValue("string", key, value)
复制代码

我们可以通过project.extensions去获取到当前项目下的其他的Extension。这个时候我们只要获取到androidExtension,就可以像在build.gradle调用resValue一样,调用defaultConfig内的resValue方法,去添加资源信息了。

修改Mainfest?

由于项目内的一些特殊编码需求,我们要根据applicationId的不同来设置不同的placeHolder。

如果不用ManifestPleaceHolder的方法,我们可以不可以考虑下通过什么方式去在manifest合并完成之后,再对这个已经定稿的manifest做一些修改。

  val variantName = variant.name.capitalize()
  val processManifestTask = project.tasks.getByName("process${variantName}Manifest")
          as ProcessApplicationManifest
  processManifestTask.doLast {
  }
复制代码

上面的代码可以从projcet的task内获取到Manifest合并的Task。上一篇文章我介绍过,Task作为Gradle任务的核心单元,其实我们可以在doFirst,doLast对这个Task进行一定的修改。

比如说任务完成之后我们可以通过文件路径对Manifest的xml进行一些修改的操作,这样就能根据不同的代码需要对manifest做一些增删改查了,最后只要覆盖当前的Manifest文件就会对整个项目生效。

如何在Plugin中添加依赖?

大家有没有想过项目内的dependencies是什么东西呢??

其实我们在项目内添加的implementation,api等等,这些操作都只是在Project的DefaultDependencyHandler内添加一个数据结构,其中包含了group+name+version,最后在Configuration内添加真实的依赖关系。

可以参考下我大佬的文章Gradle Configuration

  project.dependencies.add(Implement, "com.squareup.okhttp3:okhttp:4.9.0")
复制代码

我们只要在project中获取到dependencies,然后调用add方法就可以在plugin中,给项目添加依赖的aar了。

如何在Plugin中添加另外一个Plugin?

由于项目内FCMHMS都需要引入一个厂商编写的Plugin,而当使用方要去接入的时候就会造成很多问题。那么我们能不能通过我们自己的插件去把这些插件依赖也整合起来呢?

dependencies {
    implementation gradleApi()
    implementation localGroovy()
    implementation 'commons-io:commons-io:2.6'
    implementation "com.squareup:javapoet:1.13.0"
    implementation 'com.huawei.agconnect:agcp:1.3.1.300'
    implementation "com.google.gms:google-services:4.2.0"
}
复制代码

首先我们要通过implementation把我们需要的插件先整合进来。要不然我们不就引用不到他们的Plugin的代码。

        val plugins = project.pluginManager
        if (!plugins.hasPlugin("com.google.android.gms.google-services")) {
              plugins.apply(GoogleServicesPlugin::class.java)
        }
复制代码

从代码上来解释,我们只是给project添加一个额外的Plugin而已,所以相对来说还是比较简单的。当然这个Plugin是实际会生效的,各位可以放心。

根据条件生成策略类

首先抛出一个问题,Plugin内有没有什么节点可以和apt一样生成一个java代码呢?

各位大佬,不知道有没有了解过Jake大神的ButterKnife'com.jakewharton.butterknife'插件是如何生成R2的。

因为以前的Module内的R.Id因为都不是final的所以没有办法被注解所使用,这个时候Jake大神通过Hook了R文件生成的Task,然后copy了一份重新生成了一份R2.

android.applicationVariants.all { variant ->
     variant.outputs.all { output ->
       variant.outputs.all { output ->
      // Though there might be multiple outputs, their R files are all the same. Thus, we only
      // need to configure the task once with the R.java input and action.
      if (once.compareAndSet(false, true)) {
        val processResources = output.processResourcesProvider.get() // TODO lazy

        // TODO: switch to better API once exists in AGP (https://issuetracker.google.com/118668005)
        val rFile =
            project.files(
                when (processResources) {
                  is GenerateLibraryRFileTask -> processResources.textSymbolOutputFile
                  is LinkApplicationAndroidResourcesTask -> processResources.textSymbolOutputFile
                  else -> throw RuntimeException(
                      "Minimum supported Android Gradle Plugin is 3.3.0")
                })
                .builtBy(processResources)
        val generate = project.tasks.create("generate${variant.name.capitalize()}R2", R2Generator::class.java) {
          it.outputDir = outputDir
          it.rFile = rFile
          it.packageName = rPackage
          it.className = "R2"
        }
        variant.registerJavaGeneratingTask(generate, outputDir)
      }
    }
}
复制代码

上面的代码,就是在AndroidPlugin中可以注册一个registerJavaGeneratingTask的Task。这个Task可以在编译阶段生成一些我们所需要的java类,而这个阶段和Transform不一样,因为没有进入JavaCompiler环节,所以我们可以通过javapoet去生成java类,而且在我们实际编码的环节中是可以引用到这个类的。

因为Plugin的Extension是知道当前的项目需要使用几个厂商推送的,这样我们就可以通过生成代码的方式直接生成好策略类(以前这个策略类是要由接入方自己实现的),能让代码解决的问题就尽量不要让开发来写。

上面就是我生成的一个简单的java类,对于接入方来说做的越少那么就越不容易出问题,也就是架构上所说的高内聚。javapoet还有kotlinpoet这两个都可以展开一篇文章了,这边就不过分展开了啊。

如何将aar变更成源码依赖

前文解决了Plugin调试困难的问题,但是文章还有最后一个小问题,因为在Plugin是提供给别的App使用,所以直接使用了maven依赖。但是在Demo开发阶段源码的编译方式会更适合我开发,所以如何将一个group+name+version更换成一个本地的Module呢??

以前介绍过git-repo这个项目就是通过setting的方式给当将别的仓库引入到当前工程,然后通过configurationsresolutionStrategy所谓的解决策略来帮助项目解决多仓库依赖的问题。

那么我们能不能把这段逻辑偷过来呢,哈哈哈。

val splite = pair.first.split(":")
val substitute = "${splite[0]}:${splite[1]}"
project.subprojects.forEach { it ->
    it.configurations.all { config ->
      config.resolutionStrategy {
            it.dependencySubstitution.substitute(
                  it.dependencySubstitution.module(
                      substitute
                  )
                  ).because("debug enable").with(it.dependencySubstitution.project(pair.second))
                        it.dependencySubstitution.project(pair.second)
        }
    }
}
复制代码

首先在策略中,我们更换了dependencySubstitution,其中substitute就是group+name,然后pair.second则是其映射到本地的project。所以这段逻辑就是把group+name对应的取出来,然后替换成本地的仓库映射,而resolutionStrategy会更换项目内所有的group更换,这样我们就完成了项目内的本地映射了哦。

TODO

我还是有个地方想做的,由于当前推送为了保证最少的依赖,所以就连OKHttp都没有直接引用,其实可以在Plugin内根据当前Projcet的dependencies中是否含有一些第三方库,然后根据这个来引入其中的一部分类似Retrofit一样的Adapter的方式来引入依赖,就可以更好的提高当前仓库的能力。

总结

来了大B之后还是做了点好玩的东西的。关于行业内劝退安卓我个人看法哦,虽然现在客户端的职位可能会相对以前少了很多,但是并不代表着客户端的技术栈很浅啊。安卓可以玩的东西其实有很多啊,Aop, Apt,Apm性能监控,调试相关,编译优化,CI/CD,静态检查,网络优化,模块化,gradle相关,DSL等等。

对于一个技术来说,最重要的其实就是在自己的领域做得非常深入,只要能做到所谓的不可替代性,所以也没有什么客户端劝退一说吧,和各位大佬共勉吧。

作者:究极逮虾户
链接:https://juejin.im/post/6881414546859229192

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345