Android中的类装载器DexClassLoader

本文详细介绍了DexClassLoader的概念、作用以及在Android应用程序中的具体使用方法,包括如何创建实例、加载类和使用反射调用插件类的方法。通过实际代码示例,展示了如何访问另一个apk中的代码,并讨论了使用DexClassLoader实现插件化框架的可能性。
类装载器DexClassLoader的介绍
   
  在java中,有个概念叫做“类加载器”(ClassLoader),它的作用就是动态的装载Class文件。标准的java sdk中有一个
ClassLoader类,借助这个类可以装载想要的Class文件,每个ClassLoader对象在初始化时必须制定Class文件的路径。
    可能有人会问,在写程序的时候不是有import关键字可以引用制定的类吗?为何还要使用这个类加载器呢?
    原因其实是这样的,使用import关键字引用的类必须符合以下两个条件
  •    类文件必须在本地,当程序运行时需要次类时,这时类装载器会自动装载该类,程序员不需要关注此过程。
  •    编译的时候必须有这个类文件,否则编译不通过。
    如果想让程序在运行的时候动态调用怎么办呢?用import显示是不符合上面的两种要求的。此时ClassLoader就派上用场了。
    关于java的Classloader的装载机制请参考此链接 http://www.iteye.com/topic/83978,最好到网上自行搜索一下。
    http://www.artima.com/insidejvm/ed2/《Inside the Java Virtural Machine》
    对于android应用程序,本质上使用的是java开发,使用标准的java编译器编译出Class文件,和普通的java开发不同的
    地方是把class文件再重新打包成dex类型的文件,这种重新打包会对Class文件内部的各种函数表、变量表等进行优化,
    最终产生了dex文件。dex文件是一种经过android打包工具优化后的Class文件,因此加载这样特殊的Class文件就需要特殊的类装载器,
    所以android中提供了DexClassLoader类。
类装载器DexClassLoader类结构
继承关系:
    

A class loader that loads classes from .jar and .apk files containing a classes.dex entry. This can be used to execute code not installed as part of an application.

This class loader requires an application-private, writable directory to cache optimized classes. Use Context.getDir(String, int) to create such a directory:

   File dexOutputDir = context.getDir("dex", 0);
 

Do not cache optimized classes on external storage. External storage does not provide access controls necessary to protect your application from code injection attacks.

翻译:这个类加载器用来从.jar和.apk类型的文件内部加载classes.dex文件。通过这种方式可以用来执行非安装的程序代码,作为程序的一部分进行运行。
    这个装载类需要一个程序私有的,可写的文件目录去存放优化后的classes文件。通过Contexct.getDir(String, int)来创建

这个目录:

     File dexOutputDir = context.getDir("dex", 0);

不要把优化优化后的classes文件存放到外部存储设备上,防代码注入攻击。


这个类只有一个构造函数:
public DexClassLoader (String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent)
Added in  API level 3

Creates a DexClassLoader that finds interpreted and native code. Interpreted classes are found in a set of DEX files contained in Jar or APK files.

创建一个DexClassLoader用来找出指定的类和本地代码(c/c++代码)。用来解释执行在DEX文件中的class文件。

路径的分隔符使用通过System的属性 path.separator 获得 :.

String separeater = System.getProperty("path.separtor");

Parameters
dexPath 需要装载的APK或者Jar文件的路径。包含多个路径用File.pathSeparator间隔开,在Android上默认是 ":" 
optimizedDirectory 优化后的dex文件存放目录,不能为null
libraryPath 目标类中使用的C/C++库的列表,每个目录用File.pathSeparator间隔开; 可以为 null
parent 该类装载器的父装载器,一般用当前执行类的装载器
类装载器DexClassLoader的具体使用

这个类的使用过程基本是这样:
  • 通过PacageMangager获得指定的apk的安装的目录,dex的解压缩目录,c/c++库的目录
  • 创建一个 DexClassLoader实例
  • 加载指定的类返回一个Class
  • 然后使用反射调用这个Class
下面是代码部分,代码参考自《Android内核剖析》(作者柯元旦,这本书不错,推荐阅读):
[html]  view plain copy
  1. @SuppressLint("NewApi") private void useDexClassLoader(){  
  2.     //创建一个意图,用来找到指定的apk  
  3.     Intent intent = new Intent("com.suchangli.android.plugin", null);  
  4.     //获得包管理器  
  5.     PackageManager pm = getPackageManager();  
  6.     List<ResolveInfo> resolveinfoes =  pm.queryIntentActivities(intent, 0);  
  7.     //获得指定的activity的信息  
  8.     ActivityInfo actInfo = resolveinfoes.get(0).activityInfo;  
  9.       
  10.     //获得包名  
  11.     String pacageName = actInfo.packageName;  
  12.     //获得apk的目录或者jar的目录  
  13.     String apkPath = actInfo.applicationInfo.sourceDir;  
  14.     //dex解压后的目录,注意,这个用宿主程序的目录,android中只允许程序读取写自己  
  15.     //目录下的文件  
  16.     String dexOutputDir = getApplicationInfo().dataDir;  
  17.       
  18.     //native代码的目录  
  19.     String libPath = actInfo.applicationInfo.nativeLibraryDir;  
  20.     //创建类加载器,把dex加载到虚拟机中  
  21.     DexClassLoader calssLoader = new DexClassLoader(apkPath, dexOutputDir, libPath,  
  22.             this.getClass().getClassLoader());  
  23.       
  24.     //利用反射调用插件包内的类的方法  
  25.       
  26.     try {  
  27.         Class<?> clazz = calssLoader.loadClass(pacageName+".Plugin1");  
  28.           
  29.         Object obj = clazz.newInstance();  
  30.         Class[] param = new Class[2];  
  31.         param[0] = Integer.TYPE;  
  32.         param[1] = Integer.TYPE;  
  33.           
  34.         Method method = clazz.getMethod("function1", param);  
  35.           
  36.         Integer ret = (Integer)method.invoke(obj, 1,12);  
  37.           
  38.         Log.i("Host", "return result is " + ret);  
  39.           
  40.     } catch (ClassNotFoundException e) {  
  41.         e.printStackTrace();  
  42.     } catch (InstantiationException e) {  
  43.         e.printStackTrace();  
  44.     } catch (IllegalAccessException e) {  
  45.         e.printStackTrace();  
  46.     } catch (NoSuchMethodException e) {  
  47.         e.printStackTrace();  
  48.     } catch (IllegalArgumentException e) {  
  49.         e.printStackTrace();  
  50.     } catch (InvocationTargetException e) {  
  51.         e.printStackTrace();  
  52.     }  
  53.    }  
Plugin1.apk中的一个类:
[html]  view plain copy
  1. package com.suchangli.plugin1;  
  2.   
  3. public class Plugin1 {  
  4.     public int function1(int a, int b){  
  5.           
  6.         return a+b;  
  7.     }  
  8. }  


Log输出的结果:
注意要在Plugin1中的清单文件的一个activity中添加一个如下的action,这个action必须是宿主和插件约定好的。
源代码:
通过以上代码就可以访问另一个apk中的代码了,一个比较麻烦的地方就是必须用反射才能调用方法,
有没不用反射也可以调用方法方式呢?当然有,通过接口。

请参见下一篇博客:基于类装载器设计“插件”框架


### 热修复机制的基本原理 热修复是一种在不重新发布应用的情况下,通过动态加载修复代码来解决线上 bug 的技术。其核心思想是将修复后的类文件打包成 `.dex` 文件,并通过 `DexClassLoader` 将其插入到系统默认的类加载器中,从而覆盖原有的错误类实现 bug 修复[^4]。 Android 中的类加载机制基于 `ClassLoader`,其中 `PathClassLoader` 和 `DexClassLoader` 是两个关键的子类。`PathClassLoader` 只能加载 APK 文件中的 `.dex` 文件,而 `DexClassLoader` 则可以加载任意路径下的 `.dex`、`.zip`、`.apk` 或 `.jar` 文件,因此更适合用于热修复场景[^2]。 ### 实现步骤与关键技术点 1. **生成修复 dex 文件** 在修复完成后,需将修改后的 Java 类编译为 `.class` 文件,并使用 Android SDK 提供的 `dx.bat` 工具将其转换为 `.dex` 文件。该文件随后被上传至服务器或本地存储路径,以便应用下载并加载[^3]。 2. **加载修复 dex 文件** 使用 `DexClassLoader` 加载修复后的 `.dex` 文件,并指定一个私有目录作为优化目录(optimizedDirectory)。这个目录通常为应用内部存储的一个子目录,以确保安全性[^1]。 示例代码如下: ```java DexClassLoader dexClassLoader = new DexClassLoader(dexPath, optimizedDir, null, getClassLoader()); ``` 3. **反射合并 dexElements 数组** Android 的类加载过程依赖于 `BaseDexClassLoader` 中维护的 `DexPathList` 对象,其中包含一个 `Element[] dexElements` 数组,记录了所有可加载的 `.dex` 文件。为了使修复后的 `.dex` 文件优先于原始 `.dex` 被加载,需要通过反射将修复 `.dex` 对应的 `Element` 插入到数组最前面。 具体操作包括: - 获取当前应用的 `PathClassLoader`。 - 获取其内部的 `DexPathList` 和 `dexElements` 数组。 - 使用 `DexClassLoader` 加载修复 `.dex` 后获取新的 `dexElements`。 - 将新旧 `dexElements` 合并,并替换回原 `PathClassLoader` 中的数组。 4. **触发类加载逻辑** 当应用再次尝试加载目标类时,由于修复 `.dex` 被提前插入到 `dexElements` 数组中,系统会优先从修复 `.dex` 中加载类,从而达到热修复的效果[^4]。 ### 注意事项与优化建议 - **ODex 过程的影响** Android 在安装 APK 时会对 `.dex` 文件进行 ODEX 优化,即预编译为设备特定格式。如果修复 `.dex` 文件与原有 `.dex` 存在兼容性问题,可能会导致崩溃。因此,在构建修复包时应确保与原 `.dex` 结构一致,并避免引入新类或方法签名冲突[^5]。 - **安全性和完整性校验** 动态加载外部 `.dex` 文件存在潜在的安全风险,应确保文件来源可信,并对内容进行完整性校验(如 MD5、SHA256 校验)。 - **性能优化** 首次加载 `.dex` 文件时,系统会执行优化操作,可能造成短暂卡顿。建议在后台线程中完成加载,或采用预加载策略提升用户体验[^2]。 - **兼容性处理** 不同 Android 版本对类加载机制的支持不同,特别是在 ART 和 Dalvik 上的表现差异较大。应在实际设备上进行充分测试,并针对不同版本做适配处理。 ---
评论 7
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值