【Android】类加载器&热修复-随记

1. 背景

在「Android插件化开发指南------类加载器」一文中曾提到,在Android中的类加载示意图为:

图中可知,加载外部jar、dex、apk都需要构建一个DexClassLoader的实例,并将对应的jar、dex、apk文件塞入其中,以构建出一个可用的类加载器实例。具体示例在「Android插件化开发指南------Hook技术(一)【长文】」有给出,实现方式一般都是【将外部dex加载到宿主app的dexElements中】。

jar、dex、apk在使用的时候,也就是有两个思路:

  1. 创建多个ClassLoader来加载方案。需要在某个地方持有,毕竟类加载器是相互独立的,不能期望在APK的类加载中加载到独属于某个外部jar的class文件;
  2. 将外部jar/dex/apk文件通过反射加载到宿主APK的dexElements中。此时使用可当做正常APK调用来使用,可在application project构建中自定义provided aar这个配置,完成APK构建的compileOnly的效果,在外部jar/dex/apk被塞入后,就能走到正常流程。但由于Android系统版本的变化性,塞入源码文件到dexElements中这里需要做一些额外的适配工作。可参考:https://github.com/Tencent/tinker - SystemClassLoaderAdder.java中的实现。

上述其实各有优缺点,那么对于热修复场景,我们生成的path类和原类重名,或者新增了类,此时我们应该如何做?找到一篇介绍文章:Android热修复技术原理(最新最全)。大致分为三类:

  • 基于Xposed思路的函数指针思路。
  • 塞入dexElements的前面,在前面的先加载思路。
  • Instant Run构建时预插桩思路。

函数指针思路笔者没什么了解。这里探讨下Instant Run下的类加载: 对这个原理感兴趣的可以阅读美团发布的博客:

关键原理:

也就是说这里其实是为外部jar构造了一个DexClassLoader,但实际上仅仅用来new出来ChangeQuickRedirect,并将这个new出来的对象塞入到了APK的类加载器加载的类的实例对象属性changeQuickRedirect中。那这为什么能生效?

可能会有疑惑,那就是类加载器不是相互独立的吗,怎么能将实例化的对象给设置到APK加载的实例对象中。

类加载器是独立加载,但是可互操作的。虽然 JAR 中的类是通过 DexClassLoader 加载的,并且APK的类和JAR的类是独立的命名空间(因为它们各自使用的类加载器),但是您可以通过反射将 JAR 中的类的实例传递给 APK 中的类。此操作不会受到类加载器的限制,因为实际上是通过引用将对象设置到了 APK 中的某个字段。这一步并没有将 JAR 中的类 "直接" 交给 APK 的类加载器,而是将它的一个实例放入了 APK 的字段中。

因此即使它是通过不同的类加载器加载的,只要能在 APK 的上下文中持有这个实例,就可以正常调用它的方法。

2. 实验

实践下上述描述。可参考:https://github.com/Meituan - PatchExecutor.java

先构造了一个DexClassLoader:

然后获取相关class,进行new实例对象和塞入:

类似的,简化下代码逻辑,模拟下也就是:

java 复制代码
// 属性
private ChangeQuickRedirect changeQuickRedirect;

// 点击事件
start_activity_by_nav.setOnClickListener(new View.OnClickListener() {
   @Override
    public void onClick(View v) {
        File jarPath = new File(getFilesDir(), "testClassLoader/a.jar");
        File dexOptDir = new File(getCacheDir(), "testClassLoader/opt");
        File nativeLibraryDir = new File(getFilesDir().getParentFile(), "lib");
        DexClassLoader patchClassLoader = new DexClassLoader(jarPath.getPath(), dexOptDir.getPath(),
            nativeLibraryDir.getPath(), getClass().getClassLoader());

        try {
            Class<?> aClass = Class.forName("com.aaa.TestClass", true, patchClassLoader);
            Object o = aClass.newInstance();
            Field iPatchClassProvider = OtherActivity.class.getDeclaredField("changeQuickRedirect");
            iPatchClassProvider.setAccessible(true);
            iPatchClassProvider.set(OtherActivity.this, o);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
});

start_activity_testbtn.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        String strings = changeQuickRedirect.patchedTest();
        Toast.makeText(OtherActivity.this, strings, Toast.LENGTH_SHORT).show();
    }
});

的确很丝滑。那么要想这个框架顺利运行起来,那就需要处理编译时动态插桩、patch包生成,并设计一套判断调用流程,以及Robust文章中所提到的R8、混淆、super等问题。后续继续学习:

  • patch如何生成
  • 如何插桩
相关推荐
三少爷的鞋1 小时前
Android 架构七条铁律:架构师真正设计的不是代码,而是秩序
android
xiangxiongfly9153 小时前
Android GsonFactory总结
android·gson·gsonfactory
WythePlus5 小时前
Android 14 SELinux策略深度解析
android·安全策略·selinux策略
俩个逗号。。8 小时前
Kotlin flow 详解
android·开发语言·kotlin
橙光成长10 小时前
android-ad-cleaner:android免 Root 批量关闭系统开屏与营销推送
android·1024程序员节
深圳雨林凯AI10 小时前
图案裂变的底色衰减:alpha通道、亮度对比与白墨分色的量化拆解
android
mmsx10 小时前
Android 模块化收口:能力组合框架、手写 LifecycleOwner 与热修复共存
android
恋猫de小郭13 小时前
Flutter 可以更直接地调用原生 API 了,Dart Direct Native Interop
android·前端·flutter
2501_9160088913 小时前
iPhone 设备信息查看,用 KeyMob 看型号、序列号、电池健康和 UDID
android·ios·小程序·https·uni-app·iphone·webview
好的佩奇1 天前
蓝牙之通信架构
android·flutter·蓝牙