你对Android
中的ClassLoader
了解吗?
在回答这个问题之前,我们需要知道Android
中ClassLoader
的类型。通过IDEA
的类的继承结构示意图可以看到。
可以看到有很多类型的ClassLoader
,我们可以尝试着看看,平常我们使用的都是哪些ClassLoader
呢?我们随便运行一个空的项目然后断点看下
从断点中,我们可以知道
- 加载
MainActivity
类的ClassLoader
是PathClassLoader
。 -
PathClassLoader
的父亲是BootClassLoader
PathClassLoader
通过上述的类的继承结构图可以知道,PathClassLoader
属于BaseDexClassLoader
的子类。在Anroid
中,PathClassLoader
通常用来加载已经安装的apk的dex文件(安装的apk的dex文件会存储在/data/dalvik-cache中)
BootClassLoader
Android系统启动时会使用BootClassLoader
来预加载常用类。
DexClassLoader
除了上述介绍的两个类型,还有DexClassLoader
也经常被使用到。因为它可以根据路径加载dex文件以及包含dex的压缩文件(apk和jar文件)。这样就为动态加载提供了可能性。
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), libraryPath, parent);
}
}
我们可以知道DexClassLoader
需要四个参数
- 要加载类文件的路径
- 优化
dex
文件的目录,不能为空 - 包含
C/C++
库的路径集合,多个路径用文件分隔符分隔分割,可以为空 - 父加载器
动态加载代码
上面介绍了很多概念性的东西。接下来要实战一下。动态加载我们SD
中的jar
文件。然后调用方法。首先我们需要一个被加载的jar
文件。先编译一个测试类,这个类很简单,只是返回一串字符串
public class HelloWorld {
public HelloWorld() {
}
public static final String getMessage() {
return "hello world";
}
}
然后找到这个类的字节码
,它所在的目录如图
使用命令行,编译该字节码
为jar
文件(因为是文件夹的关系,中间需要创建一个MANIFEST.MF
文件)然后使用命令将class
文件编译成jar
文件
jar cvf demo.jar HelloWorld.class
当我们得到jar
文件后,将jar
文件放到assest
文件夹中。尝试着用自己的ClassLoader
来加载它。
public class MainActivity extends AppCompatActivity implements PermissionUtils.SimpleCallback {
public static final String FINAL_PATH = SDCardUtils.getSDCardPathByEnvironment() + "/new/demo2.jar";
TextView tvHelloWorld;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tvHelloWorld = findViewById(R.id.tvHelloWorld);
PermissionUtils.permission(PermissionConstants.STORAGE).callback(this).request();
}
@SuppressWarnings("all")
@Override
public void onGranted() {
boolean copyResult = ResourceUtils.copyFileFromAssets("demo2.jar", FINAL_PATH);
if (copyResult) {
File cc = getDir("dex", 0);
DexClassLoader classLoader = new DexClassLoader(FINAL_PATH, cc.getAbsolutePath(), null, getClassLoader());
try {
Class mm = classLoader.loadClass("cc.dd.mm.HelloWorld");
Method method = mm.getMethod("getMessage");
String message = (String) method.invoke(null);
tvHelloWorld.setText(message);
} catch (Exception e) {
System.out.println(e);
}
}
}
@Override
public void onDenied() {
}
}
可以看到,代码的逻辑很简单。就是检查SD卡
的读写权限。然后将事先放在assets
文件夹中的jar
拷贝到SD
卡中。用自己的ClassLoader
来加载jar
文件。之后尝试调用其中的方法。
当你尝试的运行APP
的时候,你会发现有一个这样的错误
No original dex files found for dex location
这是因为我们之前直接将.class
文件转化成.jar
文件。但是Android Dalvik
并不能识别java
二进制代码。所以我们需要将刚刚生成的jar
文件,改成能被Android Dalvik
所识别的jar
文件。这里需要dx
工具,帮我们完成这个任务。(这个工具在:安卓安装目录下\SDK\build-tools)
dx --dex --output=old.jar new.jar
这样你就得到一个新的jar
文件,然后替换之前的jar
。再次运行。就会得到你想要的结果了。