一、背景
随着京东金融Android项目功能的不断增加,代码数量也随之急剧增加,虽然使用了MultiDex的处理,但是在不断的加入更多类之后,我们依旧面临着以下挑战:
问题1:编译时出现错误com.Android.dex.DexException: Toomanyclasses in --main-dex-list, main dex capacity exceeded
产生原因:主dex超过最大方法数限制。一个DEX文件中method个数采用使用原生类型short来索引文件中的方法,也就是4个字节共计最多表达65536个method,field/class的个数也均有此限制。
问题2:低端机上黑屏或白屏,冷启动速度过慢
产生原因:dex加载耗时。在项目中使用Google提供的MultiDex后dex拆分为多个,而Android运行时只默认加载classes.dex这个文件(即主dex)。其他dex会在Application.attachBaseContext中使用Multidex.install进行解压缩和加载。Multidex.install是一个耗时大户,会堵塞主线程,从而使主线程无法响应Activity的创建和初始化。而5.0及以上版本已经不存在这个问题,是由于ART模式的存在,app第一次安装之后会进行一次预编译(pre-compilation),如果这时候发现了classes(..N).dex文件就会将他们最终合成为一个.oat,并在app启动时一次性加载。
二、解决方案
面向问题1的解决方案:只要保证主dex中的方法数少于65536个,在gradle编译脚本中加入dx.additionalParameters +='--set-max-idx-number=xxx'参数即可解决。
面向问题2的解决方案:首先想到的就是异步加载,但如果此时classloader需要加载某个类,而这个类却没有在主dex中,就会出现ClassNotfindException的异常崩溃,所以其实解决这两个问题的根本方法是指定打包时将哪些类放到主dex中。
三、Scalpel介绍
Scalpel是京东金融客户端研发的业界首个可以干预主dex生成的工具,可以根据配置文件指定某些符合规则的、及其必要的依赖类放到主dex中。除了根据配置文件,Scalpel还会自动分析你的项目,把一些必须放入主dex中的类找到并放进去。
在使用MultiDex时,BuildTool会自动把代码拆成多个dex包,并通过扫描把Application , Activity , Service , ContentProvider和BrocastReceiver以及必要依赖类放进主dex包中,以下是Android的打包流程图:
为了实现指定主dex中的类,我们在生成dex文件这一步时,使用gradle自定义Task来干预dex的生成过程,由于人工分析class有着不可维护性和高风险性,Scalpel则完美解决了对人工的依赖,从而极大的提高了生产效率。
具体用法(写法和混淆文件类似):
1.工程根目录gradle.build中添加以下内容
repositories {
jcenter();
maven {
url "http://dl.bintray.com/gokuo/maven"
}
}
dependencies{
classpath 'com.jd.jrapp.plugin:Scalpel:1.0.1'
}
2:项目目录下build.gradle中添加apply plugin: 'com.jd.jrapp.scalpel'
3:项目目录下添加scalpel_rule.pro规则文件,并填写以下内容
如:
(1)把supportv4包下所有的类和相关必要依赖类都打进主dex
-keep class android.support.v4.**{*;}
(2)把Fragment类打进主dex
-keep class android.support.v4.app.Fragment
(3)把Fragment类和相关必要依赖类打入主dex
-keep class android.support.v4.app.Fragment{*;}
(4)把所有Fragment的子类打入主dex
-keep class * extends android.support.v4.app.Fragment
四、实现原理
请参见源码:https://github.com/gokuo/scalpel
五、提供支持
您如果有任何问题,可以在开源项目中提交bug报告,也可以发送邮件至 gaokuo@jd.com