c说明:该篇取自我CSDN的博客http://blog.csdn.net/gaozhan_csdn/article/details/51992100
参考资料:
《Android开发艺术探索》
博客中间会涉及到dex文件的反编译,参考博文:
对于功能越来越复杂的app的两大问题
问题一:当项目越来越大,方法数超过65536,编译时会出错(为什么是65536,参考下面关于dexopt对方法id检索存储介绍),这个所说的方法数包含用到的框架,依赖的jar包,当然还有我们应用本身的代码中的所有方法(我们自己写的)。
我们可以写个Demo看看报的具体错误。
那我们写个65536以上个方法,可以用java的IO流向一个txt里写入65537个方法。
public class MethodWriter {
public static void main(String[] args) throws IOException{
FileWriter fw =new FileWriter("demo.txt");
for(int i =1; i <=65537; i++){
fw.write("public void me"+ i +"(){ }\r\n");
fw.flush();
}
fw.close();
}
}
然后复制txt文件里的方法到AS工程里即可。注意,将这些方法分别放在几个类下面,保证每个类不要超过65536。我们所说的65536限制是整个项目的限制。下面,我们分两种方案放置这些方法,运行项目,看看AS会有什么结果。
方案一:我们自己应用的方法数超过了65536
我们所说的方法数限制,这个方法数包括了jar包,框架,还有我们自己应用的代码,当我们应用的代码超过65536时,结果如下:
我们看到,显示我们方法的引用是65579.而引用数最大是65536,建议我们开启分包方案。
方案二:我们应用的方法数没有超过65536,但是加上依赖的jar包,框架等,超过了65536(根据方案一的结果,我们应用方法数是65579,那我们删掉200个方法,就小于65536)
报错如下:
问题二:方法数并没有超过65536,编译也完成了,但是在android2.3以前的系统安装的时候,会异常中止安装。
这个问题会发生在Android 2.2以及Android 2.3的设备上,涉及到一个名为dexopt的程序,全称dex optimization,即dex文件优化程序。在优化过程中,dexopt采用一个固定大小的缓冲区(LinearAlloc)来存储应用中所有方法的信息,那么之所以会出现在老版本停止安装,是因为老版本的缓冲区的大小是5M,而在新版本中,这个缓冲区的大小是8M或者16M,在老版本中更容易超过这个限制。
dexopt的执行过程是在第一次加载dex文件的时候执行的。这个过程产生了一个ODEX文件,全称Optimised Dex。这个ODEX文件是在安装过程中从apk里提取出的可运行文件,是优化dex产生的,再把apk包里的dex文件删除,这样就做到了预先提取。如果没有ODEX文件,那么系统会从apk包中提取dex然后再运行。所以优化后可以加快软件的启动速度,预先提取,减少对RAM的占用。
在早期的Android系统中,dexopt会把每一个类的方法id检索起来,存在一个链表结构里,而这个链表的长度是用一个short类型来保存的,导致方法id的数目不能够超过65536个。虽然新版本的android系统中,dexopt修复了这个问题,但是老版本的android系统的用户市场占有率还是占一定比例,还是不能放弃这部分用户的,所以我们在开发中需要对老版本的这个问题进行兼容。
插件化技术
我们可以采用动态加载部分dex,通过将一个dex拆分成两个或多个dex,解决方法数越界的问题。
插件化是一套重量级的技术方案,我们需要通过反射来调用插件的类或方法,要使用一套插件框架来配合,而且插件化适合一些独立的模块,兼容性问题往往较多,如果只是用于解决方法数越界的话,并不是最好的方案。
multidex解决方案
为了解决方法数越界的问题,Google在2014年提出了multidex的解决方案,这个方案主要是针对AndroidStudio和Gradle编译环境的,将一个dex文件拆成两个或多个dex文件。
不过需要注意的是multidex有一个版本问题,在Android 5.0以前使用multidex需要手动引入Google提供的android-support-multidex.jar这个jar包。这个jar包我们可以在Android SDK目录下的extras/android/support/multidex/library/libs下找到。而从Android 5.0开始,Andorid默认支持了multidex。
所以,我们就需要注意我们的SDK版本了,如果已经支持了multidex,而我们又把android-support-multidex.jar放在了项目的libs文件下,就会报错。
在Gradle中配置使用Multidex
由于Android的Gradle插件在Android Build Tool 21.1开始支持使用multidex,所以我们需要使用Android Build Tools 21.1及以上版本,修改app目录下的build.gradle文件,有两点需要修改。
(1)在defaultConfig中添加multiDexEnabled true这个配置项。
(2)在dependencies中添加multidex的依赖:
compile ‘com.android.support:multidex:1.0.0’
注意buildToolsVersion要高于21.1,配置好如下:
在Gradle中配置好之后,我们还需要在代码中加入支持multidex的功能,有三种方案可选
方案一:在manifest文件中指定Application为MultiDexApplication,如下:
方案二:写一个Application类并继承MultiDexApplication,并在AndroidManifest.xml的application标签中进行注册(在application标签中增加name属性,并添加自己的Application类名即可),如果不是想重写MultiDexApplication中一些方法的话,还是方案一更方便些。如下:
注册如下:
方案三:如果不想按方案二继承,我们可以重写Application的attachBaseContext方法,注意,这个方法比onCreate方法先执行。具体方法是创建一个新类,继承Application,然后重写attachBaseContext方法,并在AndroidManifest.xml的application标签中进行注册(与方案二注册相同)如下:
对于在AndroidManifest.xml中注册,与方案二的注册相同。
我们的Demo图如下,我们根据该图和dex文件反编译的结果分析分包情况。
情况一:方法数没有越界
我们将方法数控制在65536以内,方法数没有越界的话,是不会分包的,解压apk,你会发现apk里只有一个classes.dex,如下
将其反编译后(不知道怎么反编译的可以看一下这篇博文:http://blog.csdn.net/gaozhan_csdn/article/details/51984056),结果如下:
可以发现,我们的类都在这个主dex文件里,并没有分包。
情况二:方法数越界
我们再将方法数增加到65536以上。解压apk,结果如下:
对三个dex文件反编译一下,看看它们里面分别都包含了什么类。
classes.dex(主dex)下的类视图:
classes2.dex的类视图
classes3.dex的类视图
可以发现Second类和Third类分别在classes2.dex文件和classes3.dex文件里,其他类都在主dex文件里(classes.dex),我们用multidex的确实现了分包从而解决了方法数越界的问题。
MultiDex使用起来很方便,但它也有一定的局限性。先总结一下网上查到的,后续研究。
由于需要加载额外的dex文件,应用的启动速度会降低,当其他dex文件较大的时候,甚至会出现ANR现象。
由于Dalvik linearAlloc的Bug,有可能导致使用multidex的应用无法在Android4.0以前的手机上运行。也是这个bug,可能出现应用在运行中由于采用了multidex方案从而产生大量内存消耗的情况,导致应用崩溃。
multidex涉及版本问题,所以使用的时候一定要注意,不同版本该不该手动导包
分包后,由于dex的加载问题,可能会出现找不到类的问题,所以我们需要研究一下如何将某个具体的类按我们的意愿放入到某个dex里
multidex源码
dex如何加载,加载机制