Android classLoader 双亲委托 反射 类加载 Class.forName classLoader.loadClass

82 篇文章 1 订阅

双亲委托

双亲委托机制,就是导入类的时候判断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;
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

学知识拯救世界

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值