Dex类分包的规则
我们只是指定了multiDexEnabled,那系统会将那些类放在主dex?其实它利用的是Android sdk build tool中的mainDexClasses脚本,这在版本21以上才会有。使用方法非常很简单:
mainDexClasses [--output <output file>] <application path>
该脚本要求输入一个文件组(包含编译后的目录或jar包),然后分析文件组中的类并写入到–output所指定的文件中。实现原理也不复杂,主要分为三步:
a. 环境检查,包括传入参数合法性检查,路径检查以及proguard环境检测等。
b. 使用mainDexClasses.rules规则,通过Proguard的shrink功能,裁剪无关类,生成一个tmp.jar包。
c. 通过生成的tmp jar包,调用MainDexListBuilder类生成主dex的文件列表。
这里只是简单的得到所有入口类(即rules中的Instrumentation、application、Activity、Annotation等等)的直接引入类。何为直接引用类?在init过程,会在校验阶段去resolve它各个方法、变量引用到的类,这些类统称为某个类的直接引用类。举个栗子:
public class MainActivity extends Activity { protected void onCreate(Bundle savedInstanceState) { DirectReferenceClass test = new DirectReferenceClass(); } } public class DirectReferenceClass { public DirectReferenceClass() { InDirectReferenceClass test = new InDirectReferenceClass(); } } public class InDirectReferenceClass { public InDirectReferenceClass() { } }
上面有MainActivity、DirectReferenceClass、InDirectReferenceClass三个类,其中DirectReferenceClass是MainActivity的直接引用类,InDirectReferenceClass是DirectReferenceClass的直接引用类。而InDirectReferenceClass是MainActivity的间接引用类(即直接引用类的所有直接引用类)。
加载Dex的方式
对于5.0以下的系统,我们需要在启动时手动加载其他的dex。而我们并没有要求得到所有的间接引用类,这是因为我们在attachBaseContext的时候,已将其他dex加载。例如:
public class HelloMultiDexApplication extends Application { @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); MultiDex.install(this); } }
attachBaseContext究竟处于生命周期的哪一步?可看下图:
事实上,若我们在attachBaseContext中调用Multidex.install,我们只需引入Application的直接引用类即可,mainDexClasses将Activity、ContentProvider、Service等的直接引用类也引入,主要是满足需要在非attachBaseContent加载多dex的需求。另一方面,若存在以下代码,将出现NoClassDefFoundError错误。
public class HelloMultiDexApplication extends Application { @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); DirectReferenceClass test = new DirectReferenceClass(); MultiDex.install(this); } }
这是因为在实际运行过程中,DirectReferenceClass需要的InDirectReferenceClass并不一定在主dex。解决方法是手动将该类放于dx的-main-dex-list参数中:
afterEvaluate { tasks.matching { it.name.startsWith('dex') }.each { dx -> if (dx.additionalParameters == null) { dx.additionalParameters = [] } dx.additionalParameters += '--multi-dex' dx.additionalParameters += "--main-dex-list=$projectDir/<filename>".toString() } }
Android提供的方案,或者延伸为在attachBaseContext中同步加载dex的方案,它的好处是非常简单,所需的依赖集也非常少。但是它的缺点也非常明显,即若其他dex比较大,首次加载时会出现明显的黑屏,甚至会出现ANR。
Dex形式
暂时我们还是放于assets下,以assets/secondary-program-dex-jars/secondary-N.dex.jar命名。为什么不以classes(..N).dex?这是因为一来觉得以Android的推广速度,5.0用户增长应该是遥遥无期的,二来加载Dex的代码,传进去的是zip,在加载前我们需要验证MD5,确保所加载的Dex没有被篡改(Android官方没有验证,主要是只有root才能更改吧)。
/** * Makes an array of dex/resource path elements, one per element of * the given array. */ private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory, ArrayList<IOException> suppressedExceptions) {
事实上,应该传进去的是dex也是应该可以的,这块在下一个版本将采用classes(..N).dex。但是如果我们使用了线程加载,并且弹出提示界面,对用户来说并不是无法接受。
Dex类分包的规则
分包规则即将所有Application、ContentProvider以及所有export的Activity、Service、Receiver的间接依赖集都必须放在主dex。对于微信现在来说,这部分大约有41306个方法,每次通过扫描AndroidMifest计算耗时大约为20s不到。怎么计算?可以参考buck或者mainDexClasses的做法。
public MainDexListBuilder(String rootJar, String pathString) throws IOException { path = new Path(pathString); ClassReferenceListBuilder mainListBuilder=new ClassReferenceListBuilder(path);