Android系统下的动态Dex加载

1 问题
在Android系统中,一个App的全部代码都在一个Dex文件里面。Dex是一个相似Jar的存
储了多有Java编译字节码的归档文件。由于Android系统使用Dalvik虚拟机,因此须要把
使用Java Compiler编译以后的class文件转换成Dalvik可以执行的class文件。这里须要强
调的是,Dex和Jar同样是一个归档文件,里面仍然是Java代码对应的字节码文件。
当Android系统启动一个应用的时候,有一步是对Dex进行优化,这个过程有一个专门的
工具来处理,叫DexOpt。DexOpt的执行过程是在第一次加载Dex文件的时候执行的。这
个过程会生成一个ODEX文件,即Optimised Dex。执行ODex的效率会比直接执行Dex文
件的效率要高不少。可是在早期的Android系统中,DexOpt有一个问题,也就是这篇文
章想要说明并解决的问题。DexOpt会把每个类的方法id检索起来,存在一个链表结构
里面。可是这个链表的长度是用一个short类型来保存的,致使了方法id的数目不可以超过
65536个。当一个项目足够大的时候,显然这个方法数的上限是不够的。尽管在新版本的
Android系统中,DexOpt修复了这个问题,可是咱们仍然须要对老系统作兼容。
2 思路
一种有效的解决思路是把Dex文件分割成多个较小的Dex。这就如同不少项目会把本身分
割成多个Jar文件同样,不一样的功能在不一样的Jar文件里面,经过一些配置和额外的操做,
可让虚拟机有选择性的加载Jar文件。可是在Android系统中,一个应用是只容许有一
个Dex文件的。也就是说在编译期的时候,全部的Jar文件最终会被合并成一个Dex文件。
咱们没有办法在Apk文件里面打包两个Dex,让DexOpt分别对两个Dex文件作处理,而
Android系统也不会同时为一个Apk加载两个Dex。
1
2.1 动态加载
若是咱们把Dex分红多个文件,而后在程序运行的时候,再把多的那几个动态的加载进来
是否可行呢?也就是说咱们可否在运行时阶段把代码加入虚拟机中。对于虚拟机来讲,其
实全部的代码都是在运行时被加载进来的。而不一样于C语言还存在着静态连接。虚拟机在
全部Java代码执行以前被启动,而后开始把字节码加载到环境中执行,咱们能够理解成所
有的代码都是动态加载到虚拟机里的。
而说到加载,不得不说的是ClassLoader。它的工做就是加载.class文件。在Android的
Dalvik环境中,对应的是DexClassLoader,它们的功能是彻底同样的。ClassLoader的
一大特色就是它是一个树状结构。每一个ClassLoader都有一个父亲ClassLoader。也就是
说,ClassLoader不是把全部的Class放到一个巨大的数组或别的什么数据结构中来处理。
ClassLoader在加载一个Jar中的类的时候,须要制定另外一个ClassLoader做为父亲节点,
当咱们须要经过ClassLoader获得一个类类型的时候,ClassLoader会把请求优先交给父
亲ClassLoader来处理,而父亲ClassLoader又会交给它的父亲,一直到根ClassLoader。
若是根ClassLoader有这个类,而返回这个类的类类型,不然把这个请求交给这个请求的
来源子ClassLoader。这是一种向上传递,向下分发的机制。这种状况下,对于调用着来
说,子ClassLoader永远都是包含最多Class的ClassLoader。有一点咱们须要注意,父亲
ClassLoader只会向请求来源分发本身的处理结果。因此若是来源是本身,那么若是没有
请求类它就会返回空,而不是遍历全部子ClassLoader去请求是否有被请求的类。
在Android系统中,对于一个应用来讲,其实有两个ClassLoader,一个是SystemClassLoader,这个ClassLoader里面除了Java标准的类库以外,还有一个android.jar,全部
Android Framework层的类都在这里。而另一个重要的ClassLoader就是基于Android
Context的ClassLoader。全部属于当前应用的类都是用这个ClassLoader来加载的,咱们
能够在Android源码中看到,全部的Activity,Service,View都是使用这个ClassLoader
来反射并建立的。咱们暂时把它叫作ContextClassLoader。
3 加载外部Dex
3.1 构建一个Dex文件
这一步并不复杂,首先咱们把所须要的.class文件或者是Jar文件和一些源码一块儿编译生
成一个Jar文件。而后使用Android SDK提供的dx工具把Jar文件转成Dex文件。咱们能够
提早对它进行ODex操做,让它在被DexClassLoader加载的时候,跳过DexOpt的部分工
做,从而加快加载的过程。
2
3.2 DexClassLoader
如今的工做就是在运行时加载这个Dex文件了。咱们能够在Application启动的onCreate
方法里面加载Dex,可是若是你的Dex太大,那么它会让你的App启动变慢。咱们也能够
使用线程去加载,但咱们必须保证加载完成以后再进行某个外部类的请求。固然也能够真
正等到须要某个外部类的时候再进行Dex加载。这根本上取决于Dex文件自己的大小,太
大了能够预加载,而比较小能够等到实际须要的时候再加载。咱们暂且把这个加载了外部
Dex的ClassLoader成为ExternalClassLoader
上面咱们提到了树形结构和系统中的多个ClassLoader,当咱们加载外部Dex的时候,我
们是否须要指定一个父ClassLoader呢?咱们固然须要一个父ClassLoader,不然咱们ExternalClassLoader连一些基本的Java类都没有,它根本不可能成功的加载一个Dex。进一
步的,咱们要选择哪个ClassLoader来做为咱们的父亲呢?是SystemClassLoader仍是
ExternalClassLoader?这是根据状况来定的,若是外部的Dex文件里没有任何和Android
相关的代码,那么SystemClassLoader是咱们的首选,不然咱们就应该用ContextClassLoader。若是是后者的状况,咱们的树能够被当作一个链表。
3.3 外部的View, Acitivity等
咱们知道,咱们编写的四大组建都不是由咱们本身来建立的,是由系统来给咱们构造并
管理其生命周期的。那么这个过程是什么样的呢?拿Activity来举例,咱们须要经过调用
当前Activity/Context的startActivity,传入一个Intent来调用启动一个新的Activity。系
统有一个ActivityManager来处理这里的逻辑。这里的逻辑至关的复杂,但简单来讲,
ActivityManager会收到并处理这个Intent,从而决定是是启动一个新的,仍是把旧的放
到前台。它会先查找这个Activity在哪一个应用里面,这是经过扫描每一个应用的AndroidManifest来肯定。这些信息是在PackageManager里面被检索的。总之若是这个Activity
再也不任何的manifest里面,它就不可能被启动。因此仅有一个Activity类是不够的,咱们
须要在manifest里面声明它。
上面是Activity的状况,Service之类的也是同理。那么View怎么办?尽管咱们能够直接创
建View,可是大部分的View都不是咱们建立的,而是经过XML布局文件Inflate出来的。
也就是说,咱们在XML定义了一些外部Dex里面的View,那么显然这个XML是不能被成
功的Inflate的。由于除非系统会使用咱们的ExternalClassLoader,不然它确定是找不到
咱们的类的:ContextClassLoader里面并无外部Dex中的类。
也就是说问题的根本在于,对于那些Android系统为咱们建立的对象,它是不能包含在外
部Dex里面的。而Android系统中大部分的组建类的生命周期都交给了系统来管理。咱们
3
不可能本身来建立这些类对象。那么另外一种思路:咱们是否是能够经过使用咱们的ExternalClassLoader来代替ContextClassLoader呢?尽管系统的ContextClassLoader是私有
的,可是咱们能够经过反射强制的把它替换成咱们的ExternalClassLoader。而对于那些
外部的组建(Activity等),尽管咱们没有它们的类,可是并不影响咱们在AndroidManifest里面声明这个Activity。由于Android系统只是把它做为一个检索,并不会真正检查它
里面的组建是否是真的在虚拟机环境中已经被加载了,只有真正使用Intent启动某个组建
的时候才会去检查。而只要咱们保证这个时候咱们已经加载了外部的ClassLoader,那么
这个组建就能够被正常的启动。
还有一点,除了咱们要为外部可能有的组建在AndroidManifest里面作声明一外,那些外
部组建可能用到的权限咱们也须要一一声明,例如若是外部Activity使用了相机功能,那
么若是咱们的Manifest里面没有声明使用相机功能的权限的话,即使这个Activity能成功
为加载出来,仍然是不能使用的。
4 核心代码段
加载外部Dex
mClassLoader = new DexClassLoader ( f . getAbsolutePath ( ) ,
mContext . getCacheDir ( ) . getAbsolutePath ( ) ,
null , mContext . getClassLoader ( ) ) ;
让系统使用ExternalClassLoader
t r y {
F ie ld mMainThread = ge t F ie ld ( A c t i v i t y . class , ”mMainThread”) ;
Object mainThread = mMainThread . get ( a c t i v i t y ) ;
Class t hreadClass = mainThread . get Class ( ) ;
F ie ld mPackages = ge t F ie ld ( threadClass , ”mPackages”) ;
WeakReference<?> r e f ;
Map< St ring , ?> map =(Map< St ring , ? >) mPackages . get (mainThread ) ;
r e f = (WeakReference<? >) map . get (mContext . getPackageName ( ) ) ;
Object apk = r e f . get ( ) ;
Class apkClass = apk . get Class ( ) ;
F ie ld mClassLoader = ge t F ie ld ( apkClass , ”mClassLoader”) ;
mClassLoader . set (apk , classLoader ) ;
} catch ( I llegalArgument Except ion e) {
i f (DEBUG) {
e . print St ackTrace ( ) ;
}
} catch ( I llega lAc c essEx c ept ion e) {
i f (DEBUG) {
4
e . print St ackTrace ( ) ;
}
}
5android

相关文章
相关标签/搜索