全面了解类加载器的分类、作用以及源码分析,了解双亲委派机制,动态加载第三方dex的函数。
类加载器的分类及作用
1、类加载器的种类
JVM的类加载器包括3种:
-
Bootstrap ClassLoader(引导类加载器)
C/C++代码实现的加载器,用于加载指定的JDK的核心类库,比如java.lang、java.uti等这些系统类。
Java虚拟机的启动就是通过Bootstrap,该Classloader在java里无法获取,用于加载lib下的类。
-
Extensions ClassLoader(拓展类加载器)
Java中的实现类为ExtClassLoader,提供了除了系统类之外的额外功能,可以在Java里获取,负责加载/lib/ext下的类。
-
Application ClassLoader(应用程序类加载器)
Java中的实现类为AppClassLoader,是与我们接触最多的类加载器,开发人员写的默认代码就是由它来加载,ClassLoader.getSystemClassLoader返回的就是它。
也可以自定义类加载器,只需要通过集成java.lang.ClassLoader类的方式
2、加载顺序
BootstrapClassLoader => ExtensionsClassLoader => ApplicationClassLoader
3、双亲委派
简而言之,就是一个类加载器收到了加载请求,会优先给父加载器进行加载,层层向上,如果直到最后都没能找到,那么子加载器会自己进行加载。
通俗地来说,就是儿子不愿意干活,而让父亲做,父亲也遵守次规则,直到找到加载过的才会停止,否则就层层下发,让子加载器进行加载。
这样做有什么好处?
- 避免重复加载
- 更加安全,无法自定义类来替代系统类,防止核心API库被随意篡改。
4、类加载的时机
- 隐士加载
- 显示加载
5、类加载的过程
- 装载:查找和导入Class文件
- 链接:其中解析步骤是可以选择的
- 检查:检查载入的class文件数据的正确性
- 准备:给类的警惕变量分配存储空间
- 解析:将符号引用转成直接引用
- 初始化:即调用< clinit>函数,对静态变量,静态代码块执行初始化工作
6、类加载器的继承关系以及作用
- ClassLoader为抽象类;
- BootClassLoader预加载常用类,单例模式。与Java的不同,它是由Java实现的。
- BaseDexClassLoader是PathClassLoader、DexClassLoader、InmemoryDexClassLoader的父类,类加载的主要逻辑都是BaseDexClassLoader实现的。
- SecureClassLoader继承了抽象类ClassLoader,拓展了ClassLoader类加入了权限方面的功能,加强了安全性,其子类URLClassLoader是用URL路径从jar文件中加载类
其中重点关注PathClassLoader和DexClassLoader。
PathClassLoader是Android默认使用的类加载器,一个APK的Activity等类更是在其加载。
DexClassLoader可以加载任意目录下的dex/jar/apk/zip文件,比PathClassLoader更加灵活,是实现热更新、热修复、dex加壳的重点。
在线Android源码网站:
国内:aospxref.com
国外:androidxref.com
验证类加载器顺序以及DexClassLoader加载类使用
验证类加载器顺序
源码里的parent字段很重要,这里使用一个实例来测试。
使用AndroidStudio新建一个test02项目,使用empty Activity。
package com.example.test02;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
testClassLoader();
}
/*
I/miniboom: thisClassloader:dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.example.test02-ftL2Lx2X30e9IJk52y3q4w==/base.apk"],nativeLibraryDirectories=[/data/app/com.example.test02-ftL2Lx2X30e9IJk52y3q4w==/lib/arm64, /system/lib64, /vendor/lib64]]]
this:dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.example.test02-ftL2Lx2X30e9IJk52y3q4w==/base.apk"],nativeLibraryDirectories=[/data/app/com.example.test02-ftL2Lx2X30e9IJk52y3q4w==/lib/arm64, /system/lib64, /vendor/lib64]]]***java.lang.BootClassLoader@b266b1f
this:java.lang.BootClassLoader@b266b1f***java.lang.BootClassLoader@b266b1f
root:java.lang.BootClassLoader@b266b1f
* */
public void testClassLoader(){
ClassLoader thisClassloader = MainActivity.class.getClassLoader();
Log.i("miniboom", "thisClassloader:"+thisClassloader);
ClassLoader tmpClassloader = null;
//parent双亲委派中非常重要的一个变量
ClassLoader parentClassloader = thisClassloader.getParent();
while (parentClassloader != null){
Log.i("miniboom","this:"+thisClassloader+"***"+parentClassloader);
tmpClassloader = thisClassloader.getParent();
thisClassloader = parentClassloader;
parentClassloader = tmpClassloader;
}
Log.i("miniboom", "root:"+thisClassloader);
}
}
填入以下代码,运行,结果我写在注释里了,可以很清楚验证类加载的关系,root加载器是
java.lang.BootClassLoader
使用DexClassLoader类加载器加载第三方DEX函数
首先新建一个empty Activity项目,新建一个类。build => 对生成的APK进行提取
增加读取sd卡的读写权限。
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"></uses-permission>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
用windows或者ubuntu,ubuntu直接将apk提取到此处,windows把.apk改成.zip然后解压即可。
push到真机的/sdcard/3.dex
adb push ./classes.dex /sdcard/3.dex
搞定之后,运行APP,要在权限管理,对这个APP进行授权读写权限,才可以。
在新建一个invoketest项目,用于读取该3.dex里的TestClass类里的函数。
package com.example.invoketest;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Context;
import android.os.Bundle;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import dalvik.system.DexClassLoader;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Context appContext = this.getApplicationContext();
testDexClassloader(appContext, "/sdcard/3.dex");
}
//com.example.test02.TestClass.testFunc
public void testDexClassloader(Context context, String dexfilepath){
//存放优化的dex文件
File optfile = context.getDir("opt_dex",0);
//存放so文件
File libfile = context.getDir("lib_path",0);
DexClassLoader dexClassLoader = new DexClassLoader(dexfilepath, optfile.getAbsolutePath(),libfile.getAbsolutePath(),
MainActivity.class.getClassLoader());
Class<?> clazz = null;
try {
//alt + enter解决异常代码问题
clazz = dexClassLoader.loadClass("com.example.test02.TestClass");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
if (clazz != null){
try{
Method testFuncMethod = clazz.getDeclaredMethod("testFunc");
//因为不是静态方法,先创建实例。
Object obj = null;
try {
obj = clazz.newInstance();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
try {
testFuncMethod.invoke(obj);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
}
运行即可看到testFunc打印的一段话。