亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 學院 > 開發設計 > 正文

Tinker接入及源碼分析(二)

2019-11-09 15:01:44
字體:
來源:轉載
供稿:網友

該系列文章分析基于 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元素順序的作用了,前面的會先被讀取到,如果讀取到了對應的類循環就會結束。

下篇文章會介紹補丁文件的合成過程,敬請期待…


上一篇:git 項目管理之打標簽tag

下一篇:ChartView

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
欧美激情亚洲另类| 国内精品久久久久久影视8| 高清在线视频日韩欧美| 久久99国产综合精品女同| 国产精品久久久久久久久久久不卡| 成人在线激情视频| 日本精品视频在线| 中文字幕av一区二区三区谷原希美| 最近2019中文字幕在线高清| 亚洲欧美国产日韩天堂区| 国产在线播放91| 青青草国产精品一区二区| 久久精品一本久久99精品| 黄色精品在线看| 久青草国产97香蕉在线视频| 欧美超级免费视 在线| 日韩电影中文字幕一区| 国产99久久久欧美黑人| 日韩专区在线播放| 欧美高清理论片| 亚洲人成电影网| 亚洲福利在线观看| 久久久www成人免费精品张筱雨| 久久视频中文字幕| 亚洲人成电影在线| 91午夜在线播放| 成人啪啪免费看| 欧美日韩一区二区三区在线免费观看| 午夜精品国产精品大乳美女| 久久久精品在线观看| 中文字幕日韩欧美精品在线观看| 欧美国产日韩一区二区| 中文字幕av一区| 亚洲男人天天操| 在线亚洲欧美视频| 精品福利樱桃av导航| 日韩美女免费线视频| 亚洲自拍偷拍视频| 国产精品欧美日韩久久| 日韩三级影视基地| 国产午夜精品免费一区二区三区| 91嫩草在线视频| 亚洲伊人久久大香线蕉av| 国产成人久久久精品一区| 亚洲精品中文字幕有码专区| 久久久中精品2020中文| 国产精选久久久久久| 国产一区二区在线免费| 久久综合久久美利坚合众国| 88xx成人精品| 国产日韩欧美中文| 亚洲乱码国产乱码精品精| 国产精品久久久久91| 国产精品视频1区| 7m精品福利视频导航| 日韩欧美一区视频| 国产精品亚洲美女av网站| 精品高清美女精品国产区| www高清在线视频日韩欧美| 亚洲人成电影网站色| 欧美电影免费播放| 日韩av中文字幕在线| 亚洲国产精彩中文乱码av| 欧美日韩国产中文精品字幕自在自线| 91在线精品视频| 亚洲国产古装精品网站| 伊人久久大香线蕉av一区二区| 久久精品国产亚洲一区二区| 亚洲精品小视频在线观看| 亚洲永久免费观看| 久久精品国产一区二区电影| www亚洲欧美| 欧美成年人视频| 色婷婷成人综合| 成人免费高清完整版在线观看| 超碰精品一区二区三区乱码| 2019亚洲日韩新视频| 久久精品一区中文字幕| 亚洲欧美国产高清va在线播| 国产精品丝袜久久久久久高清| 亚洲热线99精品视频| 国产综合久久久久| 亚洲精品成人久久久| 大伊人狠狠躁夜夜躁av一区| 亚洲伊人一本大道中文字幕| 日韩精品福利在线| 理论片在线不卡免费观看| 欧美与欧洲交xxxx免费观看| 亚洲最大福利网站| 国产精品永久免费观看| 青青草国产精品一区二区| 97**国产露脸精品国产| 欧美国产欧美亚洲国产日韩mv天天看完整| 国产精品日日摸夜夜添夜夜av| 91产国在线观看动作片喷水| 久久91亚洲精品中文字幕奶水| 中文字幕精品国产| 欧美成人精品影院| 欧美专区福利在线| 成人xxxxx| 久久久久久国产精品三级玉女聊斋| 国产精品999999| 91精品国产高清久久久久久久久| 亚洲人成网站777色婷婷| 欧洲精品在线视频| 亚州欧美日韩中文视频| 国产成人综合亚洲| 午夜精品福利在线观看| 久久精品成人欧美大片| 91精品国产91久久久| 亚洲精品av在线播放| 一区二区三区四区在线观看视频| 欧美激情a∨在线视频播放| 红桃av永久久久| 久久人人爽人人爽人人片亚洲| 国产亚洲欧洲高清一区| 国产精品久久久久久久久久免费| 国产精品日日做人人爱| 久久成人亚洲精品| 久久91亚洲精品中文字幕奶水| 亚洲人成在线一二| 亚洲最大福利视频网站| 成人综合国产精品| 久久久亚洲精品视频| 亚洲日本中文字幕| 蜜臀久久99精品久久久无需会员| 国产免费一区二区三区在线观看| 欧美日韩aaaa| 久久夜色精品国产亚洲aⅴ| 欲色天天网综合久久| 成人欧美在线观看| 一道本无吗dⅴd在线播放一区| 欧美日韩国产精品专区| 成人黄色av网站| 国产视频精品自拍| 亚洲国产精品中文| 中文字幕av一区二区三区谷原希美| 91久久精品国产| 亚洲欧美日韩第一区| 亚洲激情电影中文字幕| 日韩av综合网站| 中文字幕亚洲欧美日韩高清| 亚洲免费av电影| 午夜精品久久久久久久99黑人| 欧美在线亚洲在线| 日韩一区二区在线视频| 亚洲免费小视频| 在线视频日本亚洲性| 91免费在线视频| 92看片淫黄大片看国产片| 久久视频这里只有精品| 午夜精品福利在线观看| 日韩欧美精品网站| 久久精品视频va| 国内精品国产三级国产在线专| 欧美黑人性猛交| 亚洲精品456在线播放狼人| 91久久久国产精品| 九九精品视频在线观看| 欧美福利视频网站| 91九色国产社区在线观看| 久久福利视频网| 日韩国产精品视频| 日韩欧美极品在线观看|