1. 程式人生 > >Tinker接入及原始碼分析(二)

Tinker接入及原始碼分析(二)

該系列文章分析基於 Tinker1.7.6 版本

上篇文章也提及了Tinker的熱修復原理,這裡再重複一遍:

先簡單的說一下Tinker框架熱修復的原理,主要是dex檔案的修復,不再涉及資原始檔以及so檔案的修復,通過對比原dex檔案(存在bug)與現dex檔案(bug已修復)生成差異包,生成的差異包作為補丁包下發給客戶端,客戶端做一系列校驗之後,將下發的差異包與本應用的dex檔案合併成成全量的dex檔案,並進行opt優化,在應用重啟的時候,會在TinkerApplication中載入優化過的全量dex檔案,載入過程與QQ空間熱修復方案類似,將dex檔案插入到DexPathList 中 dexElements的前面。

下面先從簡單的入手,分析補丁檔案的載入過程,載入之前我們需要明確目前補丁檔案已經push到手機,並且通過校驗,使用dexDiff演算法合成全量補丁並儲存到應用data目錄下 /data/data/package_name/tinker/

還記得TinkerApplication 和 DefaultApplicationLike 嗎?我們就從應用的入口開始分析:

public class SampleApplication extends TinkerApplication {
    public SampleApplication() {
      super(
        //tinkerFlags, tinker支援的型別,dex,library,還是全部都支援!
ShareConstants.TINKER_ENABLE_ALL, //ApplicationLike的實現類,只能傳遞字串 "tinker.sample.android.app.SampleApplicationLike", //Tinker的載入器,一般來說用預設的即可 "com.tencent.tinker.loader.TinkerLoader", //tinkerLoadVerifyFlag, 執行載入時是否校驗dex與,ib與res的Md5 false); } }

TinkerApplication.:

/**
     * Hook for sub-classes to run logic after the {@link Application#attachBaseContext} has been
     * called but before the delegate is created. Implementors should be very careful what they do
     * here since {@link android.app.Application#onCreate} will not have yet been called.
     */
    private void onBaseContextAttached(Context base) {
        applicationStartElapsedTime = SystemClock.elapsedRealtime();
        applicationStartMillisTime = System.currentTimeMillis();
        loadTinker();
        ensureDelegate();
        try {
            Method method = ShareReflectUtil.findMethod(delegate, "onBaseContextAttached", Context.class);
            method.invoke(delegate, base);
        } catch (Throwable t) {
            throw new TinkerRuntimeException("onBaseContextAttached method not found", t);
        }
        //reset save mode
        if (useSafeMode) {
            String processName = ShareTinkerInternals.getProcessName(this);
            String preferName = ShareConstants.TINKER_OWN_PREFERENCE_CONFIG + processName;
            SharedPreferences sp = getSharedPreferences(preferName, Context.MODE_PRIVATE);
            sp.edit().putInt(ShareConstants.TINKER_SAFE_MODE_COUNT, 0).commit();
        }
    }

其中loadTinker()方法是通過反射初始化我們之前傳過來的com.tencent.tinker.loader.TinkerLoader,並且呼叫它的tryLoad方法,該方法也是載入補丁包的關鍵所在,我們先放一放,繼續往下看。

ensureDelegate() 方法最終會呼叫createDelegate(),createDelegate()是通過反射初始化化我們傳過來的tinker.sample.android.app.SampleApplicationLike,最後會將初始化好的物件賦值給delegate。

private Object createDelegate() {
        try {
            // Use reflection to create the delegate so it doesn't need to go into the primary dex.
            // And we can also patch it
            Class<?> delegateClass = Class.forName(delegateClassName, false, getClassLoader());
            Constructor<?> constructor = delegateClass.getConstructor(Application.class, int.class, boolean.class, long.class, long.class,
                Intent.class, Resources[].class, ClassLoader[].class, AssetManager[].class);
            return constructor.newInstance(this, tinkerFlags, tinkerLoadVerifyFlag,
                applicationStartElapsedTime, applicationStartMillisTime,
                tinkerResultIntent, resources, classLoader, assetManager);
        } catch (Throwable e) {
            throw new TinkerRuntimeException("createDelegate failed", e);
        }
    }

接下來便是在TinkerApplication各個生命週期方法中通過反射呼叫代理的ApplicationLike中對應的生命週期方法。比如onBaseContextAttached中的:

try {
    Method method = ShareReflectUtil.findMethod(delegate, "onBaseContextAttached", Context.class);
    method.invoke(delegate, base);
} catch (Throwable t) {
    throw new TinkerRuntimeException("onBaseContextAttached method not found", t);
}

以及其他方法:

 @Override
    public void onCreate() {
        super.onCreate();
        ensureDelegate();
        delegateMethod("onCreate");
    }

    @Override
    public void onTerminate() {
        super.onTerminate();
        delegateMethod("onTerminate");
    }

    @Override
    public void onLowMemory() {
        super.onLowMemory();
        delegateMethod("onLowMemory");
    }

下面主要來看一下載入補丁的方法loadTinker():

private void loadTinker() {
        //disable tinker, not need to install
        if (tinkerFlags == TINKER_DISABLE) {
            return;
        }
        tinkerResultIntent = new Intent();
        try {
            //reflect tinker loader, because loaderClass may be define by user!
            Class<?> tinkerLoadClass = Class.forName(loaderClassName, false, getClassLoader());

            Method loadMethod = tinkerLoadClass.getMethod(TINKER_LOADER_METHOD, TinkerApplication.class, int.class, boolean.class);
            Constructor<?> constructor = tinkerLoadClass.getConstructor();
            tinkerResultIntent = (Intent) loadMethod.invoke(constructor.newInstance(), this, tinkerFlags, tinkerLoadVerifyFlag);
        } catch (Throwable e) {
            //has exception, put exception error code
            ShareIntentUtil.setIntentReturnCode(tinkerResultIntent, ShareConstants.ERROR_LOAD_PATCH_UNKNOWN_EXCEPTION);
            tinkerResultIntent.putExtra(INTENT_PATCH_EXCEPTION, e);
        }
    }

其中loaderClassName是我們傳過來的:com.tencent.tinker.loader.TinkerLoader,最終會呼叫該類的tryLoad方法,下面我們轉到TinkerLoader,看tryLoad方法:

/**
     * only main process can handle patch version change or incomplete
     */
    @Override
    public Intent tryLoad(TinkerApplication app, int tinkerFlag, boolean tinkerLoadVerifyFlag) {
        Intent resultIntent = new Intent();

        long begin = SystemClock.elapsedRealtime();
        tryLoadPatchFilesInternal(app, tinkerFlag, tinkerLoadVerifyFlag, resultIntent);
        long cost = SystemClock.elapsedRealtime() - begin;
        ShareIntentUtil.setIntentPatchCostTime(resultIntent, cost);
        return resultIntent;
    }

呼叫tryLoadPatchFilesInternal,並計算耗時:

private void tryLoadPatchFilesInternal(TinkerApplication app, int tinkerFlag, boolean tinkerLoadVerifyFlag, Intent resultIntent) {
        if (!ShareTinkerInternals.isTinkerEnabled(tinkerFlag)) {
            ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_DISABLE);
            return;
        }
        //tinker
        File patchDirectoryFile = SharePatchFileUtil.getPatchDirectory(app);
        if (patchDirectoryFile == null) {
            Log.w(TAG, "tryLoadPatchFiles:getPatchDirectory == null");
            //treat as not exist
            ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_DIRECTORY_NOT_EXIST);
            return;
        }
        String patchDirectoryPath = patchDirectoryFile.getAbsolutePath();

        //check patch directory whether exist
        if (!patchDirectoryFile.exists()) {
           //...
        }

        //tinker/patch.info
        File patchInfoFile = SharePatchFileUtil.getPatchInfoFile(patchDirectoryPath);

        //check patch info file whether exist
        if (!patchInfoFile.exists()) {
            //...
        }
        //old = 641e634c5b8f1649c75caf73794acbdf
        //new = 2c150d8560334966952678930ba67fa8
        File patchInfoLockFile = SharePatchFileUtil.getPatchInfoLockFile(patchDirectoryPath);

        patchInfo = SharePatchInfo.readAndCheckPropertyWithLock(patchInfoFile, patchInfoLockFile);
        if (patchInfo == null) {
            ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_INFO_CORRUPTED);
            return;
        }

        String oldVersion = patchInfo.oldVersion;
        String newVersion = patchInfo.newVersion;

        if (oldVersion == null || newVersion == null) {
            //it is nice to clean patch
            Log.w(TAG, "tryLoadPatchFiles:onPatchInfoCorrupted");
            ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_INFO_CORRUPTED);
            return;
        }

        resultIntent.putExtra(ShareIntentUtil.INTENT_PATCH_OLD_VERSION, oldVersion);
        resultIntent.putExtra(ShareIntentUtil.INTENT_PATCH_NEW_VERSION, newVersion);

        boolean mainProcess = ShareTinkerInternals.isInMainProcess(app);
        boolean versionChanged = !(oldVersion.equals(newVersion));

        String version = oldVersion;
        if (versionChanged && mainProcess) {
            version = newVersion;
        }
        if (ShareTinkerInternals.isNullOrNil(version)) {
            Log.w(TAG, "tryLoadPatchFiles:version is blank, wait main process to restart");
            ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_INFO_BLANK);
            return;
        }

        //patch-641e634c
        String patchName = SharePatchFileUtil.getPatchVersionDirectory(version);
        if (patchName == null) {
            Log.w(TAG, "tryLoadPatchFiles:patchName is null");
            //we may delete patch info file
            ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_VERSION_DIRECTORY_NOT_EXIST);
            return;
        }
        //tinker/patch.info/patch-641e634c
        String patchVersionDirectory = patchDirectoryPath + "/" + patchName;
        File patchVersionDirectoryFile = new File(patchVersionDirectory);

        if (!patchVersionDirectoryFile.exists()) {
            Log.w(TAG, "tryLoadPatchFiles:onPatchVersionDirectoryNotFound");
            //we may delete patch info file
            ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_VERSION_DIRECTORY_NOT_EXIST);
            return;
        }

        //tinker/patch.info/patch-641e634c/patch-641e634c.apk
        File patchVersionFile = new File(patchVersionDirectoryFile.getAbsolutePath(), SharePatchFileUtil.getPatchVersionFile(version));

        if (!patchVersionFile.exists()) {
            Log.w(TAG, "tryLoadPatchFiles:onPatchVersionFileNotFound");
            //we may delete patch info file
            ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_VERSION_FILE_NOT_EXIST);
            return;
        }

        ShareSecurityCheck securityCheck = new ShareSecurityCheck(app);

        int returnCode = ShareTinkerInternals.checkTinkerPackage(app, tinkerFlag, patchVersionFile, securityCheck);
        if (returnCode != ShareConstants.ERROR_PACKAGE_CHECK_OK) {
            Log.w(TAG, "tryLoadPatchFiles:checkTinkerPackage");
            resultIntent.putExtra(ShareIntentUtil.INTENT_PATCH_PACKAGE_PATCH_CHECK, returnCode);
            ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_PACKAGE_CHECK_FAIL);
            return;
        }

        resultIntent.putExtra(ShareIntentUtil.INTENT_PATCH_PACKAGE_CONFIG, securityCheck.getPackagePropertiesIfPresent());

        final boolean isEnabledForDex = ShareTinkerInternals.isTinkerEnabledForDex(tinkerFlag);

        if (isEnabledForDex) {
            //tinker/patch.info/patch-641e634c/dex
            boolean dexCheck = TinkerDexLoader.checkComplete(patchVersionDirectory, securityCheck, resultIntent);
            if (!dexCheck) {
                //file not found, do not load patch
                Log.w(TAG, "tryLoadPatchFiles:dex check fail");
                return;
            }
        }

        final boolean isEnabledForNativeLib = ShareTinkerInternals.isTinkerEnabledForNativeLib(tinkerFlag);

        if (isEnabledForNativeLib) {
            //tinker/patch.info/patch-641e634c/lib
            //...
        }

        //check resource
        final boolean isEnabledForResource = ShareTinkerInternals.isTinkerEnabledForResource(tinkerFlag);
        Log.w(TAG, "tryLoadPatchFiles:isEnabledForResource:" + isEnabledForResource);
        if (isEnabledForResource) {
            //...
        }
        //only work for art platform oat
        boolean isSystemOTA = ShareTinkerInternals.isVmArt() && ShareTinkerInternals.isSystemOTA(patchInfo.fingerPrint);

        //we should first try rewrite patch info file, if there is a error, we can't load jar
        if (isSystemOTA
            || (mainProcess && versionChanged)) {
            //...
        }
        if (!checkSafeModeCount(app)) {
            //...
            return;
        }
        //now we can load patch jar
        if (isEnabledForDex) {
            boolean loadTinkerJars = TinkerDexLoader.loadTinkerJars(app, tinkerLoadVerifyFlag, patchVersionDirectory, resultIntent, isSystemOTA);
            if (!loadTinkerJars) {
                Log.w(TAG, "tryLoadPatchFiles:onPatchLoadDexesFail");
                return;
            }
        }

        //now we can load patch resource
        if (isEnabledForResource) {
            //...
        }
        //all is ok!
        ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_OK);
        Log.i(TAG, "tryLoadPatchFiles: load end, ok!");
        return;
    }

以上程式碼省略了部分判斷補丁檔案是否存在,是否有效,以及載入補丁資原始檔的方法,主要檢查補丁資訊中的資料是否有效,校驗補丁簽名以及tinkerId與基準包是否一致。在校驗簽名時,為了加速校驗速度,Tinker只校驗 *_meta.txt檔案,然後再根據meta檔案中的md5校驗其他檔案。最後呼叫

TinkerDexLoader.loadTinkerJars(app, tinkerLoadVerifyFlag, patchVersionDirectory, resultIntent, isSystemOTA);

開始載入補丁檔案:

/**
     * Load tinker JARs and add them to
     * the Application ClassLoader.
     *
     * @param application The application.
     */
    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    public static boolean loadTinkerJars(Application application, boolean tinkerLoadVerifyFlag, String directory, Intent intentResult, boolean isSystemOTA) {
        if (dexList.isEmpty()) {
            Log.w(TAG, "there is no dex to load");
            return true;
        }

        PathClassLoader classLoader = (PathClassLoader) TinkerDexLoader.class.getClassLoader();
        if (classLoader != null) {
            Log.i(TAG, "classloader: " + classLoader.toString());
        } else {
            Log.e(TAG, "classloader is null");
            ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_CLASSLOADER_NULL);
            return false;
        }
        String dexPath = directory + "/" + DEX_PATH + "/";
        File optimizeDir = new File(directory + "/" + DEX_OPTIMIZE_PATH);
//        Log.i(TAG, "loadTinkerJars: dex path: " + dexPath);
//        Log.i(TAG, "loadTinkerJars: opt path: " + optimizeDir.getAbsolutePath());

        ArrayList<File> legalFiles = new ArrayList<>();

        final boolean isArtPlatForm = ShareTinkerInternals.isVmArt();
        for (ShareDexDiffPatchInfo info : dexList) {
            //for dalvik, ignore art support dex
            if (isJustArtSupportDex(info)) {
                continue;
            }
            String path = dexPath + info.realName;
            File file = new File(path);

            if (tinkerLoadVerifyFlag) {
                //...校驗
            }
            legalFiles.add(file);
        }

        if (isSystemOTA) {
            parallelOTAResult = true;
            parallelOTAThrowable = null;
            Log.w(TAG, "systemOTA, try parallel oat dexes!!!!!");

            TinkerParallelDexOptimizer.optimizeAll(
                legalFiles, optimizeDir,
                new TinkerParallelDexOptimizer.ResultCallback() {
                    long start;

                    @Override
                    public void onStart(File dexFile, File optimizedDir) {
                        start = System.currentTimeMillis();
                        Log.i(TAG, "start to optimize dex:" + dexFile.getPath());
                    }

                    @Override
                    public void onSuccess(File dexFile, File optimizedDir) {
                        // Do nothing.
                        Log.i(TAG, "success to optimize dex " + dexFile.getPath() + "use time " + (System.currentTimeMillis() - start));
                    }
                    @Override
                    public void onFailed(File dexFile, File optimizedDir, Throwable thr) {
                        parallelOTAResult = false;
                        parallelOTAThrowable = thr;
                        Log.i(TAG, "fail to optimize dex " + dexFile.getPath() + "use time " + (System.currentTimeMillis() - start));
                    }
                }
            );
            if (!parallelOTAResult) {
                Log.e(TAG, "parallel oat dexes failed");
                intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_EXCEPTION, parallelOTAThrowable);
                ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_PARALLEL_DEX_OPT_EXCEPTION);
                return false;
            }
        }
        try {
            SystemClassLoaderAdder.installDexes(application, classLoader, optimizeDir, legalFiles);
        } catch (Throwable e) {
            Log.e(TAG, "install dexes failed");
//            e.printStackTrace();
            intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_EXCEPTION, e);
            ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_LOAD_EXCEPTION);
            return false;
        }

        return true;
    }

以上程式碼根據傳過來的tinkerLoadVerifyFlag選項控制是否每次載入都要驗證dex的md5值,一般來說不需要,預設也是false,會節省載入時間。

然後根據傳過來的isSystemOTA來決定是否OTA((Ahead-Of—Time 提前編譯)優化

最後呼叫

SystemClassLoaderAdder.installDexes(application, classLoader, optimizeDir, legalFiles)

載入dex檔案

@SuppressLint("NewApi")
    public static void installDexes(Application application, PathClassLoader loader, File dexOptDir, List<File> files)
        throws Throwable {

        if (!files.isEmpty()) {
            ClassLoader classLoader = loader;
            if (Build.VERSION.SDK_INT >= 24) {
                classLoader = AndroidNClassLoader.inject(loader, application);
            }
            //because in dalvik, if inner class is not the same classloader with it wrapper class.
            //it won't fail at dex2opt
            if (Build.VERSION.SDK_INT >= 23) {
                V23.install(classLoader, files, dexOptDir);
            } else if (Build.VERSION.SDK_INT >= 19) {
                V19.install(classLoader, files, dexOptDir);
            } else if (Build.VERSION.SDK_INT >= 14) {
                V14.install(classLoader, files, dexOptDir);
            } else {
                V4.install(classLoader, files, dexOptDir);
            }
            //install done
            sPatchDexCount = files.size();
            Log.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);
            }
        }
    }

這裡就是根據不同的系統版本把dex插到dexElements的前面,其中比較特殊的是安卓7.0,在Dalvik虛擬機器中是通過JIT,在執行時將熱程式碼編譯成機器碼,可以提高下次執行到這段程式碼的速度,ART虛擬機器改變了這種方式,改為OTA,一開始安裝的時候就會將位元組碼編譯成機器碼,提高執行效率,但帶來了2個問題,一個是安裝時間過長,佔用體積過大;

所以在Android N上改變了這種激進的編譯方式,改為混合編譯,混合編譯執行主要指AOT編譯,解釋執行與JIT編譯。簡單來說,在應用執行時分析執行過的程式碼以及“熱程式碼”,並將配置儲存下來。在裝置空閒與充電時,ART僅僅編譯這份配置中的“熱程式碼”。

無論是使用插入pathlist還是parent classloader的方式,若補丁修改的class已經存在與app image,它們都是無法通過熱補丁更新的。它們在啟動app時已經加入到PathClassloader的ClassTable中,系統在查詢類時會直接使用base.apk中的class

假設base.art檔案在補丁前已經存在,這裡存在三種情況:

  1. 補丁修改的類都不app image中;這種情況是最理想的,此時補丁機制依然有效;
  2. 補丁修改的類部分在app image中;這種情況我們只能更新一部分的類,此時是最危險的。一部分類是新的,一部分類是舊的,app可能會出現地址錯亂而出現crash。
  3. 補丁修改的類全部在app image中;這種情況只是造成補丁不生效,app並不會因此造成crash。

Tinker的解決方案是,完全廢棄掉PathClassloader,而採用一個新建Classloader來載入後續的所有類,即可達到將cache無用化的效果。基本原理我們清楚了,讓我們來看下程式碼吧。

if (Build.VERSION.SDK_INT >= 24) {
    classLoader = AndroidNClassLoader.inject(loader, application);
}

繼續往下看具體的載入過程,這裡就看一個V14的實現吧:

/**
     * Installer for platform versions 14, 15, 16, 17 and 18.
     */
    private static final class V14 {

        private static void install(ClassLoader loader, List<File> additionalClassPathEntries,
                                    File optimizedDirectory)
            throws IllegalArgumentException, IllegalAccessException,
            NoSuchFieldException, InvocationTargetException, NoSuchMethodException {
            /* The patched class loader is expected to be a descendant of
             * dalvik.system.BaseDexClassLoader. We modify its
             * dalvik.system.DexPathList pathList field to append additional DEX
             * file entries.
             */
            Field pathListField = ShareReflectUtil.findField(loader, "pathList");
            Object dexPathList = pathListField.get(loader);
            ShareReflectUtil.expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList,
                new ArrayList<File>(additionalClassPathEntries), optimizedDirectory));
        }

        /**
         * A wrapper around
         * {@code private static final dalvik.system.DexPathList#makeDexElements}.
         */
        private static Object[] makeDexElements(
            Object dexPathList, ArrayList<File> files, File optimizedDirectory)
            throws IllegalAccessException, InvocationTargetException,
            NoSuchMethodException {
            Method makeDexElements =
                ShareReflectUtil.findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class);

            return (Object[]) makeDexElements.invoke(dexPathList, files, optimizedDirectory);
        }
    }

通過反射拿到BaseDexClassLoader的pathList,然後通過反射呼叫PathList的makeDexElements傳進去的引數分別是補丁dexList和優化過的opt目錄,在Tinker中是dex補丁目錄的同級目錄odex/。

看一下下面這個方法,是將生成的dexElements插入到原先dexElements的前面。

public static void expandFieldArray(Object instance, String fieldName, Object[] extraElements)
        throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
        Field jlrField = findField(instance, fieldName);

        Object[] original = (Object[]) jlrField.get(instance);
        Object[] combined = (Object[]) Array.newInstance(original.getClass().getComponentType(), original.length + extraElements.length);

        // NOTE: changed to copy extraElements first, for patch load first

        System.arraycopy(extraElements, 0, combined, 0, extraElements.length);
        System.arraycopy(original, 0, combined, extraElements.length, original.length);

        jlrField.set(instance, combined);
    }

首先會拿到原始的陣列:

Object[] original = (Object[]) jlrField.get(instance);

再生成一個長度為original.length + extraElements.length的新陣列,

Object[] combined = (Object[]) Array.newInstance(original.getClass().getComponentType(), original.length + extraElements.length);

然後先把傳進來的extraElements拷貝到新陣列中,再把原始的陣列拷貝進來,這裡的位置很重要,必須得得將載入的dex檔案列表拷貝到陣列前面,再拷貝原先的陣列放在陣列的後面;否則補丁將不會生效。

System.arraycopy(extraElements, 0, combined, 0, extraElements.length);
System.arraycopy(original, 0, combined, extraElements.length, original.length);

最後將新陣列設定進instance中。

jlrField.set(instance, combined);

到此為止載入補丁dex檔案的過程就結束了。

至於為什麼新的補丁檔案載入的類會生效,那麼需要看一下BaseDexClassLoader,該類是PathClassLoader和DexClassLoader的基類,

public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(parent);

        this.originalPath = dexPath;
        this.pathList =
            new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class clazz = pathList.findClass(name);

        if (clazz == null) {
            throw new ClassNotFoundException(name);
        }

        return clazz;
    }

其中findClass會呼叫DexPathList的findClass:

//DexPathList
    
            
           

相關推薦

Tinker接入原始碼分析

該系列文章分析基於 Tinker1.7.6 版本 上篇文章也提及了Tinker的熱修復原理,這裡再重複一遍: 先簡單的說一下Tinker框架熱修復的原理,主要是dex檔案的修復,不再涉及資原始檔以及so檔案的修復,通過對比原dex檔案(存在bug)與現d

Tomcat原始碼分析 ----- Tomcat整體架構元件

前言 Tomcat的前身為Catalina,而Catalina又是一個輕量級的Servlet容器。在美國,catalina是一個很美的小島。所以Tomcat作者的寓意可能是想把Tomcat設計成一個優雅美麗且輕量級的web伺服器。Tomcat從4.x版本開始除了作為支援Servlet的容器外,額外加入了

Spring原始碼分析之IOC的三種常見用法原始碼實現

Spring原始碼分析之IOC的三種常見用法及原始碼實現(二) 回顧上文 我們研究的是 AnnotationConfigApplicationContext annotationConfigApplication = new AnnotationConfigApplicationContext

從零開始Rtklib解讀篇-簡單的程式設計理論和演算法結構分析

從bin裡進入。主進入方式為RTKLAUNCH.exe 第一個RTKPLOT右上角的小方塊可以勾選NormalAPs,RTKPOST_MKL,RTKPOST_WIN64, Minimize等選項。通常是第一個。另外64位系統下通常也是選用NormalAPs,RTKPOST_WIN

Flume NG原始碼分析支援執行時動態修改配置的配置模組

在上一篇中講了Flume NG配置模組基本的介面的類,PropertiesConfigurationProvider提供了基於properties配置檔案的靜態配置的能力,這篇細說一下PollingPropertiesFileConfigurationProvider提供的執行時動態修改配置並生效的

GCC原始碼分析——前端

原文連結:http://blog.csdn.net/sonicling/article/details/6706152   從這一篇開始,我們將從原始碼的角度來分析GCC如何完成對C語言原始檔的處理。GCC的內部構架在GCC Internals(搜“gccint.pdf”,或者見[

Glide原始碼分析——從用法來看之load&into方法

上一篇,我們分析了with方法,文章連結: https://blog.csdn.net/qq_36391075/article/details/82833260 在with方法中,進行了Glide的初始化,建立了RequesManger,並且綁定了生命週期,最終返回了一個Reques

YOLOv2原始碼分析

文章全部YOLOv2原始碼分析 接著上一講沒有講完的make_convolutional_layer函式 0x01 make_convolutional_layer //make_convolutional_laye

zigbee 之ZStack-2.5.1a原始碼分析 無線接收控制LED

本文描述ZStack-2.5.1a 模板及無線接收移植相關內容。 main HAL_BOARD_INIT // HAL_TURN_OFF_LED1 InitBoard HalDriverInit HalAdcInit

Hibernate使用原始碼分析

Hibernate使用及原始碼分析(一) 本篇文章主要通過hibernate初級使用分析一下原始碼,只是給初學者一點小小的建議,不喜勿噴,謝謝! hibernate環境搭建 簡單使用 原始碼走讀 一 hibernate環境搭建 這裡直接

兄弟連區塊鏈入門教程eth原始碼分析p2p-udp.go原始碼分析

ping方法與pending的處理,之前談到了pending是等待一個reply。 這裡通過程式碼來分析是如何實現等待reply的。pending方法把pending結構體傳送給addpending. 然後等待訊息的處理和接收。 // ping sends a ping message to the giv

HashMap實現原理原始碼分析轉載

作者: dreamcatcher-cx 出處: <http://www.cnblogs.com/chengxiao/>        雜湊表(hash table)也叫散列表,是一種非常重要的資料結構,應用場景及其豐富,

Spring原始碼分析IoC容器的實現1

    Ioc(Inversion of Control)——“控制反轉”,不是什麼技術,而是一種設計思想。在Java開發中,Ioc意味著將你設計好的物件交給容器控制,而不是傳統的在你的物件內部直接控制。理解好Ioc的關鍵是要明確“誰控制誰,控制什麼,為何是反轉(有

tornado原始碼分析之iostream

在事件驅動模型中,所有任務都是以某個事件的回撥函式的方式新增至事件迴圈中的,如:HTTPServer要從socket中讀取客戶端傳送的request訊息,就必須將該socket新增至ioloop中,並設定回掉函式,在回掉函式中從socket中讀取資料,並且檢查request訊息是否全部接收到了,如果

Cat原始碼分析:Server端

初始化 服務端消費客戶端發來的訊息進行分析和展示,所以這個的初始化指的是CatHomeModule的初始化 CatHomeModule依賴TcpSocketReceiver和MessageConsumer,前者用來接收客戶端傳送的訊息,後者用來消費訊息。 TcpSocket

subsampling-scale-image-view載入長圖原始碼分析

subsampling-scale-image-view原始碼分析概要分析總結 概要 subsampling-scale-image-view是一個支援部分載入大圖長圖的圖片庫,並且還支援縮放,在subsampling-scale-image-view載入長圖原

Spring component-scan原始碼分析 -- @Configuration註解處理

上篇文章Spring component-scan原始碼分析(一) – XML解析分析了Spring解析<context:component-scan …/>標籤時,把掃描到的合適的類封裝成BeanDefinition加入Sping容器中,本篇分析S

Spring原始碼分析IoC容器的實現3

BeanDefinition的載入和解析     這個載入過程,相當於把定義的BeanDefinition在IoC容器中轉化成一個Spring內部表示的資料結構的過程。IoC容器對Bean的管理和依賴注入功能的實現,是通過對其持有的BeanDefinition進

Spring原始碼分析IoC容器的實現2

IoC容器的初始化過程     簡單來說IoC容器的初始化是由refresh()方法啟動的,這個方法標誌著IoC容器的正式啟動。這個啟動包括BeanDefinition的Resouce定位、載入和註冊三個基本過程。     第一

groupcache 原始碼分析-- LRU

lru部分的程式碼在lru/lru.go檔案中,它主要是封裝了一系列lru演算法相關的介面,供groupcahe進行快取置換相關的呼叫。 它主要封裝了下面幾個介面: // 建立一個Cache func New(maxEntries int) *Cache /