代码基于tinker 1.9.14.7
一、tinker disable方式:
Tinker.with(context).setTinkerDisable();//内存保存flag,禁止当前进程重复触发tinker热修复。
ShareTinkerInternals.setTinkerDisableWithSharedPreferences(context)//sp持久化,禁止当前安装应用重复触发tinker热修复。
二、tinker disable场景:
2.1 Tinker.with(context).setTinkerDisable()
加载异常
DefaultLoadReporter.onLoadException
TinkerLoadResult.parseTinkerResult{
...
//found uncaught exception, just return
Throwable exception = ShareIntentUtil.getIntentPatchException(intentResult);
if (exception != null) {
...
tinker.getLoadReporter().onLoadException(exception, errorCode);
return false;
}
...
}
合成异常
DefaultPatchReporter.onPatchException
TinkerPatchService.doApplyPatch{
...
try {
if (upgradePatchProcessor == null) {
throw new TinkerRuntimeException("upgradePatchProcessor is null.");
}
result = upgradePatchProcessor.tryPatch(context, path, patchResult);
} catch (Throwable throwable) {
e = throwable;
result = false;
tinker.getPatchReporter().onPatchException(patchFile, e);
}
...
}
2.2 ShareTinkerInternals.setTinkerDisableWithSharedPreferences
DefaultLoadReporter onLoadException {
switch (errorCode) {
case ShareConstants.ERROR_LOAD_EXCEPTION_DEX: load dex失败
...
ShareTinkerInternals.setTinkerDisableWithSharedPreferences(context);
break;
case ShareConstants.ERROR_LOAD_EXCEPTION_RESOURCE: load res失败
...
ShareTinkerInternals.setTinkerDisableWithSharedPreferences(context);
break;
case ShareConstants.ERROR_LOAD_EXCEPTION_UNCAUGHT: 安全模式进入3次
...
ShareTinkerInternals.setTinkerDisableWithSharedPreferences(context);
...
break;
case ShareConstants.ERROR_LOAD_EXCEPTION_UNKNOWN:反射获取tinker loader失败
无任何逻辑
break;
default:
break;
}
...
}
三、tinker disable逻辑分析
Tinker.with(context).setTinkerDisable()覆盖patch合成和加载两个场景,本次热修复不再重复,但是下次重启app又会触发热修复(前提是tinkerFlags 不为 TINKER_DISABLE)。
ShareTinkerInternals.setTinkerDisableWithSharedPreferences(context)覆盖patch加载场景,针对加载dex、res以及安全模式验证这三处非客户端逻辑部分。此处触发的加载失败一般与系统相关,因此tinker在此处无差别进行了持久化级别的disable。
四、案例
这里主要分析下ShareTinkerInternals.setTinkerDisableWithSharedPreferences(context)场景:
基于tinker 1.9.6的错误日志:
2020-09-16 18:24:51.361 8791-8791/? E/Tinker.ResourceLoader: resource hook check failed.
java.lang.NoSuchFieldException: Field mStringBlocks not found in class android.content.res.AssetManager
at com.tencent.tinker.loader.shareutil.ShareReflectUtil.findField(ShareReflectUtil.java:55)
at com.tencent.tinker.loader.TinkerResourcePatcher.a(TinkerResourcePatcher.java:101)
at com.tencent.tinker.loader.TinkerResourceLoader.checkComplete(TinkerResourceLoader.java:122)
at com.tencent.tinker.loader.TinkerLoader.tryLoadPatchFilesInternal(TinkerLoader.java:216)
at com.tencent.tinker.loader.TinkerLoader.tryLoad(TinkerLoader.java:58)
at java.lang.reflect.Method.invoke(Native Method)
at com.tencent.tinker.loader.app.TinkerApplication.loadTinker(TinkerApplication.java:163)
at com.tencent.tinker.loader.app.TinkerApplication.onBaseContextAttached(TinkerApplication.java:132)
at com.tencent.tinker.loader.app.TinkerApplication.attachBaseContext(TinkerApplication.java:148)
这里TinkerResourceLoader.checkComplete 之后走的逻辑是:
try {
TinkerResourcePatcher.isResourceCanPatch(context);
} catch (Throwable e) {
Log.e(TAG, "resource hook check failed.", e);
intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_EXCEPTION, e);
ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_RESOURCE_LOAD_EXCEPTION);//返回res加载失败exception,设置sp为false
return false;
}
报错点在TinkerResourcePatcher.isResourceCanPatch(context);
Tinker 1.9.6:
public static void isResourceCanPatch(Context context) throws Throwable {
...
// Kitkat needs this method call, Lollipop doesn't. However, it doesn't seem to cause any harm
// in L, so we do it unconditionally.
stringBlocksField = findField(assets, "mStringBlocks");
ensureStringBlocksMethod = findMethod(assets, "ensureStringBlocks");
...
}
Tinker 1.9.14.7:
public static void isResourceCanPatch(Context context) throws Throwable {
...
// Kitkat needs this method call, Lollipop doesn't. However, it doesn't seem to cause any harm
// in L, so we do it unconditionally.
try {
stringBlocksField = findField(assets, "mStringBlocks");
ensureStringBlocksMethod = findMethod(assets, "ensureStringBlocks");
} catch (Throwable ignored) {
// Ignored.
}
...
}
1.9.6 反射android.content.res.AssetManager获取mStringBlocks属性找不到,也没try catch,直接抛异常。1.9.14.7进行了try catch...
因为tinker做了大量的源码hook,容易出现异常,对这类问题tinker统一通过sp设置开关来无差别关闭热修复功能。但是这也会导致一个问题,就是例如res加载失败也会影响后续仅修改了dex的patch包的修复。
因此这里建议对包进行区分,如果有新的修复包,还是打开开关,尝试做修复,仅对相同修复失败的包进行禁止。