双亲委托
双亲委托机制,就是导入类的时候判断parent是否已经导入过该类。
作用
1、避免重复加载,当父加载器已经加载了该类的时候,没有必要子ClassLoader再加载一次。
2、安全性考虑,防止核心API库被随意篡改。
核心代码
private final ClassLoader parent;
类是如何被加载的
调用loadclass加载
如果使用补丁包中的类
将补丁包插入到dexElement中,并且插入到dexElement列表的最前面。
已经加载过的类还能被替换修复吗
已经加载的类在缓存中了,不会再被加载了。所以Tinker Qzone需要重启生效,防止没有Bug类已经加载了,没法修复了。
如何保证正确的dex文件先被加载
将正确的dex放在dexlist数据
Android N混合编译
ART模式在Android N7.0)之前安装APK时会采用AOT(Ahead of time: 提前编译、静态编译)预编译为机器码。
而在Android N使用混合模式的运行时。应用在安装时不做编译,而是运行时解释字节码,同时在JIT编译了一些代码后将这些代码信息记录至Profile文件,等到设备空闲的时候使用AOT(AI-Of-the-Time compilation:全时段编译)编译生成称为app image的base.art(类对象映像)文件,这个art文件会在apk启动时自动加载(相当于缓存)。根据类加载原理,类被加载了无法被替换,即无法修复。
热修复流程
1、获取当前应用的PathClassloader;
2、反射获取到DexPathList属性对象pathList;
3、反射修改pathList的dexElements
a)将补丁包patch.dex转化为Element[](patch)
b)获得pathList的dexElements属性(old)
c)patch+old合并,并反射赋值给pathList的dexElements
反射基础
类加载
Class.forName()
加载 -> 链接 -> 初始化
Class.forName(A)会执行A中的静态代码块。
classLoader.loadClass()
classLoader.loadClass(A)不会执行A中的静态代码块。
判断程序的classLoader
public class MainActivity extends Activity {
private static final String TAG = "JJWorld.MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
try {
ClassLoader cls4 = Activity.class.getClassLoader();
Log.i(TAG, "" + cls4.getClass().getName());
ClassLoader cls1 = Dog.class.getClassLoader();
Log.i(TAG, "" + cls1.getClass().getName());
ClassLoader cls2 = AppCompatActivity.class.getClassLoader();
Log.i(TAG, "" + cls2.getClass().getName());
ClassLoader cls3 = MainActivity.class.getClassLoader();
Log.i(TAG, "" + cls3.getClass().getName());
} catch (Exception e) {
Log.i(TAG, "ClassLoader not found");
}
try {
Class<?> aClass = Class.forName("android.support.v4.app.SupportActivity");
Log.i(TAG, "aClass not found");
} catch (ClassNotFoundException e) {
Log.i(TAG, "class not found");
e.printStackTrace();
}
}
}
输出
2023-06-19 20:26:21.520 3061-3061/cn.jj.myapplication I/JJWorld.MainActivity: class not foundjava.lang.BootClassLoader
2023-06-19 20:26:21.520 3061-3061/cn.jj.myapplication I/JJWorld.MainActivity: dalvik.system.PathClassLoader
2023-06-19 20:26:21.520 3061-3061/cn.jj.myapplication I/JJWorld.MainActivity: dalvik.system.PathClassLoader
2023-06-19 20:26:21.520 3061-3061/cn.jj.myapplication I/JJWorld.MainActivity: dalvik.system.PathClassLoader
2023-06-19 20:26:21.520 3061-3061/cn.jj.myapplication I/JJWorld.MainActivity: class not found
PathClassLoader
BaseDexClassLoader
在这个BaseDexClassLoader中没有看到私有属性pathList
android api33版本 BaseDexClassLoader的findClass方法现在直接抛出异常ClassNotFoundException,还有pathList属性吗
在Android API级别 33及更高版本中,BaseDexClassLoader的findClass方法已经更新,它不再直接抛出异常ClassNotFoundException,而是返回一个null值来表示找不到指定的类。
至于pathList属性,它仍然存在。pathList是BaseDexClassLoader的一个私有属性,它是一个DexPathList对象,用于保存加载的Dex文件路径和相关信息。这个属性用于在类加载过程中查找和加载类。
pathList属性仍然存在但是为什么看不到
在Android API级别 33及更高版本中,pathList属性被隐藏了,并且无法直接访问。这是因为在Android的更新版本中,Google对一些内部实现进行了修改和优化,并且将一些属性设置为私有或隐藏,以增强安全性和稳定性。
虽然无法直接访问pathList属性,但您仍然可以通过反射来获取和操作它。通过使用反射,您可以获取BaseDexClassLoader对象的pathList属性,并使用它来查找和加载类。
ClassLoader
DexPathList
optimizedDirectory是一个文件目录,将dex转化为odex之后,odex文件的目录。
TK
热修复工具类
public class Hotfix {
private static final String TAG = "JJWorld.Hotfix";
/**
* 1、获取当前应用的PathClassloader;
* 2、反射获取到DexPathList属性对象pathList;
* 3、反射修改pathList的dexElements
* a)将补丁包patch.dex转化为Element[](patch)
* b)获得pathList的dexElements属性(old)
* c)patch+old合并,并反射赋值给pathList的dexElements
*/
public static void installPatch(Application application, File patchFile){
if (!patchFile.exists()){
return;
}
// 1、获取当前应用的PathClassloader;
ClassLoader classLoader = application.getClassLoader();
// 2、反射获取到DexPathList属性对象pathList;
Field pathListField = findField(classLoader, "pathList");
try {
Object pathList = pathListField.get(classLoader);
// 3-1、将补丁包转为dexElement[]数组 将补丁包patch.dex转化为Element[](patch)
List<File> files = new ArrayList<>();
File dexOutputDir = application.getCodeCacheDir();
ArrayList<IOException> suppressedExceptions = new ArrayList<>();
Method makePathElementsMethod = findMethod(pathList, "makePathElements",List.class,File.class,List.class);
// 反射执行 静态方法可以传入实例对象或者null
Object[] patchElements = (Object[])makePathElementsMethod.invoke(pathList, files, dexOutputDir, suppressedExceptions);
// 3-2、获得pathList的dexElements属性(old)
Field dexElementsField = findField(pathList, "dexElements");
Object[] dexElements = (Object[])dexElementsField.get(pathList);
// 3-3 创建一个新数组装数组中的所有元素
String s = "dalvik.system.DexPathList$Element";
Object[] newElements = (Object[])Array.newInstance(dexElements.getClass().getComponentType(), patchElements.length + dexElements.length);
System.arraycopy(patchElements,0,newElements,0,patchElements.length);
System.arraycopy(patchElements,0,newElements,patchElements.length,patchElements.length);
dexElementsField.set(pathList,newElements);
} catch (Exception e) {
Log.i(TAG,"error getting pathList:" + e.getMessage());
e.printStackTrace();
}
}
}
反射工具类
public class ReflectionUtils {
private static final String TAG = "JJWorld.ReflectionUtils";
/**
* 反射获取对象中的属性
* @param instance
* @param name
* @return
*/
public static Field findField(Object instance,String name){
Class<?> cls = instance.getClass();
while (cls != null){
try {
Field field = cls.getDeclaredField(name);
if (field != null){
field.setAccessible(true);
return field;
}
} catch (Exception e) {
Log.i(TAG, cls.getSimpleName() + " not find field " + name + "----" + e.getMessage());
}
cls = cls.getSuperclass();
}
return null;
}
/**
* 反射获取对象中的方法
* @param instance
* @param name
* @return
*/
public static Method findMethod(Object instance, String name,Class<?>... parameterTypes){
Class<?> cls = instance.getClass();
while (cls != null){
try {
Method method = cls.getDeclaredMethod(name,parameterTypes);
if (method != null){
method.setAccessible(true);
return method;
}
} catch (Exception e) {
Log.i(TAG, cls.getSimpleName() + " not find field " + name + "----" + e.getMessage());
}
cls = cls.getSuperclass();
}
return null;
}
}