首先如果仅仅是因为64K method的问题可以直接看这里DexGuard、Proguard、Multi-dex给出的解决方案。
本文主要讨论从编译层面,dex动态加载器选择层面以及安全层面讨论dex动态加载
I. 类加载器
比较两个类是否相等: 前提是采用的是同样的加载器加载的,否则必不相等。
一般加载器类别
虚拟机的角度
1. 启动类加载器(Bootstrap ClassLoader)
使用C++语言实现,虚拟机自身的一部分。
2. 其他的类加载器
使用Java语言实现,独立于JVM外部,全部继承自类java.lang.ClassLoader
。
开发人员角度
1. 启动类加载器(Bootstrap ClassLoader)
层级: native层实现。
负责加载:
JAVA_HOME\lib
目录中能被JVM识别的类库加载到JVM内存中(名称不符合的类库不会被加载)(java的核心类,如java.lang
、java.util
等,是java运行时环境所需类的一部分, 如果是Android,还会加载Android sdk层,如TextUtils
,TextView
等)。使用关系: 无法被Java程序直接引用。
2. 扩展类加载器(Extersion ClassLoader)
负责加载:
JAVA_HOME\lib\ext
目录中的类库(继承自java核心类的类)。使用关系: 可以被开发者直接使用。
3. 应用程序类加载器(Application ClassLoader)
系统类加载器。
负责加载: 用户类路径(Classpath)上所指定的类库。Android中大多数的应用中的类(从odex中加载出来的类: 如 MainActivity、自定义View、XXXApplication)。
使用关系: 可以被开发者直接使用。
备注: 一般是应用程序默认的类加载器。
特性
什么是双亲委派模型?
一个类收到了类加载请求,会将请求先委派给父类加载,每层皆如此,因此所有的类加载是从上而下的,只有上层无法加载了才到下层加载。
参考ClassLoader
中给出的解释:
1 2 3 4 | 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. |
为什么要遵循双亲委派模型?
为了保证所加载的类的唯一性,保证相同的类只会被一个加载器所加载。也保证了一个类不会被多个加载器加载到JVM中,导致同一个类在JVM被不同的加载器加载多次。
Dalvik虚拟机的类加载器与其他Java虚拟机的不同?
一般的Java虚拟机,是自定义继承自ClassLoader
的类加载器,然后通过defineClass
方法从二进制流中加载Class,或者从Class文件中读取。而Dalvik虚拟机是阉割以及修改过的,无法从二进制流中加载,Dalvik只识别dex文件,因此我们能加载的只是dex文件或包含dex文件的.jar
或.apk
。
其他梗
当java源码被编译为binary class时,编译器会插入一个静态常量
final static java.lang.Class class
,因为任意类我们都可以通过.class
获得当前类的java.lang.Class
。java中的静态对象是attach到对应的class上面的,也就是attach在该class的
ClassLoader
上面的,当ClassLoader
被unload的时候,如果再次访问该静态对象,就是一个全新的静态对象在一个新的ClassLoader
上了。可以通过
Class.getClassLoader()
来获得该类的ClassLoader
。
II. Android 动态加载Dex的方式
DexFile
Android中的这几种类加载器实际是依赖DexFile
的,对于DexFile
有以下两点:
打开的DEX文件不会直接存储在
DexFile
对象中,而是存储在对于虚拟机只读的memory-mapped上。我们无法直接调用
DexFile.loadClass
进行对dex的加载,只能通过ClassLoader进行加载。
案例参考:
PathClassLoader
的使用案例推荐参考: secondary-dex-gradle/…/secondarydex/plugin/DexClassLoader
的使用案例推荐参考: Custom Class Loading in Dalvik,如果你有网络下载dex动态打补丁的需求的话。
III. 编译层面实现打指定独立dex
Ant
可以参考这里后面的Build Process
。
Gradle
在编译层面将指定的module拆分出来打包成dex放入assets中,完全可以参考这个方案: secondary-dex-gradle/app/build.gradle
如果不理解的可以看我fork的,我添加了中文注解: Jacksgong/secondary-dex-gradle/app/build.gradle
IV. 安全性讨论
动态加载Dex的安全性主要存在两方面:
存储dex的文件暴露在其他应用可读写的目录下。
加载外部dex的时候没有做好完整的安全性校验。
解决方案
尽量将dex放到当前应用的私有目录下,保证只有当前应用uid可以读甚至写(一般就只有
Context.getFileDir()
/Context.getDir(String, MODE_PRIVATE)
/Context.getCacheDir()
),这方面目录相关知识可以参看: Android中尽量不用Storage Permission对从服务端下载或者外部加载的dex,做校验(对文件进行哈希值校验等)。
将dex文件加密,通过JNI将解密代码写在Native层,解密之后通过
defineClass
指定路径加载完成后,删除解密后文件。
共同学习,写下你的评论
评论加载中...
作者其他优质文章