Tinker接入及原始碼分析(二)
該系列文章分析基於 Tinker1.7.6 版本
Tinker專案地址:https://github.com/Tencent/tinker
Tinker接入及原始碼分析(一):簡單介紹以及如何接入
Tinker接入及原始碼分析(二):載入補丁原始碼分析
Tinker接入及原始碼分析(三):合成補丁原始碼分析
上篇文章簡單的介紹了Tinker的使用:《Tinker接入及原始碼分析(一)》
再次推薦大家閱讀官方wiki:https://github.com/Tencent/tinker/wiki
上篇文章也提及了Tinker的熱修復原理,這裡再重複一遍:
先簡單的說一下Tinker框架熱修復的原理,主要是dex檔案的修復,不再涉及資原始檔以及so檔案的修復,通過對比原dex檔案(存在bug)與現dex檔案(bug已修復)生成差異包,生成的差異包作為補丁包下發給客戶端,客戶端做一系列校驗之後,將下發的差異包與本應用的dex檔案合併成成全量的dex檔案,並進行opt優化,在應用重啟的時候,會在TinkerApplication中載入優化過的全量dex檔案,載入過程與QQ空間熱修復方案類似,將dex檔案插入到DexPathList 中 dexElements的前面。
下面先從簡單的入手,分析補丁檔案的載入過程,載入之前我們需要明確目前補丁檔案已經push到手機,並且通過校驗,使用dexDiff演算法合成全量補丁並儲存到應用data目錄下 /data/data/package_name/tinker/
還記得TinkerApplication 和 DefaultApplicationLike 嗎?我們就從應用的入口開始分析:
public class SampleApplication extends TinkerApplication {
public SampleApplication() {
super(
//tinkerFlags, tinker支援的型別,dex,library,還是全部都支援!
ShareConstants.TINKER_ENABLE_ALL,
//ApplicationLike的實現類,只能傳遞字串
"tinker.sample.android.app.SampleApplicationLike",
//Tinker的載入器,一般來說用預設的即可
"com.tencent.tinker.loader.TinkerLoader",
//tinkerLoadVerifyFlag, 執行載入時是否校驗dex與,ib與res的Md5
false);
}
}
TinkerApplication.:
/**
* Hook for sub-classes to run logic after the {@link Application#attachBaseContext} has been
* called but before the delegate is created. Implementors should be very careful what they do
* here since {@link android.app.Application#onCreate} will not have yet been called.
*/
private void onBaseContextAttached(Context base) {
applicationStartElapsedTime = SystemClock.elapsedRealtime();
applicationStartMillisTime = System.currentTimeMillis();
loadTinker();
ensureDelegate();
try {
Method method = ShareReflectUtil.findMethod(delegate, "onBaseContextAttached", Context.class);
method.invoke(delegate, base);
} catch (Throwable t) {
throw new TinkerRuntimeException("onBaseContextAttached method not found", t);
}
//reset save mode
if (useSafeMode) {
String processName = ShareTinkerInternals.getProcessName(this);
String preferName = ShareConstants.TINKER_OWN_PREFERENCE_CONFIG + processName;
SharedPreferences sp = getSharedPreferences(preferName, Context.MODE_PRIVATE);
sp.edit().putInt(ShareConstants.TINKER_SAFE_MODE_COUNT, 0).commit();
}
}
其中loadTinker()方法是通過反射初始化我們之前傳過來的com.tencent.tinker.loader.TinkerLoader,並且呼叫它的tryLoad方法,該方法也是載入補丁包的關鍵所在,我們先放一放,繼續往下看。
ensureDelegate() 方法最終會呼叫createDelegate(),createDelegate()是通過反射初始化化我們傳過來的tinker.sample.android.app.SampleApplicationLike,最後會將初始化好的物件賦值給delegate。
private Object createDelegate() {
try {
// Use reflection to create the delegate so it doesn't need to go into the primary dex.
// And we can also patch it
Class<?> delegateClass = Class.forName(delegateClassName, false, getClassLoader());
Constructor<?> constructor = delegateClass.getConstructor(Application.class, int.class, boolean.class, long.class, long.class,
Intent.class, Resources[].class, ClassLoader[].class, AssetManager[].class);
return constructor.newInstance(this, tinkerFlags, tinkerLoadVerifyFlag,
applicationStartElapsedTime, applicationStartMillisTime,
tinkerResultIntent, resources, classLoader, assetManager);
} catch (Throwable e) {
throw new TinkerRuntimeException("createDelegate failed", e);
}
}
接下來便是在TinkerApplication各個生命週期方法中通過反射呼叫代理的ApplicationLike中對應的生命週期方法。比如onBaseContextAttached中的:
try {
Method method = ShareReflectUtil.findMethod(delegate, "onBaseContextAttached", Context.class);
method.invoke(delegate, base);
} catch (Throwable t) {
throw new TinkerRuntimeException("onBaseContextAttached method not found", t);
}
以及其他方法:
@Override
public void onCreate() {
super.onCreate();
ensureDelegate();
delegateMethod("onCreate");
}
@Override
public void onTerminate() {
super.onTerminate();
delegateMethod("onTerminate");
}
@Override
public void onLowMemory() {
super.onLowMemory();
delegateMethod("onLowMemory");
}
下面主要來看一下載入補丁的方法loadTinker():
private void loadTinker() {
//disable tinker, not need to install
if (tinkerFlags == TINKER_DISABLE) {
return;
}
tinkerResultIntent = new Intent();
try {
//reflect tinker loader, because loaderClass may be define by user!
Class<?> tinkerLoadClass = Class.forName(loaderClassName, false, getClassLoader());
Method loadMethod = tinkerLoadClass.getMethod(TINKER_LOADER_METHOD, TinkerApplication.class, int.class, boolean.class);
Constructor<?> constructor = tinkerLoadClass.getConstructor();
tinkerResultIntent = (Intent) loadMethod.invoke(constructor.newInstance(), this, tinkerFlags, tinkerLoadVerifyFlag);
} catch (Throwable e) {
//has exception, put exception error code
ShareIntentUtil.setIntentReturnCode(tinkerResultIntent, ShareConstants.ERROR_LOAD_PATCH_UNKNOWN_EXCEPTION);
tinkerResultIntent.putExtra(INTENT_PATCH_EXCEPTION, e);
}
}
其中loaderClassName是我們傳過來的:com.tencent.tinker.loader.TinkerLoader,最終會呼叫該類的tryLoad方法,下面我們轉到TinkerLoader,看tryLoad方法:
/**
* only main process can handle patch version change or incomplete
*/
@Override
public Intent tryLoad(TinkerApplication app, int tinkerFlag, boolean tinkerLoadVerifyFlag) {
Intent resultIntent = new Intent();
long begin = SystemClock.elapsedRealtime();
tryLoadPatchFilesInternal(app, tinkerFlag, tinkerLoadVerifyFlag, resultIntent);
long cost = SystemClock.elapsedRealtime() - begin;
ShareIntentUtil.setIntentPatchCostTime(resultIntent, cost);
return resultIntent;
}
呼叫tryLoadPatchFilesInternal,並計算耗時:
private void tryLoadPatchFilesInternal(TinkerApplication app, int tinkerFlag, boolean tinkerLoadVerifyFlag, Intent resultIntent) {
if (!ShareTinkerInternals.isTinkerEnabled(tinkerFlag)) {
ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_DISABLE);
return;
}
//tinker
File patchDirectoryFile = SharePatchFileUtil.getPatchDirectory(app);
if (patchDirectoryFile == null) {
Log.w(TAG, "tryLoadPatchFiles:getPatchDirectory == null");
//treat as not exist
ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_DIRECTORY_NOT_EXIST);
return;
}
String patchDirectoryPath = patchDirectoryFile.getAbsolutePath();
//check patch directory whether exist
if (!patchDirectoryFile.exists()) {
//...
}
//tinker/patch.info
File patchInfoFile = SharePatchFileUtil.getPatchInfoFile(patchDirectoryPath);
//check patch info file whether exist
if (!patchInfoFile.exists()) {
//...
}
//old = 641e634c5b8f1649c75caf73794acbdf
//new = 2c150d8560334966952678930ba67fa8
File patchInfoLockFile = SharePatchFileUtil.getPatchInfoLockFile(patchDirectoryPath);
patchInfo = SharePatchInfo.readAndCheckPropertyWithLock(patchInfoFile, patchInfoLockFile);
if (patchInfo == null) {
ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_INFO_CORRUPTED);
return;
}
String oldVersion = patchInfo.oldVersion;
String newVersion = patchInfo.newVersion;
if (oldVersion == null || newVersion == null) {
//it is nice to clean patch
Log.w(TAG, "tryLoadPatchFiles:onPatchInfoCorrupted");
ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_INFO_CORRUPTED);
return;
}
resultIntent.putExtra(ShareIntentUtil.INTENT_PATCH_OLD_VERSION, oldVersion);
resultIntent.putExtra(ShareIntentUtil.INTENT_PATCH_NEW_VERSION, newVersion);
boolean mainProcess = ShareTinkerInternals.isInMainProcess(app);
boolean versionChanged = !(oldVersion.equals(newVersion));
String version = oldVersion;
if (versionChanged && mainProcess) {
version = newVersion;
}
if (ShareTinkerInternals.isNullOrNil(version)) {
Log.w(TAG, "tryLoadPatchFiles:version is blank, wait main process to restart");
ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_INFO_BLANK);
return;
}
//patch-641e634c
String patchName = SharePatchFileUtil.getPatchVersionDirectory(version);
if (patchName == null) {
Log.w(TAG, "tryLoadPatchFiles:patchName is null");
//we may delete patch info file
ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_VERSION_DIRECTORY_NOT_EXIST);
return;
}
//tinker/patch.info/patch-641e634c
String patchVersionDirectory = patchDirectoryPath + "/" + patchName;
File patchVersionDirectoryFile = new File(patchVersionDirectory);
if (!patchVersionDirectoryFile.exists()) {
Log.w(TAG, "tryLoadPatchFiles:onPatchVersionDirectoryNotFound");
//we may delete patch info file
ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_VERSION_DIRECTORY_NOT_EXIST);
return;
}
//tinker/patch.info/patch-641e634c/patch-641e634c.apk
File patchVersionFile = new File(patchVersionDirectoryFile.getAbsolutePath(), SharePatchFileUtil.getPatchVersionFile(version));
if (!patchVersionFile.exists()) {
Log.w(TAG, "tryLoadPatchFiles:onPatchVersionFileNotFound");
//we may delete patch info file
ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_VERSION_FILE_NOT_EXIST);
return;
}
ShareSecurityCheck securityCheck = new ShareSecurityCheck(app);
int returnCode = ShareTinkerInternals.checkTinkerPackage(app, tinkerFlag, patchVersionFile, securityCheck);
if (returnCode != ShareConstants.ERROR_PACKAGE_CHECK_OK) {
Log.w(TAG, "tryLoadPatchFiles:checkTinkerPackage");
resultIntent.putExtra(ShareIntentUtil.INTENT_PATCH_PACKAGE_PATCH_CHECK, returnCode);
ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_PACKAGE_CHECK_FAIL);
return;
}
resultIntent.putExtra(ShareIntentUtil.INTENT_PATCH_PACKAGE_CONFIG, securityCheck.getPackagePropertiesIfPresent());
final boolean isEnabledForDex = ShareTinkerInternals.isTinkerEnabledForDex(tinkerFlag);
if (isEnabledForDex) {
//tinker/patch.info/patch-641e634c/dex
boolean dexCheck = TinkerDexLoader.checkComplete(patchVersionDirectory, securityCheck, resultIntent);
if (!dexCheck) {
//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
//...
}
//check resource
final boolean isEnabledForResource = ShareTinkerInternals.isTinkerEnabledForResource(tinkerFlag);
Log.w(TAG, "tryLoadPatchFiles:isEnabledForResource:" + isEnabledForResource);
if (isEnabledForResource) {
//...
}
//only work for art platform oat
boolean isSystemOTA = ShareTinkerInternals.isVmArt() && ShareTinkerInternals.isSystemOTA(patchInfo.fingerPrint);
//we should first try rewrite patch info file, if there is a error, we can't load jar
if (isSystemOTA
|| (mainProcess && versionChanged)) {
//...
}
if (!checkSafeModeCount(app)) {
//...
return;
}
//now we can load patch jar
if (isEnabledForDex) {
boolean loadTinkerJars = TinkerDexLoader.loadTinkerJars(app, tinkerLoadVerifyFlag, patchVersionDirectory, resultIntent, isSystemOTA);
if (!loadTinkerJars) {
Log.w(TAG, "tryLoadPatchFiles:onPatchLoadDexesFail");
return;
}
}
//now we can load patch resource
if (isEnabledForResource) {
//...
}
//all is ok!
ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_OK);
Log.i(TAG, "tryLoadPatchFiles: load end, ok!");
return;
}
以上程式碼省略了部分判斷補丁檔案是否存在,是否有效,以及載入補丁資原始檔的方法,主要檢查補丁資訊中的資料是否有效,校驗補丁簽名以及tinkerId與基準包是否一致。在校驗簽名時,為了加速校驗速度,Tinker只校驗 *_meta.txt檔案,然後再根據meta檔案中的md5校驗其他檔案。最後呼叫
TinkerDexLoader.loadTinkerJars(app, tinkerLoadVerifyFlag, patchVersionDirectory, resultIntent, isSystemOTA);
開始載入補丁檔案:
/**
* Load tinker JARs and add them to
* the Application ClassLoader.
*
* @param application The application.
*/
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public static boolean loadTinkerJars(Application application, boolean tinkerLoadVerifyFlag, String directory, Intent intentResult, boolean isSystemOTA) {
if (dexList.isEmpty()) {
Log.w(TAG, "there is no dex to load");
return true;
}
PathClassLoader classLoader = (PathClassLoader) 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 + "/";
File optimizeDir = new File(directory + "/" + DEX_OPTIMIZE_PATH);
// Log.i(TAG, "loadTinkerJars: dex path: " + dexPath);
// Log.i(TAG, "loadTinkerJars: opt path: " + optimizeDir.getAbsolutePath());
ArrayList<File> legalFiles = new ArrayList<>();
final boolean isArtPlatForm = ShareTinkerInternals.isVmArt();
for (ShareDexDiffPatchInfo info : dexList) {
//for dalvik, ignore art support dex
if (isJustArtSupportDex(info)) {
continue;
}
String path = dexPath + info.realName;
File file = new File(path);
if (tinkerLoadVerifyFlag) {
//...校驗
}
legalFiles.add(file);
}
if (isSystemOTA) {
parallelOTAResult = true;
parallelOTAThrowable = null;
Log.w(TAG, "systemOTA, try parallel oat dexes!!!!!");
TinkerParallelDexOptimizer.optimizeAll(
legalFiles, optimizeDir,
new TinkerParallelDexOptimizer.ResultCallback() {
long start;
@Override
public void onStart(File dexFile, File optimizedDir) {
start = System.currentTimeMillis();
Log.i(TAG, "start to optimize dex:" + dexFile.getPath());
}
@Override
public void onSuccess(File dexFile, File optimizedDir) {
// Do nothing.
Log.i(TAG, "success to optimize dex " + dexFile.getPath() + "use time " + (System.currentTimeMillis() - start));
}
@Override
public void onFailed(File dexFile, File optimizedDir, Throwable thr) {
parallelOTAResult = false;
parallelOTAThrowable = thr;
Log.i(TAG, "fail to optimize dex " + dexFile.getPath() + "use time " + (System.currentTimeMillis() - start));
}
}
);
if (!parallelOTAResult) {
Log.e(TAG, "parallel oat dexes failed");
intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_EXCEPTION, parallelOTAThrowable);
ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_PARALLEL_DEX_OPT_EXCEPTION);
return false;
}
}
try {
SystemClassLoaderAdder.installDexes(application, classLoader, optimizeDir, legalFiles);
} catch (Throwable e) {
Log.e(TAG, "install dexes failed");
// e.printStackTrace();
intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_EXCEPTION, e);
ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_LOAD_EXCEPTION);
return false;
}
return true;
}
以上程式碼根據傳過來的tinkerLoadVerifyFlag選項控制是否每次載入都要驗證dex的md5值,一般來說不需要,預設也是false,會節省載入時間。
然後根據傳過來的isSystemOTA來決定是否OTA((Ahead-Of—Time 提前編譯)優化
最後呼叫
SystemClassLoaderAdder.installDexes(application, classLoader, optimizeDir, legalFiles)
載入dex檔案
@SuppressLint("NewApi")
public static void installDexes(Application application, PathClassLoader loader, File dexOptDir, List<File> files)
throws Throwable {
if (!files.isEmpty()) {
ClassLoader classLoader = loader;
if (Build.VERSION.SDK_INT >= 24) {
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);
}
}
}
這裡就是根據不同的系統版本把dex插到dexElements的前面,其中比較特殊的是安卓7.0,在Dalvik虛擬機器中是通過JIT,在執行時將熱程式碼編譯成機器碼,可以提高下次執行到這段程式碼的速度,ART虛擬機器改變了這種方式,改為OTA,一開始安裝的時候就會將位元組碼編譯成機器碼,提高執行效率,但帶來了2個問題,一個是安裝時間過長,佔用體積過大;
所以在Android N上改變了這種激進的編譯方式,改為混合編譯,混合編譯執行主要指AOT編譯,解釋執行與JIT編譯。簡單來說,在應用執行時分析執行過的程式碼以及“熱程式碼”,並將配置儲存下來。在裝置空閒與充電時,ART僅僅編譯這份配置中的“熱程式碼”。
無論是使用插入pathlist還是parent classloader的方式,若補丁修改的class已經存在與app image,它們都是無法通過熱補丁更新的。它們在啟動app時已經加入到PathClassloader的ClassTable中,系統在查詢類時會直接使用base.apk中的class
假設base.art檔案在補丁前已經存在,這裡存在三種情況:
- 補丁修改的類都不app image中;這種情況是最理想的,此時補丁機制依然有效;
- 補丁修改的類部分在app image中;這種情況我們只能更新一部分的類,此時是最危險的。一部分類是新的,一部分類是舊的,app可能會出現地址錯亂而出現crash。
- 補丁修改的類全部在app image中;這種情況只是造成補丁不生效,app並不會因此造成crash。
Tinker的解決方案是,完全廢棄掉PathClassloader,而採用一個新建Classloader來載入後續的所有類,即可達到將cache無用化的效果。基本原理我們清楚了,讓我們來看下程式碼吧。
if (Build.VERSION.SDK_INT >= 24) {
classLoader = AndroidNClassLoader.inject(loader, application);
}
以上關於AndroidN的內容可以參考:Android N混合編譯與對熱補丁影響解析
繼續往下看具體的載入過程,這裡就看一個V14的實現吧:
/**
* Installer for platform versions 14, 15, 16, 17 and 18.
*/
private static final class V14 {
private static void install(ClassLoader loader, List<File> additionalClassPathEntries,
File optimizedDirectory)
throws IllegalArgumentException, IllegalAccessException,
NoSuchFieldException, InvocationTargetException, NoSuchMethodException {
/* 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);
ShareReflectUtil.expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList,
new ArrayList<File>(additionalClassPathEntries), optimizedDirectory));
}
/**
* A wrapper around
* {@code private static final dalvik.system.DexPathList#makeDexElements}.
*/
private static Object[] makeDexElements(
Object dexPathList, ArrayList<File> files, File optimizedDirectory)
throws IllegalAccessException, InvocationTargetException,
NoSuchMethodException {
Method makeDexElements =
ShareReflectUtil.findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class);
return (Object[]) makeDexElements.invoke(dexPathList, files, optimizedDirectory);
}
}
通過反射拿到BaseDexClassLoader的pathList,然後通過反射呼叫PathList的makeDexElements傳進去的引數分別是補丁dexList和優化過的opt目錄,在Tinker中是dex補丁目錄的同級目錄odex/。
看一下下面這個方法,是將生成的dexElements插入到原先dexElements的前面。
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);
}
首先會拿到原始的陣列:
Object[] original = (Object[]) jlrField.get(instance);
再生成一個長度為original.length + extraElements.length的新陣列,
Object[] combined = (Object[]) Array.newInstance(original.getClass().getComponentType(), original.length + extraElements.length);
然後先把傳進來的extraElements拷貝到新陣列中,再把原始的陣列拷貝進來,這裡的位置很重要,必須得得將載入的dex檔案列表拷貝到陣列前面,再拷貝原先的陣列放在陣列的後面;否則補丁將不會生效。
System.arraycopy(extraElements, 0, combined, 0, extraElements.length);
System.arraycopy(original, 0, combined, extraElements.length, original.length);
最後將新陣列設定進instance中。
jlrField.set(instance, combined);
到此為止載入補丁dex檔案的過程就結束了。
至於為什麼新的補丁檔案載入的類會生效,那麼需要看一下BaseDexClassLoader,該類是PathClassLoader和DexClassLoader的基類,
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(parent);
this.originalPath = dexPath;
this.pathList =
new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class clazz = pathList.findClass(name);
if (clazz == null) {
throw new ClassNotFoundException(name);
}
return clazz;
}
其中findClass會呼叫DexPathList的findClass:
//DexPathList
public Class findClass(String name) {
for (Element element : dexElements) {
DexFile dex = element.dexFile;
if (dex != null) {
Class clazz = dex.loadClassBinaryName(name, definingContext);
if (clazz != null) {
return clazz;
}
}
}
return null;
}
從這裡便可以看出dexElements中Element元素順序的作用了,前面的會先被讀取到,如果讀取到了對應的類迴圈就會結束。
下篇文章會介紹補丁檔案的合成過程,敬請期待…
相關文章
- Tinker接入及原始碼分析(一)原始碼
- Tinker接入及原始碼分析(三)原始碼
- Android 熱修復 Tinker 接入及原始碼淺析Android原始碼
- Android 熱修復 Tinker接入及原始碼淺析Android原始碼
- Tinker原始碼分析原始碼
- Android 熱修復 Tinker 原始碼分析之DexDiff / DexPatchAndroid原始碼
- MyBatis原始碼分析(二)MyBatis原始碼
- 原始碼分析二:LeakCanary原始碼
- Backbone原始碼分析(二)原始碼
- YYCache 原始碼分析(二)原始碼
- 原始碼|jdk原始碼之HashMap分析(二)原始碼JDKHashMap
- redis原始碼分析(二)、redis原始碼分析之sds字串Redis原始碼字串
- 【MyBatis原始碼分析】select原始碼分析及小結MyBatis原始碼
- LinkedList原始碼分析(二)原始碼
- Volley原始碼分析(二)原始碼
- SDWebImage使用及原始碼分析Web原始碼
- class檔案的基本結構及proxy原始碼分析二原始碼
- netty原始碼分析之新連線接入全解析Netty原始碼
- Android tinker熱修復——實戰接入專案Android
- Retrofit原始碼分析二 代理模式原始碼模式
- SlidingMenu原始碼分析(二)原始碼
- ReentrantLock解析及原始碼分析ReentrantLock原始碼
- ArrayMap詳解及原始碼分析原始碼
- react-Router 及原始碼分析React原始碼
- EventBus詳解及原始碼分析原始碼
- AQS的原理及原始碼分析AQS原始碼
- Spring原始碼系列(二)--bean元件的原始碼分析Spring原始碼Bean元件
- 【Android】使用Bugly快速接入Tinker熱更新功能Android
- 原始碼|jdk原始碼之棧、佇列及ArrayDeque分析原始碼JDK佇列
- DRF-Throttle元件原始碼分析及改編原始碼元件原始碼
- DRF-Permission元件原始碼分析及改編原始碼元件原始碼
- Vue原始碼分析系列二:$mount()方法Vue原始碼
- 窺探React-原始碼分析(二)React原始碼
- Zookeeper原始碼分析(二) —– zookeeper日誌原始碼
- Netty原始碼分析--Reactor模型(二)Netty原始碼React模型
- Kafka原始碼分析(二) - 生產者Kafka原始碼
- Spring原始碼分析之IoC(二)Spring原始碼
- Kubernetes Deployment 原始碼分析(二)原始碼