-
Dex分包的由來
分包的概念想必我們都不陌生了,因?yàn)橐粋€(gè)dex文件中的方法數(shù)使用一個(gè)short類型的字段來記錄方法數(shù),所以最多只能存儲(chǔ)2^16=65536個(gè)方法數(shù)(16是2個(gè)字節(jié)能表示的最大值)。隨著項(xiàng)目的迭代以及各種庫的引入,單個(gè)dex的方法數(shù)遲早會(huì)超過這個(gè)數(shù)值,因此,dex分包應(yīng)運(yùn)而生。
所謂dex分包就是把原先apk中的單個(gè)dex文件分成多個(gè)dex文件,這樣就能使每個(gè)dex中的方法數(shù)控制在65536這個(gè)閾值之下。
-
如何生成多個(gè)Dex文件
dex是一種具有特定格式的文件,通過對class文件的優(yōu)化來生成,這意味著可以通過程序自動(dòng)將class文件轉(zhuǎn)換成dex文件,在android工具包中已經(jīng)有這樣的程序可供使用了,那就是
dx
和d8
。使用
dx
命令將單個(gè)class文件轉(zhuǎn)成dex文件:dx --dex --no-strict --output=/path/result.dex Demo.class
注意要加上--no-strict,可以防止包名匹配錯(cuò)誤,不加的話你需要在對應(yīng)包名的頂層父目錄中執(zhí)行,比如“com.mph.bpp.Demo”的話,你必須在com的所在目錄下執(zhí)行,且源文件制定為相對路徑"com/mph/bpp/Demo.class"。
d8
是一款用于取代dx
、更快的 Dex 編譯器,可以生成更小的 APK,在Android Studio3.1以上版本已經(jīng)成為默認(rèn)選項(xiàng)了。使用
d8
命令將單個(gè)class文件轉(zhuǎn)成dex文件:d8 --release path/Demo.class --output path/result.dex
--release表示正式編譯,編譯 DEX 字節(jié)碼時(shí)不包含調(diào)試信息,相反的--debug則表示編譯 DEX 字節(jié)碼時(shí)在其中包含調(diào)試信息,例如調(diào)試符號(hào)表,此選項(xiàng)默認(rèn)處于啟用狀態(tài),因此不需要額外指定。
如果沒指定--output則會(huì)輸出到當(dāng)前執(zhí)行目錄,默認(rèn)輸出為classed.dex。
如果我們想要把多個(gè)class文件轉(zhuǎn)到同一個(gè)dex文件中,則只需要把源文件都放在一個(gè)jar包中,然后同樣的命令,源文件換成該jar包即可。
借助這兩個(gè)工具,我們就可以指定任何的class文件打包成任意數(shù)量的dex文件,但需要注意的是,在生成dex的過程中,我們需要去計(jì)算每個(gè)class的方法數(shù),保證它們的總量不會(huì)超過65536的上限,基于這套流程,我們可以寫一個(gè)腳本來完成dex分包的過程,這就是手動(dòng)分包。
在android5.0版本及以上,虛擬機(jī)開始支持多dex加載,在打包過程中,gradle會(huì)自動(dòng)檢測dex文件中的方法數(shù)是否超過65536,如果超過了則自動(dòng)拆成多dex文件,這個(gè)過程的原理其實(shí)就是我們上面所說的那樣,只不過在gradle自動(dòng)構(gòu)建中我們無法指定那些class放在同一個(gè)dex中,這就是自動(dòng)分包。
-
自動(dòng)分包配置
app/build.gradle :
android{ defaultConfig{ multiDexEnabled true } }
然后指定你的BaseApp繼承MultiDexApplication,無法繼承的話可以重寫B(tài)aseApp的attachBaseContext方法:
protected void attachBaseContext(Context base) { super.attachBaseContext(base); MultiDex.install(this); }
其實(shí)這就是MultiDexApplication中的實(shí)現(xiàn)。
如果你沒有使用androidx,那么想要使用MultiDex類的話還需要添加'com.android.support:multidex:x.x.x'依賴。
gradle的分包完全是自動(dòng)化的,也就是說你無法決定哪些類應(yīng)該放到主dex中,雖然它會(huì)自動(dòng)分析哪些類是應(yīng)用啟動(dòng)時(shí)必須的以及識(shí)別它們的引用類,但是對于一些不太可見的復(fù)雜依賴,比如從native代碼邏輯中創(chuàng)建的Java類對象的情況,這時(shí)在啟動(dòng)的時(shí)候就會(huì)發(fā)生ava.lang.NoClassDefFoundError。
好在,gradle允許你配置哪些類放到主dex中:android { buildTypes { release { multiDexKeepProguard file('multidex-config.pro') ... } } }
然后在build.gradle同一目錄下添加multidex-config.pro文件:
-keep class com.example.MyClass -keep class com.example.MyClassToo -keep class com.example.** { *; } // All classes in the com.example package
注意,這個(gè)配置只在minSdk為21以下才會(huì)生效,而且只會(huì)保證配置內(nèi)容添加到主dex,并不能保證其他未指定的不會(huì)添加到主dex。至于為什么這樣,因?yàn)檫@個(gè)配置是為了解決某些情況下虛擬機(jī)內(nèi)部檢測不到主dex中相關(guān)的關(guān)聯(lián)類的問題,可能是21及以上的虛擬機(jī)內(nèi)部已經(jīng)優(yōu)化好了,不需要額外配置了,這也關(guān)閉了我們手動(dòng)添加主dex的大門,也是出于安全的考慮吧。
Tip1:因在實(shí)踐過程中發(fā)現(xiàn)multiDexKeepProguard中指定的class并沒有出現(xiàn)在classes.dex中,在查詢這個(gè)問題過程中看到了一個(gè)不起眼的回復(fù)說是minSdk21版本及以上不會(huì)執(zhí)行這個(gè)配置,但是我在
官方文檔中并沒有找到這個(gè)說明,所以起初沒有當(dāng)真,沒想到真是這個(gè)問題。
Tip2:在我的項(xiàng)目中使用了很多JetPack的庫,很多庫的版本都很高,minSdk需要最低21,當(dāng)改成20后會(huì)報(bào)錯(cuò),根據(jù)提示在AndroidManifest.xml中增加<uses-sdk tools:overrideLibrary="xxx1, xxx2"/> 即可通過編譯,比如<uses-sdk tools:overrideLibrary="androidx.navigation.compose"/>,哪個(gè)庫報(bào)錯(cuò)就寫哪個(gè),多個(gè)用逗號(hào)隔開,這里只是說能通過編譯,但可能會(huì)有運(yùn)行時(shí)錯(cuò)誤,我這里只是看看apk中的classes.dex里有沒有配置的類而已。
Multidex庫也有所限制,一個(gè)是如果非主dex文件過大的話,在加載的時(shí)候可能會(huì)出現(xiàn)ANR;另一個(gè)是在4.0以下,linearalloc limit(虛擬機(jī)相關(guān)的)的大小不足以支撐dex的最大方法引用數(shù),這就會(huì)導(dǎo)致未滿65536的情況下依然會(huì)崩潰,因此在4.0一下要做好相關(guān)平臺(tái)的測試工作。
使用代碼瘦身功能可以很大程度上避免這個(gè)問題:android { buildTypes { release { minifyEnabled true //shrinkResources true //資源瘦身 ... } } }
minifyEnabled設(shè)置成true也可能讓原本多個(gè)dex合成一個(gè)dex文件。比如我在測試熱修復(fù)的demo中,debug模式下生成的apk沒有配置minifyEnabled屬性,因此會(huì)自動(dòng)被分成多個(gè)dex文件,但是在開啟了minifyEnabled的release模式下就只存在一個(gè)dex了。
在我的熱修復(fù)demo中,我發(fā)現(xiàn),如果需要被修復(fù)的類存在于主dex中的話,則使用類加載機(jī)制插入dexElements的方式是無法奏效的,但是如果要被修復(fù)的類存在于非主dex中的時(shí)候就沒問題,于是,我猜測:
被分到主dex中的class在應(yīng)用啟動(dòng)的時(shí)候就已經(jīng)被加載了(這里的加載指的是通過ClassLoader的loadClass方法加載過),哪怕尚未用到它,因此之后再去添加補(bǔ)丁的dexElements也不會(huì)被替換了。
-
MultiDex.install加載源碼分析
在app啟動(dòng)時(shí),虛擬機(jī)只會(huì)加載apk中名為“classed.dex”的這個(gè)dex文件,也就是主dex,因此,不管是自動(dòng)分包還是手動(dòng)分包,最終我們還要手動(dòng)加載其他的dex文件,這里通過MultiDex.install流程來一探究竟。
//dentifies if the current VM has a native support for multidex, meaning there is no need for additional installation by this library. private static final boolean IS_VM_MULTIDEX_CAPABLE = isVMMultidexCapable(System.getProperty("java.vm.version")); public static void install(Context context) { Log.i(TAG, "Installing application"); if (IS_VM_MULTIDEX_CAPABLE) { Log.i(TAG, "VM has multidex support, MultiDex support library is disabled."); return; } ... try { ApplicationInfo applicationInfo = getApplicationInfo(context); if (applicationInfo == null) { Log.i(TAG, "No ApplicationInfo available, i.e. running on a test Context:" + " MultiDex support library is disabled."); return; } doInstallation(context, new File(applicationInfo.sourceDir), new File(applicationInfo.dataDir), CODE_CACHE_SECONDARY_FOLDER_NAME, NO_KEY_PREFIX, true); } catch (Exception e) { Log.e(TAG, "MultiDex installation failure", e); throw new RuntimeException("MultiDex installation failed (" + e.getMessage() + ")."); } Log.i(TAG, "install done"); }
isVMMultidexCapable會(huì)判斷虛擬機(jī)時(shí)候有底層的加載分包的能力,如果虛擬機(jī)可以在底層直接加載dex分包的話就不需要在這里重復(fù)操作了。如果虛擬機(jī)層面沒有這個(gè)能力,那就需要MultiDex來做了。
private static void doInstallation(Context mainContext, File sourceApk, File dataDir, String secondaryFolderName, String prefsKeyPrefix, boolean reinstallOnPatchRecoverableException) throws IOException, IllegalArgumentException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException, SecurityException, ClassNotFoundException, InstantiationException { synchronized (installedApk) { //是否已加載過 if (installedApk.contains(sourceApk)) { return; } installedApk.add(sourceApk); if (Build.VERSION.SDK_INT > MAX_SUPPORTED_SDK_VERSION) { //打印log:版本大于20的通常虛擬機(jī)都有multidex能力,能走到這里說明不支持,因此是個(gè)需要注意的地方 } ClassLoader loader = getDexClassloader(mainContext); if (loader == null) { return; } try { clearOldDexDir(mainContext); } catch (Throwable t) { ... } //路徑為context.getApplicationInfo().getDataDir()/code_cache/secondary-dexes File dexDir = getDexDir(mainContext, dataDir, secondaryFolderName); //sourceApk路徑為context.getApplicationInfo().getSourceDir()獲取的值,比如我用的模擬器上的值是“/data/app/~~v1lKTsJtGX9XmDeVroYyxA==/com.mph.bpp-vhn5lJ9Ml_iXQ9hedBHCKQ==/base.apk” MultiDexExtractor extractor = new MultiDexExtractor(sourceApk, dexDir); IOException closeException = null; try { List<? extends File> files = extractor.load(mainContext, prefsKeyPrefix, false); try { installSecondaryDexes(loader, dexDir, files); } catch (IOException e) { if (!reinstallOnPatchRecoverableException) { throw e; } files = extractor.load(mainContext, prefsKeyPrefix, true); installSecondaryDexes(loader, dexDir, files); } } finally { try { extractor.close(); } catch (IOException e) { closeException = e; } } if (closeException != null) { throw closeException; } } }
然后調(diào)用MultiDexExtractor的load方法加載dex文件:
List<? extends File> load(Context context, String prefsKeyPrefix, boolean forceReload) throws IOException { ... List<ExtractedDex> files; if (!forceReload && !isModified(context, sourceApk, sourceCrc, prefsKeyPrefix)) { try { //加載已經(jīng)解壓過的dex文件 files = loadExistingExtractions(context, prefsKeyPrefix); } catch (IOException ioe) { //如果發(fā)生異常了就直接解壓dex文件 files = performExtractions(); putStoredApkInfo(context, prefsKeyPrefix, getTimeStamp(sourceApk), sourceCrc, files); } } else { //強(qiáng)行解壓文件 files = performExtractions(); putStoredApkInfo(context, prefsKeyPrefix, getTimeStamp(sourceApk), sourceCrc, files); } return files; }
注意上面?zhèn)鬟^來的最后一個(gè)參數(shù)forceReload,如果doInstallation的第一遍加載發(fā)生異常了,會(huì)再次調(diào)用load方法,傳入true來調(diào)用performExtractions方法。
我們先看performExtractions方法:
private List<ExtractedDex> performExtractions() throws IOException { //EXTRACTED_NAME_EXT是.classes,sourceApk.getName()值為base.apk,因此extractedFilePrefix值為base.apk.classes final String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT; clearDexDir(); List<ExtractedDex> files = new ArrayList<ExtractedDex>(); //開始解壓base.apk final ZipFile apk = new ZipFile(sourceApk); try { //secondaryNumber是除主dex之外的dex名字后綴,比如主dex是classes.dex,則其余的dex會(huì)是classes2.dex、classes3.dex等 int secondaryNumber = 2; //嘗試獲取base.apk中的非主dex文件,DEX_PREFIX是classes,DEX_SUFFIX是.dex ZipEntry dexFile = apk.getEntry(DEX_PREFIX + secondaryNumber + DEX_SUFFIX); //如果存在其他的dex while (dexFile != null) { //創(chuàng)建要輸出的dex文件,比如base.apk.classes3.zip,EXTRACTED_SUFFIX是.zip String fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX; ExtractedDex extractedFile = new ExtractedDex(dexDir, fileName); //files最終保存這些dex文件 files.add(extractedFile); int numAttempts = 0; boolean isExtractionSuccessful = false; //MAX_EXTRACT_ATTEMPTS是3,表示不成功的話最多嘗試解壓3次 while (numAttempts < MAX_EXTRACT_ATTEMPTS && !isExtractionSuccessful) { numAttempts++; //解壓 extract(apk, dexFile, extractedFile, extractedFilePrefix); try { //賦值crc(Cyclic Redundancy Check(循環(huán)冗余校驗(yàn))) extractedFile.crc = getZipCrc(extractedFile); isExtractionSuccessful = true; } catch (IOException e) { isExtractionSuccessful = false; } if (!isExtractionSuccessful) { // Delete the extracted file extractedFile.delete(); } } if (!isExtractionSuccessful) { throw new IOException("Could not create zip file " + extractedFile.getAbsolutePath() + " for secondary dex (" + secondaryNumber + ")"); } secondaryNumber++; dexFile = apk.getEntry(DEX_PREFIX + secondaryNumber + DEX_SUFFIX); } } finally { try { apk.close(); } catch (IOException e) { Log.w(TAG, "Failed to close resource", e); } } return files; }
看一下extract方法:
private static void extract(ZipFile apk, ZipEntry dexFile, File extractTo, String extractedFilePrefix) throws IOException, FileNotFoundException { InputStream in = apk.getInputStream(dexFile); ZipOutputStream out = null; //tmp就是要輸出的dex文件,路徑和performExtractions方法中的extractedFile的路徑是一樣的,只不過名字多了個(gè)tmp的前綴 File tmp = File.createTempFile("tmp-" + extractedFilePrefix, EXTRACTED_SUFFIX, extractTo.getParentFile()); try { //開始壓縮操作,其實(shí)就是把dexFile文件復(fù)制到tmp out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(tmp))); try { //dexFile會(huì)復(fù)制到tmp這個(gè)zip中并命名為classes.dex ZipEntry classesDex = new ZipEntry("classes.dex"); // keep zip entry time since it is the criteria used by Dalvik classesDex.setTime(dexFile.getTime()); out.putNextEntry(classesDex); byte[] buffer = new byte[BUFFER_SIZE]; int length = in.read(buffer); while (length != -1) { out.write(buffer, 0, length); length = in.read(buffer); } out.closeEntry(); } finally { out.close(); } //最終的dex文件為了安全考慮要設(shè)置為只讀 if (!tmp.setReadOnly()) { throw new IOException("Failed to mark readonly \"" + tmp.getAbsolutePath() + "\" (tmp of \"" + extractTo.getAbsolutePath() + "\")"); } //注意File的renameTo方法,它的原理是會(huì)創(chuàng)建extractTo,然后把tmp的內(nèi)容復(fù)制過去 if (!tmp.renameTo(extractTo)) { throw new IOException("Failed to rename \"" + tmp.getAbsolutePath() + "\" to \"" + extractTo.getAbsolutePath() + "\""); } } finally { closeQuietly(in); //刪除tmp文件 tmp.delete(); } }
總結(jié)一下extract方法就是:把dexFile復(fù)制到extractedFile中去。
回到load方法中,performExtractions方法執(zhí)行完之后緊接著會(huì)調(diào)用putStoredApkInfo方法,這個(gè)方法中會(huì)把解壓后的文件關(guān)鍵信息保存到SharedPreferences中,比如TIME_STAMP、
CRC(Cyclic Redundancy Check(循環(huán)冗余校驗(yàn)))
、DEX_NUMBER(非主dex文件數(shù)量)等信息:private static void putStoredApkInfo(Context context, String keyPrefix, long timeStamp, long crc, List<ExtractedDex> extractedDexes) { SharedPreferences prefs = getMultiDexPreferences(context); SharedPreferences.Editor edit = prefs.edit(); edit.putLong(keyPrefix + KEY_TIME_STAMP, timeStamp); edit.putLong(keyPrefix + KEY_CRC, crc); edit.putInt(keyPrefix + KEY_DEX_NUMBER, extractedDexes.size() + 1); int extractedDexId = 2; for (ExtractedDex dex : extractedDexes) { edit.putLong(keyPrefix + KEY_DEX_CRC + extractedDexId, dex.crc); edit.putLong(keyPrefix + KEY_DEX_TIME + extractedDexId, dex.lastModified()); extractedDexId++; } edit.commit(); }
現(xiàn)在再來看loadExistingExtractions方法就很清晰了:
private List<ExtractedDex> loadExistingExtractions( Context context, String prefsKeyPrefix) throws IOException { //也就是base.apk.classes final String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT; //獲取之前保存信息的SharedPreferences SharedPreferences multiDexPreferences = getMultiDexPreferences(context); //SharedPreferences獲取之前保存的非主dex數(shù)量 int totalDexNumber = multiDexPreferences.getInt(prefsKeyPrefix + KEY_DEX_NUMBER, 1); final List<ExtractedDex> files = new ArrayList<ExtractedDex>(totalDexNumber - 1); //根據(jù)保存的信息來構(gòu)造ExtractedDex for (int secondaryNumber = 2; secondaryNumber <= totalDexNumber; secondaryNumber++) { //找到之前含有dexFile的壓縮文件 String fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX; ExtractedDex extractedFile = new ExtractedDex(dexDir, fileName); if (extractedFile.isFile()) { //安全驗(yàn)證CRC和time,來保證這中間沒被惡意修改過 extractedFile.crc = getZipCrc(extractedFile); long expectedCrc = multiDexPreferences.getLong( prefsKeyPrefix + KEY_DEX_CRC + secondaryNumber, NO_VALUE); long expectedModTime = multiDexPreferences.getLong( prefsKeyPrefix + KEY_DEX_TIME + secondaryNumber, NO_VALUE); long lastModified = extractedFile.lastModified(); if ((expectedModTime != lastModified) || (expectedCrc != extractedFile.crc)) { throw new IOException("Invalid extracted dex: " + extractedFile + " (key \"" + prefsKeyPrefix + "\"), expected modification time: " + expectedModTime + ", modification time: " + lastModified + ", expected crc: " + expectedCrc + ", file crc: " + extractedFile.crc); } files.add(extractedFile); } else { throw new IOException("Missing extracted secondary dex file '" + extractedFile.getPath() + "'"); } } return files; }
到此,我們已經(jīng)清楚了整個(gè)讀取dexFile的過程,下面我們繼續(xù)看如何加載到ClassLoader的。
MultiDexExtractor的load方法完成后會(huì)拿到所有的含有dexFile的壓縮zip(內(nèi)部都是叫classes.dex),然后調(diào)用installSecondaryDexes(loader, dexDir, files):
private static void installSecondaryDexes(ClassLoader loader, File dexDir, List<? extends File> files) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException, IOException, SecurityException, ClassNotFoundException, InstantiationException { if (!files.isEmpty()) { if (Build.VERSION.SDK_INT >= 19) { V19.install(loader, files, dexDir); } else if (Build.VERSION.SDK_INT >= 14) { V14.install(loader, files); } else { V4.install(loader, files); } } }
這里會(huì)根據(jù)不同的SDK版本來分別執(zhí)行不同的install方法,以V19為例:
static void install(ClassLoader loader, List<? extends File> additionalClassPathEntries, File optimizedDirectory) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException, IOException { Field pathListField = findField(loader, "pathList"); Object dexPathList = pathListField.get(loader); ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>(); expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList, new ArrayList<File>(additionalClassPathEntries), optimizedDirectory, suppressedExceptions)); if (suppressedExceptions.size() > 0) { for (IOException e : suppressedExceptions) { Log.w(TAG, "Exception in makeDexElement", e); } Field suppressedExceptionsField = findField(dexPathList, "dexElementsSuppressedExceptions"); IOException[] dexElementsSuppressedExceptions = (IOException[]) suppressedExceptionsField.get(dexPathList); if (dexElementsSuppressedExceptions == null) { dexElementsSuppressedExceptions = suppressedExceptions.toArray( new IOException[suppressedExceptions.size()]); } else { IOException[] combined = new IOException[suppressedExceptions.size() + dexElementsSuppressedExceptions.length]; suppressedExceptions.toArray(combined); System.arraycopy(dexElementsSuppressedExceptions, 0, combined, suppressedExceptions.size(), dexElementsSuppressedExceptions.length); dexElementsSuppressedExceptions = combined; } suppressedExceptionsField.set(dexPathList, dexElementsSuppressedExceptions); IOException exception = new IOException("I/O exception during makeDexElement"); exception.initCause(suppressedExceptions.get(0)); throw exception; } } /** * A wrapper around * {@code private static final dalvik.system.DexPathList#makeDexElements}. */ private static Object[] makeDexElements( Object dexPathList, ArrayList<File> files, File optimizedDirectory, ArrayList<IOException> suppressedExceptions) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { Method makeDexElements = findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class, ArrayList.class); return (Object[]) makeDexElements.invoke(dexPathList, files, optimizedDirectory, suppressedExceptions); }
這里會(huì)利用反射拿到傳進(jìn)來的ClassLoader的pathList屬性,類型是DexPathList,再次反射獲取它的dexElements屬性,他是個(gè)Element數(shù)組,makeDexElements方法中利用反射調(diào)用DexPathList的makeDexElements方法構(gòu)造一個(gè)Element數(shù)組,里面包含我們之前獲取的所有非主dex的zip文件,然后在expandFieldArray方法中把pathList中原有的dexElements數(shù)組和新創(chuàng)建的整合到一起(新創(chuàng)建的放在后面,也就是主dex的位置在非主dex前面),最后再設(shè)置到pathList的dexElements屬性上。
那為什么要這么做呢?那就得提到android中的類加載機(jī)制了。
-
Android中的類加載機(jī)制
Android的類加載基于Java的類加載機(jī)制,都是雙親委托機(jī)制,什么是雙親委托呢?就是每次加載一個(gè)類的時(shí)候都先從更上一級的父加載器中查找,如果父加載器已經(jīng)加載過了則直接使用不再加載,這樣做可以避免重復(fù)加載,最重要的是防止系統(tǒng)類的惡意修改和替換。
在Android系統(tǒng)中有兩個(gè)ClassLoader可以供我們加載自定義資源,一個(gè)是DexClassLoader,一個(gè)是PathClassLoader,他們的區(qū)別就是DexClassLoader多了一個(gè)可以指定odex(dex的優(yōu)化文件)的輸出目錄的選項(xiàng),它們都繼承自BaseDexClassLoader。
當(dāng)我們用到某個(gè)類的時(shí)候會(huì)由虛擬機(jī)調(diào)用ClassLoader的loadClass方法:
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { //之前是否已加載過 Class<?> c = findLoadedClass(name); if (c == null) { try { //嘗試從父加載器加載(最頂層是BootClassLoader) if (parent != null) { c = parent.loadClass(name, false); } else { //默認(rèn)為空 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { } if (c == null) { //如果都未找到則調(diào)用自身的findClass方法 c = findClass(name); } } return c; }
BaseDexClassLoader中實(shí)現(xiàn)了findClass方法:
@Override protected Class<?> findClass(String name) throws ClassNotFoundException { List<Throwable> suppressedExceptions = new ArrayList<Throwable>(); Class c = pathList.findClass(name, suppressedExceptions); if (c == null) { ClassNotFoundException cnfe = new ClassNotFoundException( "Didn't find class \"" + name + "\" on path: " + pathList); for (Throwable t : suppressedExceptions) { cnfe.addSuppressed(t); } throw cnfe; } return c; }
可見,這里會(huì)調(diào)用pathList的findClass方法:
public Class<?> findClass(String name, List<Throwable> suppressed) { for (Element element : dexElements) { Class<?> clazz = element.findClass(name, definingContext, suppressed); if (clazz != null) { return clazz; } } if (dexElementsSuppressedExceptions != null) { suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions)); } return null; }
看到這里,我們就明白了,原來最終會(huì)從dexElements中查找類啊,因此也就理解了上面的dex加載操作。
這也是熱修復(fù)中類加載修復(fù)方式的原理依據(jù),也是插件化思想的原理依據(jù)之一。
關(guān)于Dex分包
最后編輯于 :
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
- 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
- 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
- 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
- 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
- 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
- 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
- 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
- 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
- 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
- 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
推薦閱讀更多精彩內(nèi)容
- 轉(zhuǎn)載:tech.meituan.com/mt-android-auto-split-dex.html 概述 作為一...
- 0x01 開篇 官方文檔MultiDex解釋: 1.Dalvik Executable (DEX)文件的總方法數(shù)限...
- 在android apk中如果項(xiàng)目引用的方法數(shù)超過64k(包括android框架方法、庫方法、自己代碼中的方法)的...
- 概述 Android開發(fā)者應(yīng)該都遇到了64K最大方法數(shù)限制的問題,針對這個(gè)問題,google也推出了multide...
- 什么是Android的熱修復(fù)熱修復(fù)是一種在應(yīng)用程序運(yùn)行時(shí)對已發(fā)布版本進(jìn)行動(dòng)態(tài)修復(fù)bug或更新功能的技術(shù)。當(dāng)APP發(fā)...