標籤:
Android Studio升級到2.0之後,新增了Instant Run功能,該功能可以熱替換apk中的部分代碼,大幅提高測試安裝的效率。
但是,由於我的項目中自訂了一些ClassLoader,當使用InstantRun時,經常出現class載入不正確的問題。分析後原因如下。
使用Instant Run編譯出的apk裡面會多出幾個dex檔案,和一個instant-run.zip,這個zip裡也是一堆dex檔案:
所以推測,instant Run的實現原理是:
根據代碼結構,將App的源碼分割成多個dex,然後使用自訂的classloader來載入他們,當然這個自訂的classloader也要繼承BaseDexClassLoader,因為BaseDexClassLoader裡有個DexPathList,這個所謂的List裡存的是多個dex的檔案資訊,所以當某段代碼修改時,只需編譯和替換相應的dex檔案即可(這同樣也是MultiDex的實現原理)。
下面我們簡單驗證一下。
首先正常的apk運行時,其classloader列印出來是這樣的:
dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.zkw.hostdemo-1/base.apk"],nativeLibraryDirectories=[/data/app/com.zkw.hostdemo-1/lib/arm, /system/lib, /vendor/lib, system/vendor/lib, system/vendor/lib/egl, system/lib/hw]]]
而使用Instant Run運行起來的apk,其classloader列印出來長這樣:
com.android.tools.fd.runtime.IncrementalClassLoader$DelegateClassLoader[DexPathList[[dex file "/data/data/com.zkw.hostdemo/files/instant-run/dex/slice-support-annotations-23.3.0_6be31c7c3de045eced09b0e58c45c46ba1a8b4da-classes.dex", dex file "/data/data/com.zkw.hostdemo/files/instant-run/dex/slice-slice_9-classes.dex", dex file "/data/data/com.zkw.hostdemo/files/instant-run/dex/slice-slice_8-classes.dex", dex file "/data/data/com.zkw.hostdemo/files/instant-run/dex/slice-slice_7-classes.dex", dex file "/data/data/com.zkw.hostdemo/files/instant-run/dex/slice-slice_6-classes.dex", dex file "/data/data/com.zkw.hostdemo/files/instant-run/dex/slice-slice_5-classes.dex", dex file "/data/data/com.zkw.hostdemo/files/instant-run/dex/slice-slice_4-classes.dex", dex file "/data/data/com.zkw.hostdemo/files/instant-run/dex/slice-slice_3-classes.dex", dex file "/data/data/com.zkw.hostdemo/files/instant-run/dex/slice-slice_2-classes.dex", dex file "/data/data/com.zkw.hostdemo/files/instant-run/dex/slice-slice_1-classes.dex", dex file "/data/data/com.zkw.hostdemo/files/instant-run/dex/slice-slice_0-classes.dex", dex file "/data/data/com.zkw.hostdemo/files/instant-run/dex/slice-ormlite-core-4.47_2aa30d6da8ed45bfc6255e592f80eb5f44eace4c-classes.dex", dex file "/data/data/com.zkw.hostdemo/files/instant-run/dex/slice-ormlite-android-4.47_a7ee90c985672a4cd4bdfca79190a330465fcdb0-classes.dex", dex file "/data/data/com.zkw.hostdemo/files/instant-run/dex/slice-lecore_4c1e07fda866033d90832ceae724962d4943e790-classes.dex", dex file "/data/data/com.zkw.hostdemo/files/instant-run/dex/slice-internal_impl-23.1.1_12c3206fb094d3315355dc809fcd39ad94f6e5e8-classes.dex", dex file "/data/data/com.zkw.hostdemo/files/instant-run/dex/slice-fastjson-1.1.45.android_317ce285230b187682809682934f3690b4d9580d-classes.dex", dex file "/data/data/com.zkw.hostdemo/files/instant-run/dex/slice-com.android.support-support-v4-23.1.1_1cfecee433501e7056d02c08b2393c569c575b33-classes.dex", dex file "/data/data/com.zkw.hostdemo/files/instant-run/dex/slice-com.android.support-multidex-1.0.1_a3117677a6ad7ee678ee3f3a4af434c4d08ee7ba-classes.dex"],nativeLibraryDirectories=[/data/app/com.zkw.hostdemo-1/lib/arm, /system/lib, /vendor/lib, system/vendor/lib, system/vendor/lib/egl, system/lib/hw]]]
很長吧,發個:
可以看到這個ClassLoader是:com.android.tools.fd.runtime.IncrementalClassLoader$DelegateClassLoader,DexPathList對應著instant-run.zip裡的所有dex。
到這裡,感覺基本符合我的猜想,但是這個IncrementalClassLoader是如何添加到裝置中的,又如何在運行時起作用的,我們接著分析。
instant run啟動並執行apk裡,和class有關的就是classes.dex、classes2.dex和instant-run.zip裡的dex。
通過觀察命名和反編譯,我發現instant-run.zip裡的dex都是和我們app代碼相關的,而classes.dex和classes2.dex是AndroidStudio編譯時間自己添加的,其中classes2.dex最重要,我們反編譯看看:
終於看到了熟悉的IncrementalClassLoader,那這個classloader是怎麼生效的呢,我們看看BootstrapApplication的源碼(可想而知,AndroidManifest檔案中的application屬性也被Android Studio改了):
注意標綠的那行,繼續進去看看(那行下面有個createRealApplication(),其實就是通過反射擷取app自訂的Application,然後做對應的操作):
灰色的那行就是安裝IncrementalClassLoader的地方,進入IncrementalClassLoader的inject()看看,
源碼中可以看到,Studio其實就是利用反射,將自己的IncrementalClassLoader設定成app中預設ClassLoader的parent,這樣就攔截了所有類載入的動作,從而實現了對多個dex檔案的動態載入。
到此,原理分析結束。
所以如果你的app裡用到了自訂的ClassLoader,請謹慎使用Instant Run!
謝謝閱讀,轉載請註明出處。
[原創]Android Studio的Instant Run(即時安裝)原理分析和源碼淺析