Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
WanDa1993 opened this issue Dec 15, 2024 · 8 comments

Comments

@WanDa1993
Copy link

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

手机系统版本:Android 12

tinker版本:v1.9.15.1

gradle版本:8.9

@WanDa1993
Copy link
Author

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

热修复前

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

热修复后

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

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

问题情况:

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

@WanDa1993
Copy link
Author

WanDa1993 commented Dec 15, 2024

问题异常:

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
Copy link
Author

深入源码:

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
Copy link
Author

深入查看产物:

  • 查看生成的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
Copy link
Author

解决方式:

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

@WanDa1993
Copy link
Author

WanDa1993 commented Dec 15, 2024

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

image image image

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

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

image

@WanDa1993
Copy link
Author

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

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

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

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

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant