tinker icon indicating copy to clipboard operation
tinker copied to clipboard

【探讨】关于Tinker补丁成功的校验方法: SystemClassLoaderAdder#checkDexInstall

Open WanDa1993 opened this issue 1 year ago • 12 comments

手机型号:小米10、华为等Android11+机型

手机系统版本:Android 12

tinker版本:v1.9.15.1

gradle版本:8.9

WanDa1993 avatar Dec 15 '24 03:12 WanDa1993

问题背景:在某次版本迭代需要使用Tinker热修复一个关于ButterKnife的类型转换的异常

热修复前

@BindView(R.id.root)
lateinit var root: ConstraintLayout

热修复后

@BindView(R.id.root)
lateinit var root: LinearLayout

然后对应布局也进行了一波调整

问题情况:

  • 补丁生成 通过
  • 补丁安装 通过
  • 重启应用时,发现其抛出异常,如下

WanDa1993 avatar Dec 15 '24 03:12 WanDa1993

问题异常:

2024-12-10 19:46:40.601  6457-6457  Tinker.DefaultLoadReporter                         com.example.tinker                   E  patch loadReporter onLoadException: tinker dex check fail:Tinker Exception:checkDexInstall failed
2024-12-10 19:46:40.604  6457-6457  Tinker.DefaultLoadReporter                         com.example.tinker                   I  dex exception disable tinker forever with sp
2024-12-10 19:46:40.605  6457-6457  Tinker.DefaultLoadReporter                         com.example.tinker                   E  tinker load exception, welcome to submit issue to us: https://github.com/Tencent/tinker/issues
2024-12-10 19:46:40.605  6457-6457  Tinker.DefaultLoadReporter                         com.example.tinker                   E  tinker load exception  com.tencent.tinker.loader.TinkerRuntimeException: Tinker Exception:checkDexInstall failed
                                                                                                                               	at com.tencent.tinker.loader.SystemClassLoaderAdder.installDexes(SystemClassLoaderAdder.java:73)
                                                                                                                               	at com.tencent.tinker.loader.TinkerDexLoader.loadTinkerJars(TinkerDexLoader.java:191)
                                                                                                                               	at com.tencent.tinker.loader.TinkerLoader.tryLoadPatchFilesInternal(TinkerLoader.java:356)
                                                                                                                               	at com.tencent.tinker.loader.TinkerLoader.tryLoad(TinkerLoader.java:57)
                                                                                                                               	at java.lang.reflect.Method.invoke(Native Method)
                                                                                                                               	at com.tencent.tinker.loader.app.TinkerApplication.loadTinker(TinkerApplication.java:126)
                                                                                                                               	at com.tencent.tinker.loader.app.TinkerApplication.onBaseContextAttached(TinkerApplication.java:164)
                                                                                                                               	at com.tencent.tinker.loader.app.TinkerApplication.attachBaseContext(TinkerApplication.java:187)
                                                                                                                               	at android.app.Application.attach(Application.java:338)
                                                                                                                               	at android.app.Instrumentation.newApplication(Instrumentation.java:1191)
                                                                                                                               	at android.app.LoadedApk.makeApplication(LoadedApk.java:1546)
                                                                                                                               	at android.app.ActivityThread.handleBindApplication(ActivityThread.java:8574)
                                                                                                                               	at android.app.ActivityThread.access$2800(ActivityThread.java:313)
                                                                                                                               	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2912)
                                                                                                                               	at android.os.Handler.dispatchMessage(Handler.java:117)
                                                                                                                               	at android.os.Looper.loopOnce(Looper.java:205)
                                                                                                                               	at android.os.Looper.loop(Looper.java:293)
                                                                                                                               	at android.app.ActivityThread.loopProcess(ActivityThread.java:9986)
                                                                                                                               	at android.app.ActivityThread.main(ActivityThread.java:9975)
                                                                                                                               	at java.lang.reflect.Method.invoke(Native Method)
                                                                                                                               	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:586)
                                                                                                                               	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1240)

WanDa1993 avatar Dec 15 '24 03:12 WanDa1993

在排查问题的过程中,也发现了一部分相似的问题:

  • #1727
  • #1699
  • #1566

本质上述问题的原因,都是出现SystemClassLoaderAdder#checkDexInstall 这个函数

WanDa1993 avatar Dec 15 '24 03:12 WanDa1993

深入源码:

SystemClassLoaderAdder#installDexes

    public static void installDexes(Application application, ClassLoader loader, File dexOptDir, List<File> files,
                                    boolean isProtectedApp, boolean useDLC) throws Throwable {
        ShareTinkerLog.i(TAG, "installDexes dexOptDir: " + dexOptDir.getAbsolutePath() + ", dex size:" + files.size());

        if (!files.isEmpty()) {
            files = createSortedAdditionalPathEntries(files);
            ClassLoader classLoader = loader;
            if (Build.VERSION.SDK_INT >= 24 && !isProtectedApp) {
                classLoader = NewClassLoaderInjector.inject(application, loader, dexOptDir, useDLC, files);
            } else {
                injectDexesInternal(classLoader, files, dexOptDir);
            }
            //install done
            sPatchDexCount = files.size();
            ShareTinkerLog.i(TAG, "after loaded classloader: " + classLoader + ", dex size:" + sPatchDexCount);

            if (!checkDexInstall(classLoader)) {
                //reset patch dex
                SystemClassLoaderAdder.uninstallPatchDex(classLoader);
                throw new TinkerRuntimeException(ShareConstants.CHECK_DEX_INSTALL_FAIL);
            }
        }
    }

SystemClassLoaderAdder#checkDexInstall

    private static boolean checkDexInstall(ClassLoader classLoader) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        Class<?> clazz = Class.forName(CHECK_DEX_CLASS, true, classLoader);
        Field filed = ShareReflectUtil.findField(clazz, CHECK_DEX_FIELD);
        boolean isPatch = (boolean) filed.get(null);
        ShareTinkerLog.i(TAG, "checkDexInstall result: %s, checker_classloader: %s", isPatch, clazz.getClassLoader());
        return isPatch;
    }

整体流程没有大的问题,对于补丁合成的流程的校验也是OK的

WanDa1993 avatar Dec 15 '24 03:12 WanDa1993

深入查看产物:

  • 查看生成的Patch文件
image

其TinkerTestDexLoad#isPatch的值为true

  • 查看Tinker的合成补丁之后的产物 tinker_classN image

发现其TinkerTestDexLoad#isPatch的值为false

  • 问题初步确定 1.合成之后的产物TinkerTestDexLoad#isPatch为false,导致校验失败 2.往上进一步分析,是基准包在打包的时候已经包含TinkerTestDexLoad#isPatch为false、而补丁包的TinkerTestDexLoad#isPatch虽然为true,classLoader在加载时会优先使用前面的classes.dex文件,才导致问题产生

WanDa1993 avatar Dec 15 '24 03:12 WanDa1993

解决方式:

  • 打包的时候,将TinkerTestDexLoad#isPatch移除
  • 根据目前的理解,如果补丁生成以及合成校验都没问题的话,直接找到补丁包的TinkerTestDexLoad#isPatch为true,同样也说明补丁合成流程没问题

WanDa1993 avatar Dec 15 '24 03:12 WanDa1993

我根据这个问题,大部分的Tinker的源码我都查阅了。我发现TinkerTestDexLoad#isPatch这块的流程很早版本之前就存在了

image image image

同样我也找到,为什么加载这个标志的原因

微信Tinker的一切都在这里,包括源码(一)

image

WanDa1993 avatar Dec 15 '24 03:12 WanDa1993

这里我有部分疑问,需要各位朋友帮忙解答一下:

  • TinkerTestDexLoad#isPatch相关的代码在16年就存在了,但是当时热修复使用几乎没有此类问题,为什么在Android 11 之后此类问题却频繁出现?这个问题更深层次的原因到底是由于哪块问题导致的?

  • 上面判定出问题原因是合成之后的TinkerTestDexLoad#isPatch重复导致的,还有没有更深层次的原因?

  • 上面的修复方式,虽然解决了目前问题,但是不确定是不是一个好的修复方式?

我看后续Tinker的小的适配部分都是您这边在维护,麻烦抽空帮忙看看这个问题,感谢 @tys282000

WanDa1993 avatar Dec 15 '24 03:12 WanDa1993

Solution:

  • When packaging, remove TinkerTestDexLoad#isPatch
  • According to the current understanding, if there is no problem with patch generation and synthesis verification, directly find the patch package TinkerTestDexLoad#isPatch is true, which also indicates that the patch synthesis process is fine.

Can you elaborate on the steps you took to fix this issue in your app? I am also facing this. Thank you! @WanDa1993

gergesh avatar Jan 19 '25 16:01 gergesh

@gergesh At that time, I was testing for that issue.

Although the experimental results were as expected, I did not choose this method for repair.

Because we were unsure if this repair would cause other problems, we ultimately chose not to use a hot fix and opted for a more stable release version patch.

I documented this scenario, and I might only try this approach in emergency and necessary situations.

WanDa1993 avatar Jan 20 '25 08:01 WanDa1993

@WanDa1993 How can i set this isPatch variable to true while packing?

Image

Thanks.

farhazulMullick avatar Mar 15 '25 11:03 farhazulMullick