33. 熱修復-QQ空間超級補丁方案-Android N兼容性

Android N混合編譯

ART 是在 Android KitKat(Android 4.0)引入並在 Lollipop(Android 5.0)中設爲默認運行環境,可以看作Dalvik2.0。 ART模式在Android N(7.0)之前安裝APK時會採用AOT(Ahead of time:提前編譯、靜態編譯)預編譯爲機器碼。

而在Android N使用混合模式的運行時。應用在安裝時不做編譯,而是運行時解釋字節碼,同時在JIT編譯了一 些代碼後將這些代碼信息記錄至Profile文件,等到設備空閒的時候使用AOT(All-Of-the-Time compilation:全 時段編譯)編譯生成稱爲app_image的base.art(類對象映像)文件,這個art文件會在apk啓動時自動加載(相當 於緩存)。也就是說這種情況下,當我們的application的onCreate方法執行的時候,可能需要被修復的類已經被加載過了,根據類加載原理,類被加載了無法被替換,即無法修復。

Tinker團隊 Android N混合編譯與對熱補丁影響解析

https://mp.weixin.qq.com/s?__biz=MzAwNDY1ODY2OQ==&mid=2649286341&idx=1&sn=054d595af6e824cbe4edd79427fc2706&scene=0#wechat_redirect

可以看下tinker對這個問題的描述




那麼既然存在這樣的問題,解決的辦法是什麼?就是運行時替換PathClassLoader方案

運行時替換PathClassLoader方案

App image中的class是插入到PathClassloader中的ClassTable中。假設我們完全廢棄掉PathClassloader,而 採用一個新建Classloader來加載後續的所有類,即可達到將cache無用化的效果。

這種方式不會影響沒有補丁時的性能,但在加載補丁後,由於廢棄了App image帶來一定的性能損耗。具體數據如下:


代碼實現

1.新建ClassLoader
        /**
         * 1、先把補丁包的dex拼起來
         */
        // 獲得原始的dexPath用於構造classloader
        StringBuilder dexPathBuilder = new StringBuilder();
        String packageName = context.getPackageName();
        boolean isFirstItem = true;
        for (File patch : patchs) {
            //添加:分隔符  /xx/a.dex:/xx/b.dex
            if (isFirstItem) {
                isFirstItem = false;
            } else {
                dexPathBuilder.append(File.pathSeparator);
            }
            dexPathBuilder.append(patch.getAbsolutePath());
        }

        /**
         * 2、把apk中的dex拼起來
         */
        //得到原本的pathList
        Field pathListField = ShareReflectUtil.findField(oldClassLoader, "pathList");
        Object oldPathList = pathListField.get(oldClassLoader);

        //dexElements
        Field dexElementsField = ShareReflectUtil.findField(oldPathList, "dexElements");
        Object[] oldDexElements = (Object[]) dexElementsField.get(oldPathList);

        //從Element上得到 dexFile
        Field dexFileField = ShareReflectUtil.findField(oldDexElements[0], "dexFile");
        for (Object oldDexElement : oldDexElements) {
            String dexPath = null;
            DexFile dexFile = (DexFile) dexFileField.get(oldDexElement);
            if (dexFile != null) {
                dexPath = dexFile.getName();
            }
            if (dexPath == null || dexPath.isEmpty()) {
                continue;
            }
            if (!dexPath.contains("/" + packageName)) {
                continue;
            }
            if (isFirstItem) {
                isFirstItem = false;
            } else {
                dexPathBuilder.append(File.pathSeparator);
            }
            dexPathBuilder.append(dexPath);
        }
        String combinedDexPath = dexPathBuilder.toString();

        /**
         * 3、獲取apk中的so加載路徑
         */
        //  app的native庫(so) 文件目錄 用於構造classloader
        Field nativeLibraryDirectoriesField = ShareReflectUtil.findField(oldPathList, "nativeLibraryDirectories");
        List<File> oldNativeLibraryDirectories = (List<File>) nativeLibraryDirectoriesField.get(oldPathList);
        StringBuilder libraryPathBuilder = new StringBuilder();
        isFirstItem = true;
        for (File libDir : oldNativeLibraryDirectories) {
            if (libDir == null) {
                continue;
            }
            if (isFirstItem) {
                isFirstItem = false;
            } else {
                libraryPathBuilder.append(File.pathSeparator);
            }
            libraryPathBuilder.append(libDir.getAbsolutePath());
        }

        String combinedLibraryPath = libraryPathBuilder.toString();

        //創建自己的類加載器
        ClassLoader result = new EnjoyClassLoader(combinedDexPath, combinedLibraryPath, ClassLoader.getSystemClassLoader());
2.替換ClassLoader
        Thread.currentThread().setContextClassLoader(classLoader);
        Context baseContext = (Context) ShareReflectUtil.findField(app, "mBase").get(app);
        if (Build.VERSION.SDK_INT >= 26) {
            ShareReflectUtil.findField(baseContext, "mClassLoader").set(baseContext, classLoader);
        }
        Object basePackageInfo = ShareReflectUtil.findField(baseContext, "mPackageInfo").get(baseContext);
        ShareReflectUtil.findField(basePackageInfo, "mClassLoader").set(basePackageInfo, classLoader);
        if (Build.VERSION.SDK_INT < 27) {
            Resources res = app.getResources();
            try {
                ShareReflectUtil.findField(res, "mClassLoader").set(res, classLoader);

                final Object drawableInflater = ShareReflectUtil.findField(res, "mDrawableInflater").get(res);
                if (drawableInflater != null) {
                    ShareReflectUtil.findField(drawableInflater, "mClassLoader").set(drawableInflater, classLoader);
                }
            } catch (Throwable ignored) {
                // Ignored.
            }
        }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章