--- title: 'Android 64K 方法 & MultiDex' disqus: kyleAlien --- Android 64K 方法 & MultiDex === ## OverView of Content [TOC] ## 64K 方法 當 APP 開發到一定版本以後,必可避免的會遇到 APK **64K 方法數的問題**,這個跟 Dex 虛擬機有 關係,這裡也會說明解決方案 64K 其實是方便記憶,其實限制數目為 2 Byte => 0xFFFF 也就是 65536 ### 64K 方法 - 錯誤訊息 若 APP 中建構的方法數量 64K,則會在編譯的時候出現錯誤,導致 build 失敗,錯誤訊息如下 1. 舊版本錯誤訊息 ```shell= # 舊版本錯誤訊息 Conversion to Dalvik format failed: Unable to execute dex: method ID not in [0, 0xffff]: 65536 ``` 2. 新版錯誤訊息 ```shell= # 新版本錯誤訊息 trouble writing output: Too many field references: 131000; max is 65536. You may try using --multi-dex option. ``` ### 64K 限制原因 * Android APK 的看作是一個 **++壓縮檔++**,裡面包含許多的 dex 可執行檔 :::success * dex 檔案 **dex 檔案簡單來說就是 .class 檔案透過 javac 編譯過後的 .java 檔案壓縮而成**,Dex 檔案也是專門給 Dalvik 虛擬機執行的檔案 ::: > ![](https://i.imgur.com/Js4mj3p.png) * 64K 方法限制是因為 Dalvik 可執行檔限制,單一 `.dex` 檔案最多能參考的方法數為 65536 個 (也就是 2 Byte 的大小) ## MultiDex ### Android 5.0 之前 (API level 21) * 在 Android 5.0 之前,全部的 dex 檔案都是透過 Dalvik 虛擬機來運行的,當方法數量超過 64K 時我們需要拆分 classes.dex 檔案 (將一個 classes.dex 檔拆成 classes.dex、classes2.dex、classes3.dex) * 在啟動時會先加載 classed.dex 檔案 (**classed.dex 檔案也稱為 ++Primary dex++**),之後才會加載其他 dex 檔案 (**其他的稱為 ++Secondary dex++**) ### Android 5.0 之前 - MultiDex 注意事項 1. 應用啟動後會,Dalvik 虛擬機會執行 dexopt 的操作 (優化 dex 檔案),產生 ODEX 檔案,這個過程 **複雜、耗時**,若 dex 檔案過大則可能導致 ANR 2. 由於 Dalvik 的線性記憶體分配器 LinearAlloc 的限制,使用 MultiDex 的應用在出現很大的記憶體分配時容易導致 App 當機,根本原因是 **Dalvik 虛擬機用來載入類別的堆積記憶體大小被強制寫入** :::success * 堆積記憶體大小 | Android 版本 | 堆積記憶體大小 | | -------- | -------- | | Android 2.3 以下 | 5M | | Android 2.3 | 8M | | Android 4.0 | 16M | * Android 5.0 以上使用 ART,就不存在 LinearAlloc 限制問題 (除非你手動用 ClassLoader 加載) ::: * 由於存在 Primary、Secondary 兩種 dex 分類,所以還需區分那些類必須放置在 Primary dex (像是啟動 Application 類就要放置),若 ClassLoader 加載不到則會出現 NoClassDefFoundError 錯誤 ### Android 5.0 之後 * **從 Android 5.0 開始,Android 使用了 ART 虛擬機來代替 Dalvik 虛擬機**,但並非捨棄了 dex 檔案,ART 虛擬機仍可載入 .dex 檔案 * ART 的特色是,在 APP 安裝期間會執行 **==預編譯== 動作,掃描 APK 中的 全部的 `.dex` 檔案,並將它們編譯成 ++單一個 `.oat`++ 檔案**,在應用執行期間去載入 `.oat` 檔案而不是 `.dex` 檔 :::info * ART 不能在運行期間載入 dex 檔案 ? ART 預設會先跑 oat 檔,但能是支持運行期加載 dex 檔案的,熱修復有其中一個方案就是採用自己透過 ClassLoader 加載 dex 檔案做修復 ::: ### Android Module * **每個 Android Module 都會對應到不同的 dex 檔案**,有幾個 Module 就有幾個 dex 檔,通過這個點可以把主 dex 放置到主 Module 上,其他工具類放到別的 Module > ![](https://i.imgur.com/A7nALDa.png) ## 解決方案 有個廢話就是~ 盡量減少方法,不讓其觸及 64K 的上限,這麼說是有原因的 因為在 APP 啟動時載入的 classes.dex 檔案若過多、過大,會導致啟動速度降低,並且可能導致 OOM 等等問題 (上面有提過) ### gradle proguard 設定 * 可以透過 Gradle 的 proguard 設定,能套過分析位元組碼,壓縮並移除沒有使用到的函數,可以有效減少方法數量 ### 專案開啟 MultiDex 設定 1. ^1^ gradle#defaultConfig 設定 **multiDexEnabled** true,^2^ 並使用 multidex Lib ```groovy= android { compileSdk 31 defaultConfig { applicationId "com.alien.myapplication" minSdk 23 targetSdk 31 versionCode 1 versionName "1.0" // 設定 multiDexEnabled multiDexEnabled true } } dependencies { implementation 'com.android.support:multidex:1.0.3' // ... 省略其他 } ``` 2. 自訂一個 Application,並對其設定(有兩種方案),^1^ 繼承 MultiDexApplication、^2^ 透過 MultiDex#install 初始化 ```java= // 方案一 public class MyApplication extends MultiDexApplication { @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); // 方案二 MultiDex.install(this); } } ``` :::info * 為何使用在 attachBaseContext 方法 因為 attachBaseContext 方法在 onCreate 方法前執行 ::: 3. 在 AndroidManifest.xml 設定自定義的 Application ```xml= <!-- AndroidManifest.xml --> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.MyApplication" android:name=".MyApplication"> <activity android:name=".MainActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> ``` ## Appendix & FAQ :::info ::: ###### tags: `Android 進階`