代码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 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类的初始化完成,然后调用Tinker的install
Tinker.java
public void install(Intent intentResult, Class extends AbstractResultService> 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 extends AbstractResultService> 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反馈结果。