0x3、常用API补漏
创建配置dsl,先定义dsl结构,定义里面的属性,然后在plugin apply方法中添加以下:
TestExtension extension = project.getExtensions().create("testExt", TestExtension) project.extensions.add("testExt", TestExtension) project.task("TestTask") { doLast { //2.获取外界配置的 TestExtension TestExtension extension = project.testExt //3.输出插件扩展属性 println ">>>>>>" + extension.message } } testExt { //给插件扩展的属性赋值 message "helloworld" }
0x4、插件源码探索——美团渠道包生成插件Walle
没有啥idea,强行写个没啥用的插件没啥意思,刚好群里有人谈到了打包插件:walle,直接看人家插件是怎么实现的~
比起Android自带打包要快上许多,在美团技术团队的博客上有介绍这个工具的大概实现原理:新一代开源Android渠道包生成工具Walle,简单说说,等下慢慢跟一波源码~
先是v2签名前后的APK包差异:
多了个 APK Signing Block
区块,除它之外其它三个区块都是受保护的,签名后对这些区块的修改都逃不过应用签名方案的检查。美团的打包插件就是从 APK Signing Block
区块入手的,区块2格式描述如下:
然后就是从 ID-value
入手的,v2签名信息是以 ID(0x7109871a)
的ID-value来保存在这个区块中,Android系统对于其它的ID-value选择忽略,打包插件就是定义了自定义的ID-value把渠道信息写入到这个区域,App运行时读取渠道信息,再去完成特定渠道初始化。整套插件主要由四个部分组成:
- ① 用于写入ID-value信息的Java类库;
- ② Gradle构建插件用来和Android的打包流程进行结合 ;
- ③ 用于读取ID-value信息的Java类库;
- ④ 用于供 com.android.application 使用的读取渠道信息的AAR;
行吧,大概原理就了解到这里,接着跟一波插件具体的实现源码,直接定位到插件配置文件:
打开看下接口实现类是哪个:
1. GradlePlugin
定位到 GradlePlugin.groovy
,逻辑不算复杂:
定位到 Extension.groovy
,就是DSL传递的参数:
对应文档这里:
看完 applyExtension()
接着看看 applyTask()
:
跟下 ChannelMaker
Task类~
2. ChannelMaker
集成 DefaultTask
, @TaskAction
注解标识Task本身要执行的方法:
然后判断
根据下述几种情况调用对应生成渠道APK的方法:
- PROPERTY_CHANNEL_LIST→ channelList.each { generateChannelApk(...) }
- PROPERTY_CONFIG_FILE → generateChannelApkByConfigFile(...)
- PROPERTY_CHANNEL_FILE → generateChannelApkByChannelFile(...)
- configFile instanceof File → generateChannelApkByConfigFile(...)
- channelFile instanceof File → generateChannelApkByChannelFile(...)
- variantConfigFileName != null && variantConfigFileName.length() > 0 → generateChannelApkByConfigFile(...)
上述方法各自有不同的处理,但最终调用的都是:generateChannelApk
:
呕吼,接着看看ChannelWriter是怎么写入渠道信息的~
3. ChannelWriter
跟下 put()
方法,最后调用的都是:
跟下 putRaw()
方法:
定位下 APK_CHANNEL_BLOCK_ID
:
哈,这个插件把渠道信息写到 APK Signing Block
里的 ID,跟下 PayloadWriter.put()
。
4. PayloadWriter
一步步跟,跟到putAll,看上面的代码似乎很复杂的样子?其实不然先是 ApkSigningBlockHandler
回调接口,定义了一个handle方法:
传入了一个 originIdValues
,其实就是apk本身带的ID-value,然后遍历新的 idValues
,写入其中,最后通过 addPayload()
将数据塞到 ApkSigningBlock
实例中返回。
接着是v3签名的一些处理,然后把渠道信息写到apk里,这里就真的是 技术活,太强了!!!
byte级别精细化的文件操作,这功底...我的确是个假安卓,跟到 ApkSigningBlock → writeApkSigningBlock()
就是对应上表,把区块2的内容写回apk中:
把渠道包信息打入到apk的大概流程就这样,任务的执行时机也在 assemble
后。
写弄懂了,接着看下读,官方文档中写道:
APP还要另外依赖这个aar,在运行时读取对应的渠道信息
跟下 WalleChannelReader
,在项目的 library
目录下:
5. WalleChannelReader
传入comtext,获取apk的路径,接着传入 ChannelReader.get()
:
6. ChannelReader
跟下getMap():
跟下 PayloadReader.getString()
7. PayloadReader
getString() → get() → getAll()
RandomAccessFile.getChannel() 获得文件通道对象,然后传入 ApkUtil.findApkSigningBlock()
就是找到 APK Signing Block
区块,返回ByteBuffer实例,然后 ApkUtil.findIdValues(apkSigningBlock2)
获取Id-Values 们,此时再回到 ChannelReader → get()
处,就懂了吧。
这是调用 getChannel()
的实现,如果根据 key
获取则是走 getChannelInfoMap()
,流程比较相似,就不再复述了。
以上就是此插件实现的完整讲解,当然核心难点Byte级别的文件操作,后面解完apk构建过程再去研究研究~