熱修復(fù)框架 - Tinker 安裝流程分析

代碼tinker 1.9.14.7
TinkerApplication初始化完成之后,接著會(huì)在繼承DefaultApplicationLike的子類中進(jìn)行Tinker初始化:

@Override
public void onBaseContextAttached(Context base) {
    super.onBaseContextAttached(base);
    Log.d(TAG, "HotFixApplicationLike onBaseContextAttached");

    MultiDex.install(base);//使應(yīng)用支持分包

    LoadReporter loadReporter = new DefaultLoadReporter(base);
    PatchReporter patchReporter = new DefaultPatchReporter(base);
    PatchListener patchListener = new DefaultPatchListener(base);
    AbstractPatch upgradePatchProcessor = new UpgradePatch();

    TinkerInstaller.install(this,
            loadReporter,//加載合成的包的報(bào)告類
            patchReporter,//打修復(fù)包過(guò)程中的報(bào)告類
            patchListener,//對(duì)修復(fù)包最開(kāi)始的檢查
            DefaultTinkerResultService.class, //patch包合成完成的后續(xù)操作服務(wù)
            upgradePatchProcessor);//生成一個(gè)新的patch合成包
}

這篇文章就研究下TinkerInstaller.install過(guò)程。

TinkerInstaller.java
public static Tinker install(ApplicationLike applicationLike, LoadReporter loadReporter, PatchReporter patchReporter,
                             PatchListener listener, Class<? extends AbstractResultService> resultServiceClass,
                             AbstractPatch upgradePatchProcessor) {

    Tinker tinker = new Tinker.Builder(applicationLike.getApplication())
        .tinkerFlags(applicationLike.getTinkerFlags())
        .loadReport(loadReporter)
        .listener(listener)
        .patchReporter(patchReporter)
        .tinkerLoadVerifyFlag(applicationLike.getTinkerLoadVerifyFlag()).build();

    Tinker.create(tinker);
    tinker.install(applicationLike.getTinkerResultIntent(), resultServiceClass, upgradePatchProcessor);
    return tinker;
}

Tinker類的初始化完成,然后調(diào)用Tinker的install

Tinker.java
public void install(Intent intentResult, Class<? extends AbstractResultService> serviceClass,
                    AbstractPatch upgradePatch) {
    sInstalled = true;
   //將UpgradePatch和DefaultTinkerResultService 組合進(jìn)TinkerPatchService
    TinkerPatchService.setPatchProcessor(upgradePatch, serviceClass);

    TinkerLog.i(TAG, "try to install tinker, isEnable: %b, version: %s", isTinkerEnabled(), ShareConstants.TINKER_VERSION);

    if (!isTinkerEnabled()) {
        TinkerLog.e(TAG, "tinker is disabled");
        return;
    }
    if (intentResult == null) {
        throw new TinkerRuntimeException("intentResult must not be null.");
    }
    tinkerLoadResult = new TinkerLoadResult();
   //解析TinkerApplication啟動(dòng)過(guò)程中反饋的加載補(bǔ)丁結(jié)果
    tinkerLoadResult.parseTinkerResult(getContext(), intentResult);
    //after load code set
    loadReporter.onLoadResult(patchDirectory, tinkerLoadResult.loadCode, tinkerLoadResult.costTime);

    if (!loaded) {
        TinkerLog.w(TAG, "tinker load fail!");
    }
}

先看 TinkerPatchService.setPatchProcessor(upgradePatch, serviceClass);

TinkerPatchService.java
public static void setPatchProcessor(AbstractPatch upgradePatch, Class<? extends AbstractResultService> serviceClass) {
    upgradePatchProcessor = upgradePatch;
    resultServiceClass = serviceClass;
    //try to load
    try {
        Class.forName(serviceClass.getName());//確認(rèn)AbstractResultService實(shí)現(xiàn)類存在,如果存在會(huì)預(yù)先將.class加載到虛擬機(jī)中
    } catch (ClassNotFoundException e) {
        TinkerLog.printErrStackTrace(TAG, e, "patch processor class not found.");
    }
}

TinkerPatchService是執(zhí)行patch合成的服務(wù),upgradePatch是進(jìn)行合成的功能類,AbstractResultService是返回結(jié)果處理的服務(wù),它可以自定義,默認(rèn)是合成成功會(huì)killPorcess。

TinkerPatchService服務(wù)的啟動(dòng)是在觸發(fā)合成patch的時(shí)候啟動(dòng)的:

Tinker.with(context).getPatchListener().onPatchReceived(patchLocation);

接著看 tinkerLoadResult.parseTinkerResult(getContext(), intentResult);

TinkerLoadResult.java
public boolean parseTinkerResult(Context context, Intent intentResult) {
    Tinker tinker = Tinker.with(context);
    loadCode = ShareIntentUtil.getIntentReturnCode(intentResult);

    costTime = ShareIntentUtil.getIntentPatchCostTime(intentResult);
    systemOTA = ShareIntentUtil.getBooleanExtra(intentResult, ShareIntentUtil.INTENT_PATCH_SYSTEM_OTA, false);
    oatDir = ShareIntentUtil.getStringExtra(intentResult, ShareIntentUtil.INTENT_PATCH_OAT_DIR);
    useInterpretMode = ShareConstants.INTERPRET_DEX_OPTIMIZE_PATH.equals(oatDir);

    final boolean isMainProcess = tinker.isMainProcess();

    TinkerLog.i(TAG, "parseTinkerResult loadCode:%d, process name:%s, main process:%b, systemOTA:%b, fingerPrint:%s, oatDir:%s, useInterpretMode:%b",
        loadCode, ShareTinkerInternals.getProcessName(context), isMainProcess, systemOTA, Build.FINGERPRINT, oatDir, useInterpretMode);

    //@Nullable
    final String oldVersion = ShareIntentUtil.getStringExtra(intentResult, ShareIntentUtil.INTENT_PATCH_OLD_VERSION);
    //@Nullable
    final String newVersion = ShareIntentUtil.getStringExtra(intentResult, ShareIntentUtil.INTENT_PATCH_NEW_VERSION);

    final File patchDirectory = tinker.getPatchDirectory();
    final File patchInfoFile = tinker.getPatchInfoFile();

    if (oldVersion != null && newVersion != null) {
        if (isMainProcess) {
            currentVersion = newVersion;
        } else {
            currentVersion = oldVersion;
        }

        TinkerLog.i(TAG, "parseTinkerResult oldVersion:%s, newVersion:%s, current:%s", oldVersion, newVersion,
            currentVersion);
        //current version may be nil
        String patchName = SharePatchFileUtil.getPatchVersionDirectory(currentVersion);
        if (!ShareTinkerInternals.isNullOrNil(patchName)) {
            patchVersionDirectory = new File(patchDirectory.getAbsolutePath() + "/" + patchName);
            patchVersionFile = new File(patchVersionDirectory.getAbsolutePath(), SharePatchFileUtil.getPatchVersionFile(currentVersion));
            dexDirectory = new File(patchVersionDirectory, ShareConstants.DEX_PATH);
            libraryDirectory = new File(patchVersionDirectory, ShareConstants.SO_PATH);
            resourceDirectory = new File(patchVersionDirectory, ShareConstants.RES_PATH);
            resourceFile = new File(resourceDirectory, ShareConstants.RES_NAME);
        }
        final boolean isProtectedApp = ShareIntentUtil.getBooleanExtra(intentResult, ShareIntentUtil.INTENT_IS_PROTECTED_APP, false);
        patchInfo = new SharePatchInfo(oldVersion, newVersion, isProtectedApp, false, Build.FINGERPRINT, oatDir, false);
        versionChanged = !(oldVersion.equals(newVersion));
    }

    //found uncaught exception, just return
    Throwable exception = ShareIntentUtil.getIntentPatchException(intentResult);
    if (exception != null) {
        TinkerLog.i(TAG, "Tinker load have exception loadCode:%d", loadCode);
        int errorCode = ShareConstants.ERROR_LOAD_EXCEPTION_UNKNOWN;
        switch (loadCode) {
            case ShareConstants.ERROR_LOAD_PATCH_UNKNOWN_EXCEPTION:
                errorCode = ShareConstants.ERROR_LOAD_EXCEPTION_UNKNOWN;
                break;
            case ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_LOAD_EXCEPTION:
                errorCode = ShareConstants.ERROR_LOAD_EXCEPTION_DEX;
                break;
            case ShareConstants.ERROR_LOAD_PATCH_VERSION_RESOURCE_LOAD_EXCEPTION:
                errorCode = ShareConstants.ERROR_LOAD_EXCEPTION_RESOURCE;
                break;
            case ShareConstants.ERROR_LOAD_PATCH_UNCAUGHT_EXCEPTION:
                errorCode = ShareConstants.ERROR_LOAD_EXCEPTION_UNCAUGHT;
                break;
            default:
                break;
        }
        tinker.getLoadReporter().onLoadException(exception, errorCode);
        return false;
    }

    switch (loadCode) {
        case ShareConstants.ERROR_LOAD_GET_INTENT_FAIL:
            TinkerLog.e(TAG, "can't get the right intent return code");
            throw new TinkerRuntimeException("can't get the right intent return code");
        case ShareConstants.ERROR_LOAD_DISABLE:
            TinkerLog.w(TAG, "tinker is disable, just return");
            break;
        // case ShareConstants.ERROR_LOAD_PATCH_NOT_SUPPORTED:
        //     TinkerLog.w(TAG, "tinker is not supported, just return");
        //     break;
        case ShareConstants.ERROR_LOAD_PATCH_DIRECTORY_NOT_EXIST:
        case ShareConstants.ERROR_LOAD_PATCH_INFO_NOT_EXIST:
            TinkerLog.w(TAG, "can't find patch file, is ok, just return");
            break;

        case ShareConstants.ERROR_LOAD_PATCH_INFO_CORRUPTED:
            TinkerLog.e(TAG, "path info corrupted");
            tinker.getLoadReporter().onLoadPatchInfoCorrupted(oldVersion, newVersion, patchInfoFile);
            break;

        case ShareConstants.ERROR_LOAD_PATCH_INFO_BLANK:
            TinkerLog.e(TAG, "path info blank, wait main process to restart");
            break;

        case ShareConstants.ERROR_LOAD_PATCH_VERSION_DIRECTORY_NOT_EXIST:
            TinkerLog.e(TAG, "patch version directory not found, current version:%s", currentVersion);
            tinker.getLoadReporter().onLoadFileNotFound(patchVersionDirectory,
                ShareConstants.TYPE_PATCH_FILE, true);
            break;

        case ShareConstants.ERROR_LOAD_PATCH_VERSION_FILE_NOT_EXIST:
            TinkerLog.e(TAG, "patch version file not found, current version:%s", currentVersion);
            if (patchVersionFile == null) {
                throw new TinkerRuntimeException("error load patch version file not exist, but file is null");
            }
            tinker.getLoadReporter().onLoadFileNotFound(patchVersionFile,
                ShareConstants.TYPE_PATCH_FILE, false);
            break;
        case ShareConstants.ERROR_LOAD_PATCH_PACKAGE_CHECK_FAIL:
            TinkerLog.i(TAG, "patch package check fail");
            if (patchVersionFile == null) {
                throw new TinkerRuntimeException("error patch package check fail , but file is null");
            }
            int errorCode = intentResult.getIntExtra(ShareIntentUtil.INTENT_PATCH_PACKAGE_PATCH_CHECK, ShareConstants.ERROR_LOAD_GET_INTENT_FAIL);
            tinker.getLoadReporter().onLoadPackageCheckFail(patchVersionFile, errorCode);
            break;
        case ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_DIRECTORY_NOT_EXIST:
            if (dexDirectory != null) {
                TinkerLog.e(TAG, "patch dex file directory not found:%s", dexDirectory.getAbsolutePath());
                tinker.getLoadReporter().onLoadFileNotFound(dexDirectory,
                    ShareConstants.TYPE_DEX, true);
            } else {
                //should be not here
                TinkerLog.e(TAG, "patch dex file directory not found, warning why the path is null!!!!");
                throw new TinkerRuntimeException("patch dex file directory not found, warning why the path is null!!!!");
            }
            break;
        case ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_FILE_NOT_EXIST:
            String dexPath = ShareIntentUtil.getStringExtra(intentResult,
                ShareIntentUtil.INTENT_PATCH_MISSING_DEX_PATH);
            if (dexPath != null) {
                //we only pass one missing file
                TinkerLog.e(TAG, "patch dex file not found:%s", dexPath);
                tinker.getLoadReporter().onLoadFileNotFound(new File(dexPath),
                    ShareConstants.TYPE_DEX, false);

            } else {
                TinkerLog.e(TAG, "patch dex file not found, but path is null!!!!");
                throw new TinkerRuntimeException("patch dex file not found, but path is null!!!!");
                // tinker.getLoadReporter().onLoadFileNotFound(null,
                //     ShareConstants.TYPE_DEX, false);
            }
            break;
        case ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_OPT_FILE_NOT_EXIST:
            String dexOptPath = ShareIntentUtil.getStringExtra(intentResult,
                ShareIntentUtil.INTENT_PATCH_MISSING_DEX_PATH);
            if (dexOptPath != null) {
                //we only pass one missing file
                TinkerLog.e(TAG, "patch dex opt file not found:%s", dexOptPath);
                tinker.getLoadReporter().onLoadFileNotFound(new File(dexOptPath),
                    ShareConstants.TYPE_DEX_OPT, false);

            } else {
                TinkerLog.e(TAG, "patch dex opt file not found, but path is null!!!!");
                throw new TinkerRuntimeException("patch dex opt file not found, but path is null!!!!");
                // tinker.getLoadReporter().onLoadFileNotFound(null,
                //     ShareConstants.TYPE_DEX, false);
            }
            break;
        case ShareConstants.ERROR_LOAD_PATCH_VERSION_LIB_DIRECTORY_NOT_EXIST:
            if (patchVersionDirectory != null) {
                TinkerLog.e(TAG, "patch lib file directory not found:%s", libraryDirectory.getAbsolutePath());
                tinker.getLoadReporter().onLoadFileNotFound(libraryDirectory,
                    ShareConstants.TYPE_LIBRARY, true);
            } else {
                //should be not here
                TinkerLog.e(TAG, "patch lib file directory not found, warning why the path is null!!!!");
                throw new TinkerRuntimeException("patch lib file directory not found, warning why the path is null!!!!");

                // tinker.getLoadReporter().onLoadFileNotFound(null,
                //     ShareConstants.TYPE_LIBRARY, true);
            }

            break;
        case ShareConstants.ERROR_LOAD_PATCH_VERSION_LIB_FILE_NOT_EXIST:
            String libPath = ShareIntentUtil.getStringExtra(intentResult,
                ShareIntentUtil.INTENT_PATCH_MISSING_LIB_PATH);
            if (libPath != null) {
                //we only pass one missing file and then we break
                TinkerLog.e(TAG, "patch lib file not found:%s", libPath);
                tinker.getLoadReporter().onLoadFileNotFound(new File(libPath),
                    ShareConstants.TYPE_LIBRARY, false);
            } else {
                TinkerLog.e(TAG, "patch lib file not found, but path is null!!!!");
                throw new TinkerRuntimeException("patch lib file not found, but path is null!!!!");
                // tinker.getLoadReporter().onLoadFileNotFound(null,
                //     ShareConstants.TYPE_LIBRARY, false);
            }
            break;
        case ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_CLASSLOADER_NULL:
            TinkerLog.e(TAG, "patch dex load fail, classloader is null");
            break;
        case ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_MD5_MISMATCH:
            String mismatchPath = ShareIntentUtil.getStringExtra(intentResult,
                ShareIntentUtil.INTENT_PATCH_MISMATCH_DEX_PATH);
            if (mismatchPath == null) {
                TinkerLog.e(TAG, "patch dex file md5 is mismatch, but path is null!!!!");
                throw new TinkerRuntimeException("patch dex file md5 is mismatch, but path is null!!!!");
            } else {
                TinkerLog.e(TAG, "patch dex file md5 is mismatch: %s", mismatchPath);
                tinker.getLoadReporter().onLoadFileMd5Mismatch(new File(mismatchPath),
                    ShareConstants.TYPE_DEX);
            }
            break;
        case ShareConstants.ERROR_LOAD_PATCH_REWRITE_PATCH_INFO_FAIL:
            TinkerLog.i(TAG, "rewrite patch info file corrupted");
            tinker.getLoadReporter().onLoadPatchInfoCorrupted(oldVersion, newVersion, patchInfoFile);
            break;
        case ShareConstants.ERROR_LOAD_PATCH_VERSION_RESOURCE_DIRECTORY_NOT_EXIST:
            if (patchVersionDirectory != null) {
                TinkerLog.e(TAG, "patch resource file directory not found:%s", resourceDirectory.getAbsolutePath());
                tinker.getLoadReporter().onLoadFileNotFound(resourceDirectory,
                    ShareConstants.TYPE_RESOURCE, true);
            } else {
                //should be not here
                TinkerLog.e(TAG, "patch resource file directory not found, warning why the path is null!!!!");
                throw new TinkerRuntimeException("patch resource file directory not found, warning why the path is null!!!!");
            }
            break;
        case ShareConstants.ERROR_LOAD_PATCH_VERSION_RESOURCE_FILE_NOT_EXIST:
            if (patchVersionDirectory != null) {
                TinkerLog.e(TAG, "patch resource file not found:%s", resourceFile.getAbsolutePath());
                tinker.getLoadReporter().onLoadFileNotFound(resourceFile,
                    ShareConstants.TYPE_RESOURCE, false);
            } else {
                //should be not here
                TinkerLog.e(TAG, "patch resource file not found, warning why the path is null!!!!");
                throw new TinkerRuntimeException("patch resource file not found, warning why the path is null!!!!");
            }
            break;
        case ShareConstants.ERROR_LOAD_PATCH_VERSION_RESOURCE_MD5_MISMATCH:
            if (resourceFile == null) {
                TinkerLog.e(TAG, "resource file md5 mismatch, but patch resource file not found!");
                throw new TinkerRuntimeException("resource file md5 mismatch, but patch resource file not found!");
            }
            TinkerLog.e(TAG, "patch resource file md5 is mismatch: %s", resourceFile.getAbsolutePath());

            tinker.getLoadReporter().onLoadFileMd5Mismatch(resourceFile,
                ShareConstants.TYPE_RESOURCE);
            break;
        case ShareConstants.ERROR_LOAD_PATCH_GET_OTA_INSTRUCTION_SET_EXCEPTION:
            tinker.getLoadReporter().onLoadInterpret(ShareConstants.TYPE_INTERPRET_GET_INSTRUCTION_SET_ERROR, ShareIntentUtil.getIntentInterpretException(intentResult));
            break;
        case ShareConstants.ERROR_LOAD_PATCH_OTA_INTERPRET_ONLY_EXCEPTION:
            tinker.getLoadReporter().onLoadInterpret(ShareConstants.TYPE_INTERPRET_COMMAND_ERROR, ShareIntentUtil.getIntentInterpretException(intentResult));
            break;
        case ShareConstants.ERROR_LOAD_OK:
            TinkerLog.i(TAG, "oh yeah, tinker load all success");
            tinker.setTinkerLoaded(true);
            // get load dex
            dexes = ShareIntentUtil.getIntentPatchDexPaths(intentResult);
            libs = ShareIntentUtil.getIntentPatchLibsPaths(intentResult);

            packageConfig = ShareIntentUtil.getIntentPackageConfig(intentResult);

            if (useInterpretMode) {
                tinker.getLoadReporter().onLoadInterpret(ShareConstants.TYPE_INTERPRET_OK, null);
            }
            if (isMainProcess && versionChanged) {
                //change the old version to new
                tinker.getLoadReporter().onLoadPatchVersionChanged(oldVersion, newVersion, patchDirectory, patchVersionDirectory.getName());
            }
            return true;
        default:
            break;
    }
    return false;
}

這里intentResult是在TinkerLoader.tryLoadPatchFilesInternal過(guò)程中put進(jìn)去的執(zhí)行結(jié)果
例如:

private void tryLoadPatchFilesInternal(TinkerApplication app, Intent resultIntent) {
    final int tinkerFlag = app.getTinkerFlags();
    //確保tinker enable 且非patch進(jìn)程
    if (!ShareTinkerInternals.isTinkerEnabled(tinkerFlag)) {
        Log.w(TAG, "tryLoadPatchFiles: tinker is disable, just return");
        ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_DISABLE);
        return;
    }
    if (ShareTinkerInternals.isInPatchProcess(app)) {
        Log.w(TAG, "tryLoadPatchFiles: we don't load patch with :patch process itself, just return");
        ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_DISABLE);
        return;
    }
...
}

這里通過(guò)ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_DISABLE);設(shè)置了對(duì)應(yīng)的執(zhí)行結(jié)果。

熱修復(fù)加載失敗,定位問(wèn)題就看這個(gè)parseTinkerResult結(jié)果。

舉例Demo中遇到的問(wèn)題:

I/Tinker.TinkerLoadResult: parseTinkerResult loadCode:-3, process name:com.stan.tinkersdkdemo, main process:true, systemOTA:false, fingerPrint:Xiaomi/dipper/dipper:9/PKQ1.180729.001/9.10.122:user/test-keys, oatDir:null, useInterpretMode:false

loadCode -3 :對(duì)應(yīng)ERROR_LOAD_PATCH_INFO_NOT_EXIST
看看是什么原因set的

if (!patchInfoFile.exists()) {
    Log.w(TAG, "tryLoadPatchFiles:patch info not exist:" + patchInfoFile.getAbsolutePath());
    ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_INFO_NOT_EXIST);
    return;
}

patch.info不存在, adb查看下果然是,patch.info生成是在合成patch的地方,debug過(guò)去,最終問(wèn)題是對(duì)應(yīng)patch合成的service沒(méi)有在manifest注冊(cè),原因是我打的是jar而非aar,所以
需要向manifest注冊(cè)下幾個(gè)service,問(wèn)題解決。

完了。

這個(gè)過(guò)程非常簡(jiǎn)單,總結(jié)這個(gè)過(guò)程干了兩件事:

  • Tinker install過(guò)程就是初始化過(guò)程,初始化一些report類和監(jiān)聽(tīng),以及完成熱修復(fù)相關(guān)功能的service。一切都是為后續(xù)主動(dòng)觸發(fā)patch包合成做準(zhǔn)備。
  • 通過(guò)parseTinkerResult解析TinkerApplication啟動(dòng)過(guò)程中加載合成補(bǔ)丁包的結(jié)果,并通過(guò)onLoadResult反饋結(jié)果。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請(qǐng)通過(guò)簡(jiǎn)信或評(píng)論聯(lián)系作者。

友情鏈接更多精彩內(nèi)容