Android 动态加载dex

jopen 8年前

 

首先如果仅仅是因为64K method的问题可以直接看这里 DexGuard、Proguard、Multi-dex 给出的解决方案

本文主要讨论从编译层面,dex动态加载器选择层面以及安全层面讨论dex动态加载

前言

比较两个类是否相等: 是基于采用同样的加载器加载的,否则必不相等。

一般加载器类别

虚拟机的角度

1. 启动类加载器(Bootstrap ClassLoader)

使用C++语言实现,虚拟机自身的一部分

2. 其他的类加载器

使用Java语言实现,独立于JVM外部,全部继承自类java.lang.ClassLoader

开发人员角度

以下三种都是系统提供的供开发人员使用的加载器

1. 启动类加载器(Bootstrap ClassLoader)

负责加载: JAVA_HOME\lib目录中 能被JVM是被的类库到JVM内存中(名称不符合的类库不会被加载)

无法被Java程序直接引用。

2. 扩展类加载器(Extersion ClassLoader)

负责加载: JAVA_HOME\lib\ext目录中的类库

可以被开发者直接使用

3. 应用程序类加载器(Application ClassLoader)

也称为 系统类加载器

负责加载: 用户类路径(Classpath)上所指定的类库

可以被开发者直接使用

一般是应用程序默认的类加载器

什么是双亲委派模型?

一个类收到了类加载请求,会将请求先委派给父类加载,每层皆如此,因此所有的类加载是从上而下的,只有上层无法加载了才到下层加载。

也可以参考ClassLoader中给出的解释:

Loads the class with the specified name, optionally linking it after loading. The following steps are performed:  1. Call findLoadedClass(java.lang.String) to determine if the requested class has already been loaded.  2. If the class has not yet been loaded: Invoke this method on the parent class loader.  3. If the class has still not been loaded: Call findClass(java.lang.String) to find the class.

为什么要遵循双亲委派模型?

为了保证所加载的类的唯一性,保证相同的类只会被一个加载器所加载。

Dalvik虚拟机的类加载器与其他Java虚拟机的不同?

一般的Java虚拟机,是自定义继承自ClassLoader的类加载器,然后通过defineClass方法从二进制流中加载Class,或者从Class文件中读取。而Dalvik虚拟机是阉割以及修改过的,无法从二进制流中加载,Dalvik只识别dex文件,因此我们能加载的只是dex文件或包含dex文件的.jar或.apk。

I. Android 动态加载Dex的方式

Android 动态加载dex

DexFile

Android中的这几种类加载器实际是依赖DexFile的,对于DexFile有以下两点

  1. 打开的DEX文件不会直接存储在DexFile对象中,而是存储在对于虚拟机只读的memory-mapped上。
  2. 我们无法直接调用DexFile.loadClass进行对dex的加载,只能通过ClassLoader进行加载

PathClassLoader的使用案例推荐参考: secondary-dex-gradle/…/secondarydex/plugin/

DexClassLoader的使用案例推荐参考: Custom Class Loading in Dalvik ,如果你有网络下载dex动态打补丁的需求的话

II. 何在编译层面实现打指定独立dex

Ant

可以参考 这里 后面的Build Process.

Gradle

在编译层面将指定的module拆分出来打包成dex放入assets中,完全可以参考这个方案:

secondary-dex-gradle/app/build.gradle

如果不理解的可以看我fork的,我添加了中文注解: Jacksgong/secondary-dex-gradle/app/build.gradle

III. 安全性讨论

动态加载Dex的安全性主要存在两方面

  1. 存储dex的文件暴露在其他应用可读写的目录下
  2. 加载外部dex的时候没有做好完整的安全性校验

解决方案

  1. 尽量将dex放到当前应用的私有目录下,保证只有当前应用uid可以写甚至读(一般就只有Context.getFileDir()/Context.getDir(String, MODE_PRIVATE)/Context.getCacheDir()),这方面目录相关知识可以参看 Android中尽量不用Storage Permission
  2. 对从服务端下载或者外部加载的dex,做校验(对文件进行哈希值校验等)
  3. 将dex文件加密,通过JNI将解密代码写在Native层,解密之后通过defineClass指定路径加载完成后,删除解密后文件