之前在介绍 dx 生成 mulitdex 工作原理 的时候,在 processAllFiles
处理所有 class 的时候会对 - -main-dex-list=<file>
设置的对应参数 this.args.mainDexListFile
进行判断。
|
|
现在我们聊一下这个 keep 文件的来历。
当然它的作用就是在 dx 将 class 拆分成多个 dex 文件的时候,保证 classes.dex 中必要的类 不 被分出去,避免应用在启动时会造成 classNotFoundException 。关键在于如何生成这个文件。
当我们使用 mulitdex 处理 apk 时候,在工程 app —> build —> intermidiates —> muliti-dex —>
maindexlist.txt
: 以 class 全路径形式列出了 keep 在 main dex 中的所有 class;manifest_keep.txt
: 读取 appManifest 中的 application class,keep 住 application class 实例化过程中的调用类。
而 Google 在 SDK Build tools 下有这样几个文件
mainDexClasses
: 这个脚本太长了,总结一下它的作用- 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859...# 省略前面寻找和判断文件路径的过程。disableKeepAnnotated=# 根据 usage 猜测传入参数为# ./mainDexClasses --output /Users/handsomeyang/Desktop/outDir --disable-annotation-resolution-workaround --aapt-rules /Users/handsomeyang/Desktop/mainDexClassesNoAapt.rules <inputFiles>while true; do# 当 $1 = --output 为 trued , 可以 man expr 看一下 : 用法if expr "x$1" : 'x--output' >/dev/null; then# 将标准输出重定向到 output ,1 代表标准输出exec 1>$2# 删除前两位参数,即 删掉 --output /Users/handsomeyang/Desktop/outDirshift 2# 现在 $1 就是 --disable-annotation-resolution-workaroundelif expr "x$1" : 'x--disable-annotation-resolution-workaround' >/dev/null; then# --disable-annotation-resolution-workaround 是禁用一种容易导致 bug 的 keep 方式disableKeepAnnotated=$1# 在把 --disable-annotation-resolution-workaround 删掉shift 1elif expr "x$1" : "x--aapt-rules" >/dev/null; then# ./mainDexClasses --aapt-rules /Users/handsomeyang/Desktop/mainDexClassesNoAapt.rulesextrarules=$2# 把剩下参数也删掉shift 2# 此时脚本参数状态: ./mainDexClasses inputFileselsebreakfidone# 把 optinos 删掉后,如果没有 input files 显示 usageif [ $# -ne 1 ]; thenecho "Usage : $0 [--output <output file>] <application path>" 1>&2exit 2fitmpOut=`makeTempJar`trap cleanTmp 0# "\" 表示命令换行,# -injars {class_path} 指定要处理的应用程序jar,war,ear和目录# -libraryjars {classpath} 指定要处理的应用程序jar,war,ear和目录所需要的程序库文件# -dontoptimize 不优化输入的类文件# -dontobfuscate 不混淆输入的类文件# -outjars {class_path} 指定处理完后要输出的jar,war,ear和目录的名称# -include {filename} 从给定的文件中读取配置参数# 1>/dev/null 表示不再控制台输出命令标准输出# 把 <inputFiles> 通过 maDexClasses.rules 和 mainDexClassesNoAapt.rules 规则混淆# 并且压缩资源,输出到 temOut 的 jar 文件中"${proguard}" -injars ${@} -dontwarn -forceprocessing -outjars "${tmpOut}" \-libraryjars "${shrinkedAndroidJar}" -dontoptimize -dontobfuscate -dontpreverify \-include "${baserules}" -include "${extrarules}" 1>/dev/null || exit 10# java -cp dx.jar com.android.multidex.MainDexListBuilder --disable-annotation-resolution-workaround tmpOut.jar <inputFiles># 把 混淆后的 jar 和 <inputFiles> 作为参数 调用 com.android.multidex.MainDexListBuilder 的 main 方法。java -cp "$jarpath" com.android.multidex.MainDexListBuilder ${disableKeepAnnotated} "${tmpOut}" ${@} || exit 11
找到
mainDexClass.rules
和dx.jar
路径找到
proguard.sh
和shrinkedAndroid.jar
路径生成
mainDexClass-<pid>.tmp.jar
根据
mainDexClasses.rules
和mainDexClassesNoAapt.rules
的 keep 规则进行混淆和资源压缩调用 dx.jar 中的
com.android.multidex.MainDexListBuilder
main 方法来生成 maindexlist.txt
mainDexClasses.rules
- 12345678910111213141516171819-keep public class * extends android.app.Instrumentation {<init>();}-keep public class * extends android.app.Application {<init>();void attachBaseContext(android.content.Context);}-keep public class * extends android.app.backup.BackupAgent {<init>();}# We need to keep all annotation classes because proguard does not trace annotation attribute# it just filter the annotation attributes according to annotation classes it already kept.-keep public class * extends java.lang.annotation.Annotation {*;}# Keep old fashion tests in the main dex or they'll be silently ignored by InstrumentationTestRunner-keep public class * extends android.test.InstrumentationTestCase {<init>();}
主要 keep 了 application 相关类。
mainDexClassesNoAapt.rules
- 123456789101112-keep public class * extends android.app.Activity {<init>();}-keep public class * extends android.app.Service {<init>();}-keep public class * extends android.content.ContentProvider {<init>();}-keep public class * extends android.content.BroadcastReceiver {<init>();}
keep 了四大组件,即 组件实例化所需要的相关类。
既然要计算 main dex 的 class 引用情况,那么混淆和资源压缩,首先去除没有的引用减少计算量很好理解。
那么来看下 com.android.multidex.MainDexListBuilder
这个类:
|
|
理下生成 maindexlist
流程:
mainDexClasses
脚本进行混淆 class file,调用 dx.jar 中MainDexListBuilder
Main 方法,将mainDexClasses.rules
和mainDexClassesNoAapt.rules
的 keep 规则作为参数传入;- main 方法处理参数,实例
ClassReferenceListBuilder
调用addRoots
添加依赖; - 判断 class 是否带有 运行时可见 注解
mainDexListBuilder.getMainDexList()
返回 main dex 中需要的 class list;