背景:
刚刚通过MultiDex 解决了Android 65535 的坑,怎么又出现了这个问题? MultiDex 是官方的解决方案难道还有问题?
先来说下MultiDex 是怎么回事
分两步:
1.修改gradle脚本来产生多dex。
2.修改manifest 使用MulitDexApplication。
步骤1.在gradle脚本里写上:
android {
compileSdkVersion 21
buildToolsVersion "21.1.0"
defaultConfig {
...
minSdkVersion 14
targetSdkVersion 21
...
// Enabling multidex support.
multiDexEnabled true
}
...
}
dependencies {
compile 'com.android.support:multidex:1.0.0'
}
步骤2. manifest声明修改为MultiDexApplication
如果有自己的Application,继承MulitDexApplication。如果当前代码已经继承自其它Application没办法修改那也行,就重写 Application的attachBaseContext()这个方法。
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
}
run一下,可以了!但是dex过程好像变慢了。。。
文档还写明了multiDex support lib 的局限。瞄一下是什么:
1.在应用安装到手机上的时候dex文件的安装是复杂的(complex)有可能会因为第二个dex文件太大导致ANR。请用proguard优化你的代码。呵呵
2.使用了mulitDex的App有可能在4.0(api level 14)以前的机器上无法启动,因为Dalvik linearAlloc bug(Issue 22586) 。请多多测试自祈多福。用proguard优化你的代码将减少该bug几率。呵呵
3.使用了mulitDex的App在runtime期间有可能因为Dalvik linearAlloc limit (Issue 78035) Crash。该内存分配限制在 4.0版本被增大,但是5.0以下的机器上的Apps依然会存在这个限制。
4.主dex被dalvik虚拟机执行时候,哪些类必须在主dex文件里面这个问题比较复杂。build tools 可以搞定这个问题。但是如果你代码存在反射和native的调用也不保证100%正确。呵呵
感觉这就是个坑啊。补丁方案又引入一些问题。但是插件化方案要求对现有代码有比较大的改动,代价太大,而且动态化加载框架意味着维护成本更高,会有更多潜在bug。所以先测试,遇到有问题的版本再解决。
4.0 系统 ANR 了 ?
问题又来了!这次不仅仅是2.3 的机型!还有一些中档配置的4.x系统的机型。问题现象是:第一次安装后,点击图标,1s,2s,3s... 程序没有任何反应就好像你没点图标一样。
5s过去。。。程序ANR!
其实不仅仅总悟君的App存在这个问题,其他很多App也存在首次安装运行后几秒都无任何响应的现象或者最后ANR了。唯一的例外是美团App,点击图标立马就出现界面。唉要不就算啦?反正就一次。。。不行,这可是产品给用户的第一印象啊太重要了,而且美团搞得定就说明这问题有解决方案。
ANR了是不是局限1描述的现象??不过也不重要...因为Google只是告诉你说第二个dex太大了导致的。并没有进一步解释根本原因。怎么办?Google一发?搜索点击图标 然后ANR?怎么可能有解决方案嘛。ANR就意味着UI线程被阻塞了,老老实实查看log吧。
adb logcat -v time > log.txt
于是发现 是 install dex + dexopt 时间太长!
梳理一下流程:
安装完app点击图标之后,系统木有发现对应的process,于是从该apk抽取classes.dex(主dex) 加载,触发 一次dexopt。
App 的laucherActivity准备启动 ,触发Application启动,
Application的 onattach()方法调用,这时候MultiDex.install()调用,classes2.dex 被install,再次触发dexopt。
然后Applicaition onCreate()执行。
然后 launcher Activity真的起来了。
这些必须在5s内完成不然就ANR给你看!
有点棘手。首先主dex是无论如何都绕不过加载和dexopt的。如果主dex比较小的话可以节省时间。主dex小就意味着后面的dex大啊,MultiDex.install()是在主线程里做的,总时间又没有实质性改变。install() 能不能放到线程里做啊?貌似不行。。。如果异步化,什么时候install完成都不知道。这时候如果进程需要seconday.dex里的classes信息不就悲剧?主dex越小这个错误几率就越大。要悲剧啊总悟君。
淡定,这次Google搜索MultiDex.install 。于是总悟君发现了美团多dex拆包方案。 读完之后感觉看到胜利曙光。美团的主要思路是:精简主dex+异步加载secondary.dex 。对异步化执行速度的不确定性,他们的解决方案是重写Instrumentation execStartActivity 方法,hook跳转Activity的总入口做判断,如果当前secondary.dex 还没有加载完成,就弹一个loading Activity等待加载完成,如果已经加载完成那最好不过了。不错,RTFSC果然是王道。 可以试一试。
但是有几个问题需要解决:
1.分析主dex需要的classes这个脚本比较难写。。。Google文档说过这个问题比较复杂, 而且buildTools 不是已经帮我们搞定了吗?去瞄一下主dex的大小:8M 以及secondary.dex 3M 。 它是如何工作的?文档说dx的时候,先依据manifest里注册的组件生成一个 main-list,然后把这list里的classes所依赖的classes找出来,把他们打成classes.dex就是主dex。剩下的classes都放clsses2.dex(如果使用参数限制dex大小的话可能会有classe3.ex 等等) 。主dex至少含有main-list 的classes + 直接依赖classes ,使用mini-main-list参数可以仅仅包含刚才说的classes。
关于写分析脚本的思路是:直接使用mini-main-list参数获取build目录下的main-list文件,这样manifest声明的类和他们的直接依赖类搞定的了,那后者的直接依赖类怎么解?这些在dvk runtime也是必须的classes。一个思路是解析class文件获得该class的依赖类。还一个思路是自己使用Dexclassloader 加载dex,然后hook getClass()方法,调用一次就记录一个。都挺折腾的。
2.由于历史原因,总悟君在维护的App的manifest注册的组件的那些类,承载业务太多,依赖很多三方jar,导致直接依赖类非常多,而且短时间内无法梳理精简,没办法mini化主dex。
3.Application的启动入口太多。Appication初始化未必是由launcher Activity的启动触发,还有可能是因为Service ,Receiver ,ContentProvider 的启动。 靠拦截重写Instrumentation execStartActivity 解决不了问题。要为 Service ,Receiver ,ContentProvider 分别写基类,然后在oncreate()里判断是否要异步加载secondary.dex。如果需要,弹出Loading Acitvity?用户看到这个会感觉比较怪异。
结合自身App的实际情况来看美团的拆包方案虽然很美好然但是不能照搬啊。果然不能愉快地回家看动漫了。
相关资料
MultiDex中出现的main dex capacity exceeded解决之道
...
网友评论