热修复框架 - Tinker 安装流程分析

代码tinker 1.9.14.7
TinkerApplication初始化完成之后,接着会在继承DefaultApplicationLike的子类中进行Tinker初始化:

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

    MultiDex.install(base);//使应用支持分包

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

    TinkerInstaller.install(this,
            loadReporter,//加载合成的包的报告类
            patchReporter,//打修复包过程中的报告类
            patchListener,//对修复包最开始的检查
            DefaultTinkerResultService.class, //patch包合成完成的后续操作服务
            upgradePatchProcessor);//生成一个新的patch合成包
}

这篇文章就研究下TinkerInstaller.install过程。

TinkerInstaller.java
public static Tinker install(ApplicationLike applicationLike, LoadReporter loadReporter, PatchReporter patchReporter,
                             PatchListener listener, Class 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类的初始化完成,然后调用Tinker的install

Tinker.java
public void install(Intent intentResult, Class serviceClass,
                    AbstractPatch upgradePatch) {
    sInstalled = true;
   //将UpgradePatch和DefaultTinkerResultService 组合进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启动过程中反馈的加载补丁结果
    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 serviceClass) {
    upgradePatchProcessor = upgradePatch;
    resultServiceClass = serviceClass;
    //try to load
    try {
        Class.forName(serviceClass.getName());//确认AbstractResultService实现类存在,如果存在会预先将.class加载到虚拟机中
    } catch (ClassNotFoundException e) {
        TinkerLog.printErrStackTrace(TAG, e, "patch processor class not found.");
    }
}

TinkerPatchService是执行patch合成的服务,upgradePatch是进行合成的功能类,AbstractResultService是返回结果处理的服务,它可以自定义,默认是合成成功会killPorcess。

TinkerPatchService服务的启动是在触发合成patch的时候启动的:

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过程中put进去的执行结果
例如:

private void tryLoadPatchFilesInternal(TinkerApplication app, Intent resultIntent) {
    final int tinkerFlag = app.getTinkerFlags();
    //确保tinker enable 且非patch进程
    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;
    }
...
}

这里通过ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_DISABLE);设置了对应的执行结果。

热修复加载失败,定位问题就看这个parseTinkerResult结果。

举例Demo中遇到的问题:

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 :对应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过去,最终问题是对应patch合成的service没有在manifest注册,原因是我打的是jar而非aar,所以
需要向manifest注册下几个service,问题解决。

完了。

这个过程非常简单,总结这个过程干了两件事:

  • Tinker install过程就是初始化过程,初始化一些report类和监听,以及完成热修复相关功能的service。一切都是为后续主动触发patch包合成做准备。
  • 通过parseTinkerResult解析TinkerApplication启动过程中加载合成补丁包的结果,并通过onLoadResult反馈结果。

你可能感兴趣的:(热修复框架 - Tinker 安装流程分析)