代碼基于tinker 1.9.14.7
一队询、tinker disable方式:
Tinker.with(context).setTinkerDisable();//內(nèi)存保存flag唆铐,禁止當(dāng)前進(jìn)程重復(fù)觸發(fā)tinker熱修復(fù)。
ShareTinkerInternals.setTinkerDisableWithSharedPreferences(context)//sp持久化,禁止當(dāng)前安裝應(yīng)用重復(fù)觸發(fā)tinker熱修復(fù)。
二、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: 安全模式進(jìn)入3次
...
ShareTinkerInternals.setTinkerDisableWithSharedPreferences(context);
...
break;
case ShareConstants.ERROR_LOAD_EXCEPTION_UNKNOWN:反射獲取tinker loader失敗
無任何邏輯
break;
default:
break;
}
...
}
三别伏、tinker disable邏輯分析
Tinker.with(context).setTinkerDisable()覆蓋patch合成和加載兩個場景,本次熱修復(fù)不再重復(fù)忧额,但是下次重啟app又會觸發(fā)熱修復(fù)(前提是tinkerFlags 不為 TINKER_DISABLE)厘肮。
ShareTinkerInternals.setTinkerDisableWithSharedPreferences(context)覆蓋patch加載場景,針對加載dex睦番、res以及安全模式驗證這三處非客戶端邏輯部分类茂。此處觸發(fā)的加載失敗一般與系統(tǒng)相關(guān),因此tinker在此處無差別進(jìn)行了持久化級別的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,設(shè)置sp為false
return false;
}
報錯點(diǎn)在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進(jìn)行了try catch...
因為tinker做了大量的源碼hook夫嗓,容易出現(xiàn)異常迟螺,對這類問題tinker統(tǒng)一通過sp設(shè)置開關(guān)來無差別關(guān)閉熱修復(fù)功能。但是這也會導(dǎo)致一個問題舍咖,就是例如res加載失敗也會影響后續(xù)僅修改了dex的patch包的修復(fù)矩父。
因此這里建議對包進(jìn)行區(qū)分,如果有新的修復(fù)包排霉,還是打開開關(guān)浙垫,嘗試做修復(fù),僅對相同修復(fù)失敗的包進(jìn)行禁止郑诺。