分析 MultiDexTransform
当我们在 gradle 中将 multiDexEnabled 设为 true 后,编译 app 的过程中 Terminal 会多出一行: :app:transformClassesWithMultidexlistForDebug
显然 MultiDex 相关操作也是通过 Transform Api 完成了,自然我们查看 MultiDexTransform 源码,直接看 #transform 方法:
|
|
哟吼,核心代码好少啊,一个 shrinkWithProguard, 一个 computeList。
shrinkWithProguard
当我看到了方法名叫 shrinkWithProguard ,感觉很亲切啊,这不就是混淆器嘛,然后联想起 app 编译过程中输出的 app/build/intermediates/multi-dex/debug/ 下的那几个文件了:
其中 manifest_keep.txt 里的内容:
|
|
我的乖乖,shrinkWithProguard 方法势必和混淆器有扯不断的关系咯,来看看 shrinkWithProguard 具体的实现:
|
|
有点长,但是结构很清晰,我把上面代码块分为了7个部分:
=> 1
第一部分是干嘛的?我以第一个 dont 方法为例,dontobfuscate:
|
|
configuration 是 proguard 里的一个配置类,换言之,这样写的效果等同于我们在给 app 做混淆的时候在 proguard-rules.pro 写:
|
|
好的,第一部分代码其实就是对混淆进行了配置。
=> 2
那接下来的第二部分就太好理解了,applyConfigurationFile(manifestKeepListProguardFile); :
|
|
manifestKeepListProguardFile 就是之前提到的 manifest_keep.txt,等于把 manifest_keep.txt 里的 keep 规则也加了进来。
=> 3
那第三部分和第二部分也是一样的咯,第三部分相当于是给开发人员的外部拓展入口,在 build.gradle 中配置:
|
|
=> 4
第四部分就是一大堆 keep 规则,包括 keep Application 、Annotation 啦。
以上四部分就是把 keep 规则搞好了,继续看第五步,比较重要,先看 findShrinkedAndroidJar
|
|
返回的是 Android SDK 的 build-tools 里的 shrinkedAndroid.jar
=> 5
那很明显了,第五部分就是把 shrinkedAndroid.jar 和刚刚的 input 文件都加入 classpath 里。
=> 6
第六部分则是定义了一下相关输出文件。
=> 7
第七部分运行混淆器。
从以上流程我们能得知,shrinkWithProguard 就是将我们的原来编译好的 jar 文件在使用 proguard 后输出了一个满足规则的 jar ,这个 jar 在哪?下图里的 componentClasses.jar 就是了,并且 components.flags 就是 shrinkWithProguard 中前四步所生成的 keep 规则。
computeList
来看源码:
|
|
先看看 callDx :
|
|
再看 createMainDexList:
|
|
从上面的代码很明显能得知 createMainDexList 中调用了 com.android.multidex.ClassReferenceListBuilder 的 main 方法,然后将所得的 Set 进行返回,那么 ClassReferenceListBuilder 的 main 方法执行了啥?
|
|
将参数按顺序又实例化了一个 MainDexListBuilder,然后通过这个对象调用 getMainDexList() 取出 MainDexList,最后再做输出,那么看看 MainDexListBuilder :
|
|
filesToKeep 变量最终的结果就是在 computeList 中的 mainDexClasses 的结果,那么在这个类里有两处地方调用了 filesToKeep.add,一处是 keepAnnotated 里,当存在运行时可见注解时会添加进来,另外一种就是遍历 mainListBuilder.getClassNames(),来看看这个又从哪来的?
首先用 allClassesJarFile 的 path 实例化 ClassReferenceListBuilder,然后将 jarOfRoots(这个 jar 文件就是我们执行 shrinkWithProguard 后生成的 componentClasss.jar) addRoots 到 ClassReferenceListBuilder 中,来看看 addRoots:
|
|
可以看到 classNames 变量是收集符合要求后的 classes 的集合,同时更应该看到这里的 keep 包括了两部分,一个是 jarOfRoots 文件的 root class,另一个是这个 root class 的直接引用,关于 keep 住 root class 的引用部分涉及到常量池,需要单开一篇文章做讲解,这里只要知道他 keep 住了这个 root class 的直接引用,以防运行这个 dex 时找不到类或方法。
到此,我们总算分析出了 callDx 干了啥,简单说就是通过 shrinkWithProguard 后生成的 componentClasss.jar 找出了所有应该在 mainDex 中出现的 class。
那么 callDx 下方还有段代码,很简单了,通过在 build.gradle 中配置需要加在 mainDex 的方法,如 multiDexKeepFile file(‘./main_dex_list.txt’)
最后会把所有在 mainDex 里的 class 输出在 maindexlist.txt 中:
小结 MultiDexTransform
以上终于把 MultiDexTransform 讲完了,一句话总结,其实我们就是弄清楚了 mainDex 是如何得来的。那么这还不够啊,搞了半天才输出了一个 maindexlist.txt,所以继续搞起。
分析 DexTransform
|
|
在 app 编译过程中,在 MultiDex 后面后面执行的 Task 可谓是相当重要了,众所周知,将 class 文件转成 dex 文件就是这个 Task 做的了,那么先来看看 DexTrasnform 的构造函数:
|
|
其中重要的变量大家肯定一眼就看出来了,一个是 multiDex 的 boolean,一个是 mainDexListFile 的 File,来看看是在哪里实例化的:
|
|
可以看到,在 MultiDexTransform 实例化之后就去实例化了 DexTransform,实际上是将是否开启了 multidex 和 MultiDexTransform 生成的 maindexlist.txt 传给了 DexTransform,拿了参数做了啥?来看看 DexTransform 的 transform 方法:
|
|
继续往,由于调用链比较深,需要重点关注的我再单独贴代码:
- AndroidBuilder#convertByteCode =>
- DexByteCodeConverter#convertByteCode =>
- DexProcessBuilder#build =>
- ProcessInfoBuilder#createJavaProcess =>
- com.android.dx.command.Main#main =>
- com.android.dx.command.dexer.Main#main =>
- com.android.dx.command.dexer.Main#run,这个 run 可以看下:
|
|
这里判断了是否需要运行 MultiDex,如果需要则执行 com.android.dx.command.dexer.Main 的 runMultiDex 方法,这个 Main 类相当重要,也比较复杂,建议自行阅读,我只把 runMultiDex 方法执行的意义说一下:
|
|
一共分成五个部分:
=> 1
将 MultiDexTransform 生成的 maindexlist.txt 里的内容转成 classesInMainDex Set 集合。
=> 2
创建线程池,默认大小为 4 ,之后 每个 dex 的生成都会在单独线程去执行。
=> 3
这一步是核心步骤,将所有 classes 打成 mainDex 和 其他 dex,待会再看。
=> 4
将每个线程生成的 dex 字节流加入 dexOutputArrays 集合中。
=> 5
依次输出 classes.dex、classes2.dex …
刚刚第三部分留着没讲,现在来看看:
|
|
可以看到,先是强行将 maindexlist.txt 里的 class 打进 mainDex,再去处理其他的 dex,关于其他的 dex 是根据什么规则产生的,有兴趣的可以自行去研究。