Dex动态加载是为了解决什么问题?

在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修复了这个问题,但是我们仍然需要对老系统做兼容。

Dex动态加载思路

一种有效的解决思路是把Dex文件分割成多个较小的Dex。这就如同很多项目会把自己分割成多个Jar文件一样,不同的功能在不同的Jar文件里面,通过一些配置和额外的操作,可以让虚拟机有选择性的加载Jar文件。

但是在Android系统中,一个应用是只允许有一个Dex文件的。也就是说在编译期的时候,所有的Jar文件最终会被合并成一个Dex文件。我们没有办法在Apk文件里面打包两个Dex,让DexOpt分别对两个Dex文件做处理,而Android系统也不会同时为一个Apk加载两个Dex。

如果我们把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,一个是SystemClass-Loader,这个ClassLoader里面除了Java标准的类库之外,还有一个android.jar,所有Android Framework层的类都在这里。

而另外一个重要的ClassLoader就是基于Android Context的ClassLoader。所有属于当前应用的类都是用这个ClassLoader来加载的,我们可以在Android源码中看到,所有的Activity,Service,View都是使用这个ClassLoader来反射并创建的。我们暂时把它叫做ContextClassLoader。

加载外部Dex

首先构建一个Dex文件,这一步并不复杂,首先我们把所需要的.class文件或者是Jar文件和一些源码一起编译生成一个Jar文件。然后使用Android SDK提供的dx工具把Jar文件转成Dex文件。

我们可以提前对它进行ODex操作,让它在被DexClassLoader加载的时候,跳过DexOpt的部分工作,从而加快加载的过程。

现在的工作就是在运行时加载这个Dex文件了。我们可以在Application启动的onCreate方法里面加载Dex,但是如果你的Dex太大,那么它会让你的App启动变慢。

我们也可以使用线程去加载,但我们必须保证加载完成之后再进行某个外部类的请求。当然也可以真正等到需要某个外部类的时候再进行Dex加载。这根本上取决于Dex文件本身的大小,太大了可以预加载,而比较小可以等到实际需要的时候再加载。

我们暂且把这个加载了外部Dex的ClassLoader成为ExternalClassLoader.

上面我们提到了树形结构和系统中的多个ClassLoader,当我们加载外部Dex的时候,我们是否需要指定一个父ClassLoader呢?我们当然需要一个父ClassLoader,否则ExternalClassLoader连一些基本的Java类都没有,它根本不可能成功的加载一个Dex。

进一步的,我们要选择哪一个ClassLoader来作为我们的父亲呢?是SystemClassLoader还是ContextClass-Loader?

这是根据情况来定的,如果外部的Dex文件里没有任何和Android相关的代码,那么SystemClassLoader是我们的首选,否则我们就应该用ContextClass-Loader。如果是后者的情况,我们的树可以被看成一个链表。

外部的View, Acitivity等的处理

我们知道,我们编写的四大组件都不是由我们自己来创建的,是由系统来给我们构造并管理其生命周期的。那么这个过程是什么样的呢?

拿Activity来举例,我们需要通过调用当前Activity/Context的startActivity,传入一个Intent来调用启动一个新的Activity。系统有一个ActivityManager来处理这里的逻辑。这里的逻辑相当的复杂,但简单来说,ActivityManager会收到并处理这个Intent,从而决定是是启动一个新的,还是把旧的放到前台。它会先查找这个Activity在哪个应用里面,这是通过扫描每个应用的Android-Manifest来确定。这些信息是在PackageManager里面被检索的。总之如果这个Activity不在任何的manifest里面,它就不可能被启动。

所以仅有一个Activity类是不够的,我们需要在manifest里面声明它。上面是Activity的情况,Service之类的也是同理。那么View怎么办?

尽管我们可以直接创建View,但是大部分的View都不是我们创建的,而是通过XML布局文件Inflate出来的。也就是说,我们在XML定义了一些外部Dex里面的View,那么显然这个XML是不能被成功的Inflate的。因为除非系统会使用我们的ExternalClassLoader,否则它肯定是找不到我们的类的:ContextClassLoader里面并没有外部Dex中的类。

也就是说问题的根本在于,对于那些Android系统为我们创建的对象,它是不能包含在外部Dex里面的。而Android系统中大部分的组件类的生命周期都交给了系统来管理。我们不可能自己来创建这些类对象。

那么另一种思路:我们是不是可以通过使用我们的ExternalClassLoader来代替ContextClassLoader呢?尽管系统的ContextClassLoader是私有的,但是我们可以通过反射强制的把它替换成我们的ExternalClassLoader。

而对于那些外部的组件(Activity等),尽管我们没有它们的类,但是并不影响我们在AndroidManifest里面声明这个Activity。因为Android系统只是把它作为一个检索,并不会真正检查它里面的组件是不是真的在虚拟机环境中已经被加载了,只有真正使用Intent启动某个组件的时候才会去检查。而只要我们保证这个时候我们已经加载了外部的ClassLoader,那么这个组件就可以被正常的启动。

还有一点,除了我们要为外部可能有的组件在AndroidManifest里面做声明一外,那些外部组件可能用到的权限我们也需要一一声明,例如如果外部Activity使用了相机功能,那么如果我们的Manifest里面没有声明使用相机功能的权限的话,即便这个Activity能成功为加载出来,仍然是不能使用的。

Dex动态加载的更多相关文章

  1. [转载] Android动态加载Dex机制解析

    本文转载自: http://blog.csdn.net/wy353208214/article/details/50859422 1.什么是类加载器? 类加载器(class loader)是 Java ...

  2. Android 插件开发,做成动态加载

    为什么需要插件开发: 相信你对Android方法数不能超过65K的限制应该有所耳闻,随着应用程序功能不断的丰富,总有一天你会遇到一个异常: Conversion to Dalvik format fa ...

  3. Android应用安全之外部动态加载DEX文件风险

    1. 外部动态加载DEX文件风险描述 Android 系统提供了一种类加载器DexClassLoader,其可以在运行时动态加载并解释执行包含在JAR或APK文件内的DEX文件.外部动态加载DEX文件 ...

  4. Android动态加载jar/dex

    前言 在目前的软硬件环境下,Native App与Web App在用户体验上有着明显的优势,但在实际项目中有些会因为业务的频繁变更而频繁的升级客户端,造成较差的用户体验,而这也恰恰是Web App的优 ...

  5. Android 插件技术:动态加载dex技术初探

    1.Android动态加载dex技术初探 http://blog.csdn.net/u013478336/article/details/50734108 Android使用Dalvik虚拟机加载可执 ...

  6. 动态加载框架DL分析

    动态加载框架DL分析 插件化开发,主要解决三个问题1.动态加载未安装的apk,dex,jar等文件2.activity生命周期的问题,还有service3.Android的资源调用的问题 简单说一下怎 ...

  7. Android动态加载学习笔记(一)

    前言 上周五DPAndroid小分队就第二阶段分享内容进行了讨论,结果形成了三个主题:性能优化.动态加载.内核远离.我选择的是第二项——动态加载.在目前的Android开发中,这一部分知识还是比较流行 ...

  8. Android中插件开发篇之----动态加载Activity(免安装运行程序)

    一.前言 又到周末了,时间过的很快,今天我们来看一下Android中插件开发篇的最后一篇文章的内容:动态加载Activity(免安装运行程序),在上一篇文章中说道了,如何动态加载资源(应用换肤原理解析 ...

  9. Android中的动态加载机制

    在目前的软硬件环境下,Native App与Web App在用户体验上有着明显的优势,但在实际项目中有些会因为业务的频繁变更而频繁的升级客户端,造成较差的用户体验,而这也恰恰是Web App的优势.本 ...

随机推荐

  1. 有了这个,再也不用每次连新机器都要设置secure crt属性了

    我连服务器用的是secure crt,每次ssh新服务器的时候都得手动设置字符编码和背景颜色,今天问了旁边的开发原来可以全局设置,以后连服务器的时候就再也不用手动设置相关属性了.步骤如下: 一开始点击 ...

  2. ecshop 网站标题不更新或内容不更新

    网站标题不更新,这种情况一般出在网站搬家的过程中,把以前的所有配置文件一起都搬到新的服务器上了. 网站状态: 后台店铺标题已经修改,前台不显示,数据shop_config 的shop_title能更新 ...

  3. 快速lable内边距

  4. IIS7/IIS7.5中目录执行权限的设置方法

    我们在建站的时候,通常有些目录必须给写入权限,这个时候这些目录就很可能被人写入脚本文件,为了将安全性维护得更好,我们可以关闭这些有写入权限的目录的脚本执行权限.IIS6的时候,我们很容易找到关闭的地方 ...

  5. oss cmd

    osscmd是基于python 2.5.4(其他版本没有试过),用来操作OSS的,可使用命令行来上传和下载文件. 下载地址:http://storage.aliyun.com/leo/osscmd.t ...

  6. jQuery简单实现iframe的高度根据页面内容自适应的方法

    同域下: //注意:下面的代码是放在和iframe同一个页面中调用 $("#myiframe").load(function () { var myiframeH = $(this ...

  7. 404. Sum of Left Leaves

    Find the sum of all left leaves in a given binary tree. 左树的值(9+15=24) /** * Definition for a binary ...

  8. app接口的简单案例 和一些总结

    例一: 通过接口获取一篇文章.接口需要传入文章的id,通过sql语句向数据库查询文章的内容,然后以json的格式echo出即可,即:安卓或IOS工程师获取通过接口获取到了json格式的数据,在做进一步 ...

  9. C# MVC EF中匿名类使用

    控制器中代码: var list = context.Says.Join( context.Users, a => a.UserId, b => b.Id, (a, b) => ne ...

  10. SQL Server2008窗口计算

    (一) 窗口的定义:指为用户指定的一组行,也称着"分区".如下图所示的窗口分区.每一个班级看作是一个数据窗口,一共有三个窗口 (二)窗口计算的相关方法 1)over()用法  格式 ...