Tinker和Instant Run的并存
Tinker 非代理模式的啟動(dòng)
Tinker的啟動(dòng)流程
加載補(bǔ)丁入口
前面介紹到了加載補(bǔ)丁的地方主要在TinkerLoader的tryLoad方法中当宴,代碼如下:
private void loadTinker() {
try {
//reflect tinker loader, because loaderClass may be define by user!
Class<?> tinkerLoadClass = Class.forName(loaderClassName, false, TinkerApplication.class.getClassLoader());
Method loadMethod = tinkerLoadClass.getMethod(TINKER_LOADER_METHOD, TinkerApplication.class);
Constructor<?> constructor = tinkerLoadClass.getConstructor();
tinkerResultIntent = (Intent) loadMethod.invoke(constructor.newInstance(), this);
} catch (Throwable e) {
//has exception, put exception error code
tinkerResultIntent = new Intent();
ShareIntentUtil.setIntentReturnCode(tinkerResultIntent, ShareConstants.ERROR_LOAD_PATCH_UNKNOWN_EXCEPTION);
tinkerResultIntent.putExtra(INTENT_PATCH_EXCEPTION, e);
}
}
loaderClassName這個(gè)方法是manifest中的真正的application中的創(chuàng)建時(shí)傳遞的類。一般情況是com.tencent.tinker.loader.TinkerLoader這個(gè)類倍阐,通過(guò)反射創(chuàng)建這個(gè)類之后,調(diào)用TINKER_LOADER_METHOD即tryLoad這個(gè)方法课兄。之前我們直接跳過(guò)了tryLoad 這個(gè)方法分析應(yīng)用的啟動(dòng)渺贤,現(xiàn)在分析下tryLoad這個(gè)加載補(bǔ)丁的流程,tryLoad及之后的流程才是tinker的關(guān)鍵所在颅围。
patch文件的檢查
TinkerLoader繼承自抽象類AbstractTinkerLoader皱卓,AbstractTinkerLoader只有一個(gè)抽象方法tryLoad裹芝。TinkerLoader實(shí)現(xiàn)了tryLoad方法,這個(gè)方法中主要調(diào)用了tryLoadPatchFilesInternal娜汁,這個(gè)方法執(zhí)行主要的邏輯嫂易。主要代碼如下:
private void tryLoadPatchFilesInternal(TinkerApplication app, Intent resultIntent) {
final int tinkerFlag = app.getTinkerFlags();// 在RealApplication中傳入的參數(shù)
......省略部分異常檢查代碼,檢查包括是否開(kāi)啟tinker,是否非patch進(jìn)程掐禁,patch文件是否存在等怜械。
//tinker/patch.info
//1 獲取patch.info.有點(diǎn)疑問(wèn),這個(gè)patch.info文件是什么時(shí)候放到這個(gè)目錄下的傅事?文件內(nèi)容是什么缕允?
File patchInfoFile = SharePatchFileUtil.getPatchInfoFile(patchDirectoryPath);
//old = 641e634c5b8f1649c75caf73794acbdf
//new = 2c150d8560334966952678930ba67fa8
File patchInfoLockFile = SharePatchFileUtil.getPatchInfoLockFile(patchDirectoryPath);
//2 eadAndCheckPropertyWithLock有點(diǎn)玄機(jī),lock 應(yīng)該是和同步相關(guān)蹭越,利用文件實(shí)現(xiàn)的同步灼芭?
//讀取patch.info文件中的內(nèi)容
patchInfo = SharePatchInfo.readAndCheckPropertyWithLock(patchInfoFile, patchInfoLockFile);
if (patchInfo == null) {
ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_INFO_CORRUPTED);
return;
}
......
//patch-641e634c
String patchName = SharePatchFileUtil.getPatchVersionDirectory(version);
//tinker/patch.info/patch-641e634c
String patchVersionDirectory = patchDirectoryPath + "/" + patchName;
File patchVersionDirectoryFile = new File(patchVersionDirectory);
//tinker/patch.info/patch-641e634c/patch-641e634c.apk
final String patchVersionFileRelPath = SharePatchFileUtil.getPatchVersionFile(version);
File patchVersionFile = (patchVersionFileRelPath != null ? new File(patchVersionDirectoryFile.getAbsolutePath(), patchVersionFileRelPath) : null);
ShareSecurityCheck securityCheck = new ShareSecurityCheck(app);
// 3 對(duì)patch文件進(jìn)行檢查,包括tinkerId,以及patch中的文件般又。
int returnCode = ShareTinkerInternals.checkTinkerPackage(app, tinkerFlag, patchVersionFile, securityCheck);
resultIntent.putExtra(ShareIntentUtil.INTENT_PATCH_PACKAGE_CONFIG, securityCheck.getPackagePropertiesIfPresent());
final boolean isEnabledForDex = ShareTinkerInternals.isTinkerEnabledForDex(tinkerFlag);
final boolean isEnabledForNativeLib = ShareTinkerInternals.isTinkerEnabledForNativeLib(tinkerFlag);
//check resource
final boolean isEnabledForResource = ShareTinkerInternals.isTinkerEnabledForResource(tinkerFlag);
//only work for art platform oat,because of interpret, refuse 4.4 art oat
//android o use quicken default, we don't need to use interpret mode
boolean isSystemOTA = ShareTinkerInternals.isVmArt()
&& ShareTinkerInternals.isSystemOTA(patchInfo.fingerPrint)
&& Build.VERSION.SDK_INT >= 21 && !ShareTinkerInternals.isAfterAndroidO();
resultIntent.putExtra(ShareIntentUtil.INTENT_PATCH_SYSTEM_OTA, isSystemOTA);
.......
}
tryLoadPatchFilesInternal這個(gè)方法非常繁瑣巍佑,主要是能否進(jìn)行patch的前提進(jìn)行檢查茴迁,包括是否開(kāi)啟tinker功能,是否在非patch進(jìn)程萤衰,是否存在patch文件堕义,以及檢查patch文件是否符合要求等。另外檢查過(guò)程中有個(gè)疑問(wèn),patch.info文件是什么是創(chuàng)建的倦卖? 這個(gè)答案需要我們來(lái)解答洒擦。
接下來(lái)看tryLoadPatchFilesInternal的剩余一部分,代碼如下:
private void tryLoadPatchFilesInternal(TinkerApplication app, Intent resultIntent) {
final boolean isEnabledForDex = ShareTinkerInternals.isTinkerEnabledForDex(tinkerFlag);
// 判斷邏輯中有“com.huawei.ark.app.ArkApplicationInfo”的類名怕膛,貌似是華為的方舟熟嫩,后續(xù)先認(rèn)為isArkHotRuning結(jié)果都是false
final boolean isArkHotRuning = ShareTinkerInternals.isArkHotRuning();
if (!isArkHotRuning && isEnabledForDex) {
//tinker/patch.info/patch-641e634c/dex
boolean dexCheck = TinkerDexLoader.checkComplete(patchVersionDirectory, securityCheck, oatDex, resultIntent);
if (!dexCheck) {
//file not found, do not load patch
Log.w(TAG, "tryLoadPatchFiles:dex check fail");
return;
}
}
final boolean isEnabledForArkHot = ShareTinkerInternals.isTinkerEnabledForArkHot(tinkerFlag);
if (isArkHotRuning && isEnabledForArkHot) {
boolean arkHotCheck = TinkerArkHotLoader.checkComplete(patchVersionDirectory, securityCheck, resultIntent);
if (!arkHotCheck) {
// file not found, do not load patch
Log.w(TAG, "tryLoadPatchFiles:dex check fail");
return;
}
}
final boolean isEnabledForNativeLib = ShareTinkerInternals.isTinkerEnabledForNativeLib(tinkerFlag);
if (isEnabledForNativeLib) {
//tinker/patch.info/patch-641e634c/lib
boolean libCheck = TinkerSoLoader.checkComplete(patchVersionDirectory, securityCheck, resultIntent);
if (!libCheck) {
//file not found, do not load patch
Log.w(TAG, "tryLoadPatchFiles:native lib check fail");
return;
}
}
//check resource
final boolean isEnabledForResource = ShareTinkerInternals.isTinkerEnabledForResource(tinkerFlag);
Log.w(TAG, "tryLoadPatchFiles:isEnabledForResource:" + isEnabledForResource);
if (isEnabledForResource) {
boolean resourceCheck = TinkerResourceLoader.checkComplete(app, patchVersionDirectory, securityCheck, resultIntent);
if (!resourceCheck) {
//file not found, do not load patch
Log.w(TAG, "tryLoadPatchFiles:resource check fail");
return;
}
}
//only work for art platform oat,because of interpret, refuse 4.4 art oat
//android o use quicken default, we don't need to use interpret mode
boolean isSystemOTA = ShareTinkerInternals.isVmArt()
&& ShareTinkerInternals.isSystemOTA(patchInfo.fingerPrint)
&& Build.VERSION.SDK_INT >= 21 && !ShareTinkerInternals.isAfterAndroidO();
resultIntent.putExtra(ShareIntentUtil.INTENT_PATCH_SYSTEM_OTA, isSystemOTA);
//we should first try rewrite patch info file, if there is a error, we can't load jar
if (mainProcess) {
if (versionChanged) {
patchInfo.oldVersion = version;
}
if (oatModeChanged) {
patchInfo.oatDir = oatDex;
// delete interpret odex
// for android o, directory change. Fortunately, we don't need to support android o interpret mode any more
Log.i(TAG, "tryLoadPatchFiles:oatModeChanged, try to delete interpret optimize files");
SharePatchFileUtil.deleteDir(patchVersionDirectory + "/" + ShareConstants.INTERPRET_DEX_OPTIMIZE_PATH);
}
}
if (!checkSafeModeCount(app)) {
resultIntent.putExtra(ShareIntentUtil.INTENT_PATCH_EXCEPTION, new TinkerRuntimeException("checkSafeModeCount fail"));
ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_UNCAUGHT_EXCEPTION);
Log.w(TAG, "tryLoadPatchFiles:checkSafeModeCount fail");
return;
}
//now we can load patch jar
if (!isArkHotRuning && isEnabledForDex) {
boolean loadTinkerJars = TinkerDexLoader.loadTinkerJars(app, patchVersionDirectory, oatDex, resultIntent, isSystemOTA, isProtectedApp);
if (isSystemOTA) {
// update fingerprint after load success
patchInfo.fingerPrint = Build.FINGERPRINT;
patchInfo.oatDir = loadTinkerJars ? ShareConstants.INTERPRET_DEX_OPTIMIZE_PATH : ShareConstants.DEFAULT_DEX_OPTIMIZE_PATH;
// reset to false
oatModeChanged = false;
if (!SharePatchInfo.rewritePatchInfoFileWithLock(patchInfoFile, patchInfo, patchInfoLockFile)) {
ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_REWRITE_PATCH_INFO_FAIL);
Log.w(TAG, "tryLoadPatchFiles:onReWritePatchInfoCorrupted");
return;
}
// update oat dir
resultIntent.putExtra(ShareIntentUtil.INTENT_PATCH_OAT_DIR, patchInfo.oatDir);
}
if (!loadTinkerJars) {
Log.w(TAG, "tryLoadPatchFiles:onPatchLoadDexesFail");
return;
}
}
if (isArkHotRuning && isEnabledForArkHot) {
boolean loadArkHotFixJars = TinkerArkHotLoader.loadTinkerArkHot(app, patchVersionDirectory, resultIntent);
if (!loadArkHotFixJars) {
Log.w(TAG, "tryLoadPatchFiles:onPatchLoadArkApkFail");
return;
}
}
......
}
在tryLoadPatchFilesInternal這個(gè)方法的第二部分分別對(duì)各項(xiàng)標(biāo)志位進(jìn)行賦值褐捻,包括是否支持dex進(jìn)行修復(fù)掸茅,是否支持對(duì)資源文件進(jìn)行修復(fù),是否支持對(duì)native文件進(jìn)行修復(fù)等柠逞。其中還有sSystemOTA判斷昧狮,只要用戶是ART環(huán)境并且做了OTA升級(jí),則在加載dex補(bǔ)丁的時(shí)候板壮,就會(huì)先把最近一次的補(bǔ)丁全部DexFile.loadDex一遍逗鸣。這么做的原因是有些場(chǎng)景做了OTA后,oat的規(guī)則可能發(fā)生變化绰精,在這種情況下去加載上個(gè)系統(tǒng)版本oat過(guò)的dex就會(huì)出現(xiàn)問(wèn)題撒璧。由此可見(jiàn),將東西做好的代價(jià)有點(diǎn)大茬底』Ρ可惟其如此,才有真正的價(jià)值阱表。這里先假設(shè)所有的檢查都是通過(guò)殿如,先看主流程。檢查之后就是開(kāi)始加載的過(guò)程最爬。
開(kāi)始加載
private void tryLoadPatchFilesInternal(TinkerApplication app, Intent resultIntent) {
......
//now we can load patch jar
if (!isArkHotRuning && isEnabledForDex) {
boolean loadTinkerJars = TinkerDexLoader.loadTinkerJars(app, patchVersionDirectory, oatDex, resultIntent, isSystemOTA, isProtectedApp);
if (isSystemOTA) {
// update fingerprint after load success
patchInfo.fingerPrint = Build.FINGERPRINT;
patchInfo.oatDir = loadTinkerJars ? ShareConstants.INTERPRET_DEX_OPTIMIZE_PATH : ShareConstants.DEFAULT_DEX_OPTIMIZE_PATH;
// reset to false
oatModeChanged = false;
if (!SharePatchInfo.rewritePatchInfoFileWithLock(patchInfoFile, patchInfo, patchInfoLockFile)) {
ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_REWRITE_PATCH_INFO_FAIL);
Log.w(TAG, "tryLoadPatchFiles:onReWritePatchInfoCorrupted");
return;
}
// update oat dir
resultIntent.putExtra(ShareIntentUtil.INTENT_PATCH_OAT_DIR, patchInfo.oatDir);
}
if (!loadTinkerJars) {
Log.w(TAG, "tryLoadPatchFiles:onPatchLoadDexesFail");
return;
}
}
if (isArkHotRuning && isEnabledForArkHot) {
boolean loadArkHotFixJars = TinkerArkHotLoader.loadTinkerArkHot(app, patchVersionDirectory, resultIntent);
if (!loadArkHotFixJars) {
Log.w(TAG, "tryLoadPatchFiles:onPatchLoadArkApkFail");
return;
}
}
//now we can load patch resource
if (isEnabledForResource) {
boolean loadTinkerResources = TinkerResourceLoader.loadTinkerResources(app, patchVersionDirectory, resultIntent);
if (!loadTinkerResources) {
Log.w(TAG, "tryLoadPatchFiles:onPatchLoadResourcesFail");
return;
}
}
// Init component hotplug support.
if ((isEnabledForDex || isEnabledForArkHot) && isEnabledForResource) {
ComponentHotplug.install(app, securityCheck);
}
// Before successfully exit, we should update stored version info and kill other process
// to make them load latest patch when we first applied newer one.
if (mainProcess && versionChanged) {
//update old version to new
if (!SharePatchInfo.rewritePatchInfoFileWithLock(patchInfoFile, patchInfo, patchInfoLockFile)) {
ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_REWRITE_PATCH_INFO_FAIL);
Log.w(TAG, "tryLoadPatchFiles:onReWritePatchInfoCorrupted");
return;
}
ShareTinkerInternals.killProcessExceptMain(app);
}
//all is ok!
ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_OK);
Log.i(TAG, "tryLoadPatchFiles: load end, ok!");
return;
}
加載補(bǔ)丁的方法因?yàn)槎鄠€(gè)參數(shù)而有所區(qū)別涉馁,這里我們重點(diǎn)看下loadTinkerJars()和loadTinkerResources這兩個(gè)方法。最后加載成功后將patch的版本更新到文件爱致,然后將其他進(jìn)程都?xì)⒌艨舅停詈髮atch成功的信息返回。如果tinker更新成功之后糠悯,下次啟動(dòng)是否還需要進(jìn)行patch的過(guò)程呢帮坚?按道理是不需要的,此處更新了版本信息應(yīng)該是出于這個(gè)目的互艾。熱更的兩個(gè)點(diǎn)一個(gè)是代碼更新试和,一個(gè)是資源更新,分別對(duì)應(yīng)于loadTinkerJars和loadTinkerResources這兩個(gè)方法纫普,接下來(lái)我們分析這個(gè)兩個(gè)過(guò)程阅悍。
代碼加載
在loadTinkerJars方法里面調(diào)用了一些關(guān)于patch文件的信息,這些信息是在checkComplete中準(zhǔn)備好的,所以先看下checkComplete這個(gè)方法节视,代碼如下:
public static boolean checkComplete(String directory, ShareSecurityCheck securityCheck, String oatDir, Intent intentResult) {
String meta = securityCheck.getMetaContentMap().get(DEX_MEAT_FILE);
//not found dex
if (meta == null) {
return true;
}
LOAD_DEX_LIST.clear();
classNDexInfo.clear();
ArrayList<ShareDexDiffPatchInfo> allDexInfo = new ArrayList<>();
// 解析patch文件中assets/dex_meta.txt的內(nèi)容
ShareDexDiffPatchInfo.parseDexDiffPatchInfo(meta, allDexInfo);
if (allDexInfo.isEmpty()) {
return true;
}
HashMap<String, String> dexes = new HashMap<>();
ShareDexDiffPatchInfo testInfo = null;
for (ShareDexDiffPatchInfo info : allDexInfo) {
//for dalvik, ignore art support dex
if (isJustArtSupportDex(info)) {
continue;
}
if (!ShareDexDiffPatchInfo.checkDexDiffPatchInfo(info)) {
intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_PACKAGE_PATCH_CHECK, ShareConstants.ERROR_PACKAGE_CHECK_DEX_META_CORRUPTED);
ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_PACKAGE_CHECK_FAIL);
return false;
}
if (isVmArt && info.rawName.startsWith(ShareConstants.TEST_DEX_NAME)) {
testInfo = info;
} else if (isVmArt && ShareConstants.CLASS_N_PATTERN.matcher(info.realName).matches()) {
classNDexInfo.add(info);
} else {
dexes.put(info.realName, getInfoMd5(info));
LOAD_DEX_LIST.add(info);
}
}
if (isVmArt
&& (testInfo != null || !classNDexInfo.isEmpty())) {
if (testInfo != null) {
classNDexInfo.add(ShareTinkerInternals.changeTestDexToClassN(testInfo, classNDexInfo.size() + 1));
}
dexes.put(ShareConstants.CLASS_N_APK_NAME, "");
}
...... 省略部分代碼
//if is ok, add to result intent
intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_DEXES_PATH, dexes);
return true;
}
checkComplete中主要檢查patch中assets/dex_meta.txt文件的內(nèi)容拳锚,這個(gè)文件包含了patch中dex的名字,md5,crc校驗(yàn)等信息寻行。這個(gè)文件是在patch打包時(shí)生成霍掺。生成時(shí)機(jī)是在配置完成后,大概流程如下:
TinkerPatchPlugin.apply->Runner.inkerPatch->ApkDecoder.patch()->ApkFilesVisitor.visitFile()->UniqueDexDiffDecoder.patch()->DexDiffDecoder.onAllPatchesEnd()
patch文件如何生成寡痰,此處先按照上面這樣理解抗楔,后續(xù)再詳細(xì)分析。這里我們先回過(guò)來(lái)頭接著分析TinkerDexLoader的loadTinkerJars方法拦坠。
// private static File testOptDexFile;
private static HashSet<ShareDexDiffPatchInfo> classNDexInfo = new HashSet<>();
// classNDexInfo這個(gè)容器中的內(nèi)容是上面checkComplete中添加進(jìn)入的连躏。
public static boolean loadTinkerJars(final TinkerApplication application, String directory, String oatDir, Intent intentResult, boolean isSystemOTA, boolean isProtectedApp) {
if (LOAD_DEX_LIST.isEmpty() && classNDexInfo.isEmpty()) {
Log.w(TAG, "there is no dex to load");
return true;
}
BaseDexClassLoader classLoader = (BaseDexClassLoader) TinkerDexLoader.class.getClassLoader();
if (classLoader != null) {
Log.i(TAG, "classloader: " + classLoader.toString());
} else {
Log.e(TAG, "classloader is null");
ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_CLASSLOADER_NULL);
return false;
}
String dexPath = directory + "/" + DEX_PATH + "/";
ArrayList<File> legalFiles = new ArrayList<>();
for (ShareDexDiffPatchInfo info : LOAD_DEX_LIST) {
//for dalvik, ignore art support dex
if (isJustArtSupportDex(info)) {
continue;
}
String path = dexPath + info.realName;
File file = new File(path);
// isTinkerLoadVerifyFlag這個(gè)標(biāo)志在項(xiàng)目的applictaion中配置,跳過(guò)可以加快速度
if (application.isTinkerLoadVerifyFlag()) {
long start = System.currentTimeMillis();
String checkMd5 = getInfoMd5(info);
if (!SharePatchFileUtil.verifyDexFileMd5(file, checkMd5)) {
//it is good to delete the mismatch file
ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_MD5_MISMATCH);
intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_MISMATCH_DEX_PATH,
file.getAbsolutePath());
return false;
}
Log.i(TAG, "verify dex file:" + file.getPath() + " md5, use time: " + (System.currentTimeMillis() - start));
}
// 將patch文件中的dex信息加入到legalFiles中
legalFiles.add(file);
}
// verify merge classN.apk
if (isVmArt && !classNDexInfo.isEmpty()) {
File classNFile = new File(dexPath + ShareConstants.CLASS_N_APK_NAME);
long start = System.currentTimeMillis();
// 跳過(guò)對(duì)dex進(jìn)行md5校驗(yàn)的步驟贞滨,一般設(shè)置為跳過(guò)入热,可以加快速度
legalFiles.add(classNFile);
}
File optimizeDir = new File(directory + "/" + oatDir);
// 省略isSystemOTA為真的情況
try {
SystemClassLoaderAdder.installDexes(application, classLoader, optimizeDir, legalFiles, isProtectedApp);
} catch (Throwable e) {
Log.e(TAG, "install dexes failed");
intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_EXCEPTION, e);
ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_LOAD_EXCEPTION);
return false;
}
return true;
}
loadTinkerJars這個(gè)方法也是將patch中的信息處理加工,最后調(diào)用SystemClassLoaderAdder.installDexes方法開(kāi)始加載patch中的dex文件晓铆。代碼如下:
public static void installDexes(Application application, BaseDexClassLoader loader, File dexOptDir, List<File> files, boolean isProtectedApp)
throws Throwable {
Log.i(TAG, "installDexes dexOptDir: " + dexOptDir.getAbsolutePath() + ", dex size:" + files.size());
if (!files.isEmpty()) {
//將文件排序
files = createSortedAdditionalPathEntries(files);
ClassLoader classLoader = loader;
if (Build.VERSION.SDK_INT >= 24 && !isProtectedApp) {
classLoader = AndroidNClassLoader.inject(loader, application);
}
//because in dalvik, if inner class is not the same classloader with it wrapper class.
//it won't fail at dex2opt
if (Build.VERSION.SDK_INT >= 23) {
V23.install(classLoader, files, dexOptDir);
} else if (Build.VERSION.SDK_INT >= 19) {
V19.install(classLoader, files, dexOptDir);
} else if (Build.VERSION.SDK_INT >= 14) {
V14.install(classLoader, files, dexOptDir);
} else {
V4.install(classLoader, files, dexOptDir);
}
//install done
sPatchDexCount = files.size();
Log.i(TAG, "after loaded classloader: " + classLoader + ", dex size:" + sPatchDexCount);
if (!checkDexInstall(classLoader)) {
//reset patch dex
SystemClassLoaderAdder.uninstallPatchDex(classLoader);
throw new TinkerRuntimeException(ShareConstants.CHECK_DEX_INSTALL_FAIL);
}
}
}
經(jīng)過(guò)一系列的校驗(yàn)勺良,終于到了加載dex文件的地方了。如果是在24以上的機(jī)器骄噪,就調(diào)用AndroidNClassLoader的inject對(duì)classloader進(jìn)行處理尚困,為什么處理的原因在這里Android N混合編譯與對(duì)熱補(bǔ)丁影響解析。inject的過(guò)程有點(diǎn)復(fù)雜链蕊,我們先忽略事甜。接下來(lái)按照系統(tǒng)版本分為了四種方式加載補(bǔ)丁,這么做的原因的加載的過(guò)程有變化滔韵。各個(gè)歷史版本的代碼可以在這里來(lái)看AOSPXRef逻谦,非常方便。我們先分析下V23中的install過(guò)程陪蜻。代碼如下:
private static void install(ClassLoader loader, List<File> additionalClassPathEntries,
File optimizedDirectory)
throws IllegalArgumentException, IllegalAccessException,
NoSuchFieldException, InvocationTargetException, NoSuchMethodException, IOException {
/* The patched class loader is expected to be a descendant of
* dalvik.system.BaseDexClassLoader. We modify its
* dalvik.system.DexPathList pathList field to append additional DEX
* file entries.
*/
Field pathListField = ShareReflectUtil.findField(loader, "pathList");
Object dexPathList = pathListField.get(loader);
ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
ShareReflectUtil.expandFieldArray(dexPathList, "dexElements", makePathElements(dexPathList,
new ArrayList<File>(additionalClassPathEntries), optimizedDirectory,
suppressedExceptions));
if (suppressedExceptions.size() > 0) {
for (IOException e : suppressedExceptions) {
Log.w(TAG, "Exception in makePathElement", e);
throw e;
}
}
}
通過(guò)反射拿到BaseDexClassLoader中的pathList邦马,然后調(diào)用expandFieldArray更改這個(gè)成員變量中dexElements,用makePathElements組裝好的Elements數(shù)組替換原來(lái)的dexElements的值宴卖。類的加載都是由BaseDexClassLoader中的findclass來(lái)查找后加載滋将,而findclass就是從pathList這個(gè)數(shù)組中查找的。如果擴(kuò)展了原來(lái)BaseDexClassLoader的pathList變量症昏,從其中加入patch中的class耕渴,那就能替換原來(lái)出問(wèn)題的class。所以我們想怎樣讓pathList這個(gè)變量中加入我們前面patch文件中dex的class齿兔。DexPathList這個(gè)類中有個(gè) Element數(shù)組dexElements,我們可以通過(guò)反射的方式將Element數(shù)組更改,按照類加載器的原理分苇,加載到想要的類后就會(huì)直接返回添诉,因此如果我們只要將patch中dex插入到Element數(shù)組前面。之后加載特定類就會(huì)從patch中的dex加載修復(fù)后的類医寿,而不會(huì)從原有Element數(shù)組加載原來(lái)的類栏赴。插隊(duì)的操作是在expandFieldArray這個(gè)方法中完成,代碼如下:
/**
* Replace the value of a field containing a non null array, by a new array containing the
* elements of the original array plus the elements of extraElements.
*
* @param instance the instance whose field is to be modified.
* @param fieldName the field to modify.
* @param extraElements elements to append at the end of the array.
*/
public static void expandFieldArray(Object instance, String fieldName, Object[] extraElements)
throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
Field jlrField = findField(instance, fieldName);
Object[] original = (Object[]) jlrField.get(instance);
Object[] combined = (Object[]) Array.newInstance(original.getClass().getComponentType(), original.length + extraElements.length);
// NOTE: changed to copy extraElements first, for patch load first
System.arraycopy(extraElements, 0, combined, 0, extraElements.length);
System.arraycopy(original, 0, combined, extraElements.length, original.length);
jlrField.set(instance, combined);
}
通過(guò)數(shù)組拷貝的方式將patch中dex生成的Element插入到數(shù)組的前面靖秩,而patch中的dex如何轉(zhuǎn)為成Element呢须眷?答案在makePathElements方法中,代碼如下:
/**
* A wrapper around
* {@code private static final dalvik.system.DexPathList#makePathElements}.
*/
private static Object[] makePathElements(
Object dexPathList, ArrayList<File> files, File optimizedDirectory,
ArrayList<IOException> suppressedExceptions)
throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
Method makePathElements;
try {
makePathElements = ShareReflectUtil.findMethod(dexPathList, "makePathElements", List.class, File.class,
List.class);
} catch (NoSuchMethodException e) {
Log.e(TAG, "NoSuchMethodException: makePathElements(List,File,List) failure");
try {
makePathElements = ShareReflectUtil.findMethod(dexPathList, "makePathElements", ArrayList.class, File.class, ArrayList.class);
} catch (NoSuchMethodException e1) {
Log.e(TAG, "NoSuchMethodException: makeDexElements(ArrayList,File,ArrayList) failure");
try {
Log.e(TAG, "NoSuchMethodException: try use v19 instead");
return V19.makeDexElements(dexPathList, files, optimizedDirectory, suppressedExceptions);
} catch (NoSuchMethodException e2) {
Log.e(TAG, "NoSuchMethodException: makeDexElements(List,File,List) failure");
throw e2;
}
}
}
return (Object[]) makePathElements.invoke(dexPathList, files, optimizedDirectory, suppressedExceptions);
}
這個(gè)方法也是通過(guò)反射調(diào)用DexPathList中的makePathElements方法沟突,最終將dex轉(zhuǎn)變?yōu)镋lelments花颗。下面看下makePathElements方法,代碼如下:
private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
List<IOException> suppressedExceptions, ClassLoader loader, boolean isTrusted) {
Element[] elements = new Element[files.size()];
int elementsPos = 0;
/*
* Open all files and load the (direct or contained) dex files up front.
*/
for (File file : files) {
if (file.isDirectory()) {
// We support directories for looking up resources. Looking up resources in
// directories is useful for running libcore tests.
elements[elementsPos++] = new Element(file);
} else if (file.isFile()) {
String name = file.getName();
DexFile dex = null;
if (name.endsWith(DEX_SUFFIX)) {
// Raw dex file (not inside a zip/jar).
try {
dex = loadDexFile(file, optimizedDirectory, loader, elements);
if (dex != null) {
elements[elementsPos++] = new Element(dex, null);
}
} catch (IOException suppressed) {
System.logE("Unable to load dex file: " + file, suppressed);
suppressedExceptions.add(suppressed);
}
} else {
try {
dex = loadDexFile(file, optimizedDirectory, loader, elements);
} catch (IOException suppressed) {
/*
* IOException might get thrown "legitimately" by the DexFile constructor if
* the zip file turns out to be resource-only (that is, no classes.dex file
* in it).
* Let dex == null and hang on to the exception to add to the tea-leaves for
* when findClass returns null.
*/
suppressedExceptions.add(suppressed);
}
if (dex == null) {
elements[elementsPos++] = new Element(file);
} else {
elements[elementsPos++] = new Element(dex, file);
}
}
if (dex != null && isTrusted) {
dex.setTrusted();
}
} else {
System.logW("ClassLoader referenced unknown path: " + file);
}
}
if (elementsPos != elements.length) {
elements = Arrays.copyOf(elements, elementsPos);
}
return elements;
}
在這個(gè)方法中通過(guò)loadDexFile 將dex轉(zhuǎn)化為Elelments惠拭。然后將Element返回扩劝。至此我們分析了dex的校驗(yàn),加載职辅,以及最后的插入到dexPathList中棒呛。其他版本最后的install方法也比較類似,其他流程的講解參考Android 熱修復(fù)方案Tinker(三) Dex補(bǔ)丁加載.這篇文章講的比較詳細(xì)域携,可以結(jié)合不同版本的代碼學(xué)習(xí)下簇秒。
參考鏈接
感謝微信的開(kāi)源,讓tinker成為了小廠的基礎(chǔ)設(shè)置秀鞭,讓很多應(yīng)用開(kāi)發(fā)者擺脫了熱更的諸多麻煩趋观。
感謝先行者的詳細(xì)分析和無(wú)私分享。功力尚淺气筋,請(qǐng)不吝指教拆内。