类装载器DexClassLoader

什么是ClassLoader

在Java环境中,有个概念叫做“类装载器”(ClassLoader),其作用是动态装载Class文件。标准的 Java SDK中有一个ClassLoader类,借助它可以装载想要的Class文件,每个ClassLoader对象在初始化 时必须指定Class文件的路径

import

我们需要某个类时,只需要使 用import关键字包含该类就可以了
import中所引用的类文件 有两个特点:

  • 必须存在于本地,当程序运行时需要该类时,内部类装载器会自动装载该类,这对程序员来讲 是透明的,即程序员感知不到该过程。
  • 编译时必须在现场,否则编译过程会因找不到引用文件而不能正常编译。

即ClassLoader作用: 动态加载java类,可以用来从.jar和.apk文件中加载class。可以用来加载执行没用和应用程序一起安装的那部分代码。构造函数:DexClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent)

每个ClassLoader必须有一个父ClassLoader,在装载Class文件时,子ClassLoader会先请求其父 ClassLoader加载该Class文件,只有当其父ClassLoadee找不到该Class文件时,子ClassLoadee才会继 续装载该类,这是一种安全机制

对于Android的应用程序,本质上虽然也是用Java开发,并且使用标准的Java编译器编译出Class 文件,但最终的APK文件中包含的却是dex类型的文件。dex文件是将所需的所有Class文件重新打包, 打包的规则不是简单地压缩,而是完全对Class文件内部的各种函数表、变量表等进行优化,并产生一 个新的文件,这就是dex文件。由于dex文件是一种经过优化的Class文件,因此要加载这样特殊的Class 文件就需要特殊的类装载器,这就是DexClassLoader,Android SDK中提供了 DexClassLoadee类就是出 于这个目的。

使用

在插件apk中写一个类,并安装

1
2
3
4
5
6
7
8
public class PluginClass {
public PluginClass(){

}
public int add(int a,int b){
return a+b;
}
}

在host中使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//com.mmm为插件apk中声明的action
Intent intent = new Intent("com.mmm", null);
final List<ResolveInfo> plugins = getPackageManager().queryIntentActivities(intent, 0);
ResolveInfo rinfo = plugins.get(0);
ActivityInfo ainfo = rinfo.activityInfo;
String div = System.getProperty("path.separator");
String packageName = ainfo.packageName;
/*
* 指目标类所在的APK或 Jar文件的路径 类装载器将从该路径中寻找指定的目标类,
该路径必须是APK或 Jar的全路径,比如/data/app/com.haiii.android.plugin.apk。如果要包含多个
路径,路径之间必须使用特定分隔符进行分隔,这个特定分隔符可使用System.getProperty
( “path.separtor” )获得
*/
String dexPath = ainfo.applicationInfo.sourceDir;
/*
* 由 于 dex文件被包含在APK或 者 Jar文件中,因此在装载目标类之前需要先从
APK或 Jar文件中解压出dex文件,该参数就是指定解压出的dex文件存放路径。在 Android系
统中,一个应用程序一般对应一个Linux用 户 id,应用程序仅对属于自己的数据目录路径有写
的权限,因此,该参数可以使用该程序的数据路径,本例中,该路径具体是
/data/data/com.haiii.android.pluginhost
* */
String dexOutputDir = getApplicationInfo().dataDir;
//指目标类中所使用的C/C++库存放的路径
String libPath = ainfo.applicationInfo.nativeLibraryDir;
DexClassLoader cl = new DexClassLoader(dexPath, dexOutputDir, libPath, this.getClass().getClassLoader());
Class<?> clazz = null;
try {
clazz = cl.loadClass(packageName + ".PluginClass");
Object obj = clazz.newInstance();
Class[] params = new Class[2];
params[0] = Integer.TYPE;
params[1] = Integer.TYPE;
Method action = clazz.getMethod("add", params);
Integer ret = (Integer) action.invoke(obj, 12, 34);
Log.i("Host", "return value is " + ret);
} catch (Exception e) {
e.printStackTrace();
}

使用接口方式

定义Comm接口 两个apk都需要有 插件apk实现该接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

public interface Comm {
int add(int a,int b);
}


public class PluginClass implements Comm {
public PluginClass() {

}

@Override
public int add(int a, int b) {
return a + b;
}
}

try {
clazz = cl.loadClass(packageName + ".PluginClass");
Comm comm = (Comm) clazz.newInstance();
Integer ret = comm.add(10, 15);
Log.i("Host", "return value is " + ret);
} catch (Exception e) {
e.printStackTrace();
}

参数介绍
  • dexPath:被解压的apk路径,不能为空。

  • dexOutputDir:解压后的.dex文件的存储路径,不能为空。这个路径强烈建议使用应用程序的私有路径,不要放到sdcard上,否则代码容易被注入攻击。

  • libraryPath:os库的存放路径,可以为空,若有os库,必须填写

  • parent:父亲加载器,一般为context.getClassLoader(),使用当前上下文的类加载器