1. 程式人生 > >Android開發中利用AndroidStudio分包生成多個dex檔案

Android開發中利用AndroidStudio分包生成多個dex檔案

Android中單個dex檔案所能包含的最大方法數是65536,這包含所依賴所有jar以及應用程式碼中的所有方法。簡單的apk方法數很難達到這麼多,但是對於一些複雜大型的應用來說65536就很容易超過,當方法數達到65536後,編譯器就無法完成編譯工作並丟擲類似下面異常:

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':app:transformClassesWithDexForDebug'.
> com.android.build.api.transform.TransformException: com.android.ide.common.process.ProcessException: java.util.concurrent.ExecutionException: com.android.dex.DexIndexOverflowException: method ID not in [0, 0xffff]: 65536
UNEXPECTED TOP-LEVEL EXCEPTION: com.android.dex.DexIndexOverflowException: method ID not in [0, 0xffff]: 65536 at com.android.dx.merge.DexMerger$6.updateIndex(DexMerger.Java:502) at com.android.dx.merge.DexMerger$IdMerger.mergeSorted(DexMerger.java:277) at com.android.dx.merge.DexMerger.mergeMethodIds(DexMerger.java:491) at com.android.dx.merge.DexMerger.mergeDexes(DexMerger.java:168) at com.android.dx.merge.DexMerger.merge(DexMerger.java:189) at com.android.dx.command.dexer.Main.mergeLibraryDexBuffers(Main.java:454) at com.android.dx.command.dexer.Main.runMonoDex(Main.java:302) at com.android.dx.command.dexer.Main.run(Main.java:245) at com.android.dx.command.dexer.Main.main(Main.java:214) at com.android.dx.command.Main.main(Main.java:106)
還有可能方法數沒有65536,也編譯正常完成,但是應用安裝在低版本時出現如下異常:
E/dalvikm Optimization failed
E/installed: dexopt failed on '/data/dalvik-cache/[email protected]'
出現這樣情況是因為dexopt是一個程式,應用在安裝時,系統會通過dexopt來優化dex檔案,在優化過程中dexopt採用一個固定大小的緩衝區LinearAlloc來儲存應用所有資訊,LinearAlloc緩衝區在新版本Android系統中預設大小是8MB或16MB,但在Android2.2和2.3中只有5M,當待安裝apk方法數較多時,儘管方法數沒有達到65536,可能儲存空間超過上限,這種情況dexopt程式就會報錯,從而導致安裝失敗。

這樣就出現了把一個dex拆分成多個dex,Google在2014年提出了multidex的解決方法,通過multidex可以很好解決方法數越界問題,下面就以AndroidStudio和Eclipse來說下具體怎麼實現的,這篇先說說利用AndroidStudio拆分dex,Eclipse的用法後續更新

在Android5.0以前使用multidex需要引入Google提供的android-support-multidex.jar,這個jar在Android SDK目錄下的“extras/android/support/multidex/library/libs”下,從5.0以後Android預設支援了multidex

1.修改工程中app目錄下的build.gradle,在defaultConfig中新增multiDexEnabled true,如下

android {
    compileSdkVersion 25
    buildToolsVersion "25.0.2"
    defaultConfig {
        applicationId "multidex.jason.com.multidexdemo"
        minSdkVersion 15
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        multiDexEnabled true
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}
在dependencies中新增multidex依賴compile 'org.robolectric:shadows-multidex:3.3.1'
dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })

    compile 'com.android.support:appcompat-v7:25.2.0'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    compile 'org.robolectric:shadows-multidex:3.3.1'
}
最終build.gradle檔案就配置完成了,需要檢視原始碼的可以在文章最後的連結中下載

2、在程式碼中支援multidex功能,具體有下面三種方式

1⃣️ 在AndroidManifest.xml中指定Application為MultiDexApplication,如下

    <application
        android:name="android.support.multidex.MultiDexApplication"
        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/AppTheme">
2⃣️ 讓應用的Application繼承MultiDexApplication

3⃣️如果不想讓採用第2⃣️種,可以選擇重寫Application的attachBaseContext,這個方法比Application的onCreate要先執行,如下:

public class TestApplication extends Application {
    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        MultiDex.install(this);
    }
}
通過studio基本的工作都已經完成,採用了multidex方法後,只有當應用的方法數越界後,才會生成多個dex,具體打包多少個dex檔案要看當前專案的規模了,下圖是生成了兩個dex的情況



如果想自動嘗試的同學,在這裡提供一個生成更多方法的類

public class ProductMethod {

	public static void main(String[] args) {
		productMethod();
	}

	private static void productMethod() {
		for (int i = 0; i < 10000; i++) {
			System.out.println("private void method" + i + "(){");
			if (i == 0) {
				System.out.println("    method" + 9999 + "();");
			} else {

				System.out.println("    method" + (i - 1) + "();");
			}
			System.out.println("}");
		}
	}
}
在輸出的時,選擇輸出到檔案,不要讓列印到控制檯;右鍵選中java類“Run as”-“Run Configurations”


這樣複製到專案中某個Activity裡就可以了,通過預設配置很容易就生成了多個dex檔案。

當然還可以通過build.gradle檔案中的一些配置項來定製dex生成過程。比如指定主dex檔案所要包含的類,這個時候就可以通過--main-dex-list選項來實現這個功能,下面是修改後的build.gradle檔案,在裡面新增afterEvaluate和dependencies通緝,裡面內容如下

afterEvaluate {
    println("afterEvaluate")
    tasks.matching {
        it.name.startsWith('dex')
    }.each {dx ->
       def listFile = project.rootDir.absolutePath+'/app/maindexlist.txt'
        println("root dir:"+project.rootDir.absolutePath)
        println("dex task found :"+dx.name)
        if(dx.additionalParameters == null){
            dx.additionalParameters = []
        }
        dx.additionalParameters += '--multi-dex'
        dx.additionalParameters += '--main-dex-list='+listFile
        dx.additionalParameters += '--minimal-main-dex'
    }
}
minimal-main-dex表明只有main-dex-list所指定類才能打包到主dex中,它的輸入是一個檔案maindexlist.txt,這裡是在app根目錄建立的,不一定非要在這建立,只要afterEvaluate配置地方和建立地方一致就行,maindexlist.txt具體格式如下:
multidex/jason/com/multidexdemo/MainActivity.class


android/support/multidex/BuildConfig.class
android/support/multidex/MultiDex$V14.class
android/support/multidex/MultiDex$V19.class
android/support/multidex/MultiDex$V4.class
android/support/multidex/MultiDex.class
android/support/multidex/MultiDexApplication.class
android/support/multidex/MultiDexExtractor$1.class
android/support/multidex/MultiDexExtractor.class
android/support/multidex/ZipUtil$CentralDirectory.class
android/support/multidex/ZipUtil.class

其中下面這幾個是multidex的依賴的幾個類,必須打包到主dex中,隨著multidex的升級,可能也會有所改變;如果沒有打包到主dex中,程式執行時會拋異常,無法找到multidex相關的類。另外需要注意的是Application的成員變數和程式碼塊會先於attachBaseContext初始化執行,此時還沒有其他dex檔案被載入,會出現無法載入到對應的類而中止執行,在實際開發中要避免這樣的錯誤,執行時會出現如下錯誤:

E/AndroidRuntime:FATAL EXCEPTION: main
java.lang.NoClassDefFoundError:
如果用使用其他Lib,要保證這些Lib沒有被preDex,否則可能會丟擲下面的異常
UNEXPECTED TOP-LEVEL EXCEPTION:
    com.android.dex.DexException: Library dex files are not supported in multi-dex mode
        at com.android.dx.command.dexer.Main.runMultiDex(Main.java:337)
        at com.android.dx.command.dexer.Main.run(Main.java:243)
        at com.android.dx.command.dexer.Main.main(Main.java:214)
        at com.android.dx.command.Main.main(Main.java:106)
遇到這個異常,需要在Gradle中修改,讓它不要對Lib做preDexing
android {
//  ...
    dexOptions {
        preDexLibraries = false
    }
}
另外如果每次都開啟MultiDex編譯版本的話,會比平常用更多的時間(這個也容易理解,畢竟做了不少事情)
 Android的官方文件也給了我們一個小小的建議,利用Gradle建立兩個Flavor.一個minSdkVersion設定成21,這是用了ART支援的Dex格式,避免了MultiDex的開銷.而另外一個Flavor就是原本支援的最小sdkVersion.平時開發時候除錯程式,就用前者的Flavor,釋出版本打包就用後者的Flavor.
android {
    productFlavors {
        // Define separate dev and prod product flavors.
        dev {
            // dev utilizes minSDKVersion = 21 to allow the Android gradle plugin
            // to pre-dex each module and produce an APK that can be tested on
            // Android Lollipop without time consuming dex merging processes.
            minSdkVersion 21
        }
        prod {
            // The actual minSdkVersion for the application.
            minSdkVersion 14
        }
    }
          ...
    buildTypes {
        release {
            runProguard true
            proguardFiles getDefaultProguardFile('proguard-android.txt'),
                                                 'proguard-rules.pro'
        }
    }
}
apk是一個zip壓縮包,dalvik每次載入apk都要從中解壓出class.dex檔案,載入過程還涉及到dex的classes需要的雜七雜八的依賴庫的載入,耗時間。於是Android決定優化一下這個問題,在app安裝到手機之後,系統執行dexopt程式對dex進行優化,將dex的依賴庫檔案和一些輔助資料打包成odex檔案。存放在cache/dalvik_cache目錄下。儲存格式為apk路徑 @ apk名 @ classes.dex。這樣以空間換時間大大縮短讀取/載入dex檔案的過程。

dexopt程式的dalvik分配一塊記憶體來統計你的app的dex裡面的classes的資訊,由於classes太多方法太多超過這個linearAlloc 的限制 ,減小dex的大小如下。

gradle指令碼如下:

android.applicationVariants.all {
    variant ->
        dex.doFirst{
            dex->
            if (dex.additionalParameters == null) {
                dex.additionalParameters = []
            }
                dex.additionalParameters += '--set-max-idx-number=48000'

       }
}
--set-max-idx-number= 用於控制每一個dex的最大方法個數,寫小一點可以產生好幾個dex。

Multidex方法雖然解決了方法數越界問題,也有些侷限性,下面是可能出現的問題
1.應用安裝到手機上的時候dex檔案的安裝是複雜的有可能會因為第二個dex檔案太大導致ANR,需要用proguard優化你的程式碼。
2.使用了mulitDex的App有可能在4.0(api level 14)以前的機器上無法啟動,因為Dalvik linearAlloc bug(Issue 22586)  ,用proguard優化你的程式碼將減少該bug機率。
3.使用了mulitDex的App在runtime期間有可能因為Dalvik linearAlloc limit (Issue 78035)  Crash。該記憶體分配限制在 4.0版本被增大,但是5.0以下的機器上的Apps依然會存在這個限制。
4.主dex被dalvik虛擬機器執行時候,哪些類必須在主dex檔案裡面這個問題比較複雜。build tools 可以搞定這個問題。但是如果你程式碼存在反射和native的呼叫也不保證100%正確

原始碼地址

相關推薦

Android開發利用AndroidStudio分包生成dex檔案

Android中單個dex檔案所能包含的最大方法數是65536,這包含所依賴所有jar以及應用程式碼中的所有方法。簡單的apk方法數很難達到這麼多,但是對於一些複雜大型的應用來說65536就很容易超過,當方法數達到65536後,編譯器就無法完成編譯工作並丟擲類似下面異常:

Android開發20——單個監聽器監聽按鈕點選事件

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

android 在module內建立CMakeLists.txt來實現生成.so檔案

公司要做sdk,而且大部分程式碼都是c++的,這就需要在專案中生成多個.so檔案,一個是sdk主體,一個是測試程式碼,通過網上查了相關資料有不同的方法 沒有module的實現方法 有module的實現方法 我只參考了有module的方法 首先在自己的專案下建立modul

LInux利用執行緒實現客戶端和伺服器端進行通訊

上一篇博文講了如何利用子程序實現多個客戶端和伺服器端進行通訊, 那麼,這一篇部落格就來實現一下如何利用執行緒實現多個客戶端和伺服器端進行通訊 程式碼實現: ser1.c #include <

用python生成txt檔案

在win下建立多個.txt檔案,參考下面的程式碼 for i in range(1000): i_str = str(i+1) file_name = i_str+ '.txt' f = open('a/'+file_name,'w') f.close() 在

Makefile生成目標檔案

有目錄結構如下:  mmap ├── Makefile ├── read │   ├── Makefile │   └── mmap_read.c └── write     ├── Makefile     └── mmap_write.c mmap 目錄下面有 wri

AndroidAndroid與伺服器互動 POST上傳圖片檔案、文字內容 GET下載圖片

這裡伺服器端採用的是php解析內容 HTTP請求   HTTP 請求方法有 OPTIONS、GET、HEAD、POST、PUT、DELETE、TRACE、CONNECT 這幾種。用於資料互動的最基本方法一般為GET、POST、PUT、DELETE。對

Android開發怎樣用進程、用進程的好處、進程的缺陷、解決方法(轉)

傳遞 標簽 事情 打印 ide 即時通訊 ice cati 一個數 轉自:http://blog.csdn.net/spencer_hale/article/details/54968092 1.怎樣用多進程 Android多進程概念:一般情況下,一個應用程序就是一個進

Android開發連續設定按鈕的監聽器的方法

1、首先定義一個整數型的陣列和一個Button型別的按鈕 intButtons[];privateButton tempButton; 2、然後對整數型陣列進行賦值 Buttons=newint[

Android開發遇到的問題(二)——新建android工程的時候eclipse沒有生成MainActivity和layout佈局

一、新建android工程的時候eclipse沒有生成MainActivity和layout佈局   最近由於工作上的原因,開始學習Android開發,在入門的時候就遇到了不少的坑,遇到的第一個坑就是"新建android工程的時候eclipse沒有自動生成MainActivi

Android開發點觸控的實現方法

// import略   public class ImageViewerActivity extends Activity implements OnTouchListener {      private ImageView mImageView;      private Matrix matr

Android開發有用工具之--Log工具類

util lena 日誌 日誌信息 stat 們的 常常 我們 imp 在開發的過程中。我們常常會使用Log來輸出日誌,幫助我們來調試程序 可是有時候並不能全然滿足我們的須要 ,比方我想知道這個日誌信息是來自於哪一個包 哪一個類 所以我們封裝一個這個Log類。方便我們的

Android學習探索之Java 8 在Android 開發的應用

相關 概念 容易 並不是 min etc bstr trac flavor 前言: Java 8推出已經將近2年多了,引入很多革命性變化,加入了函數式編程的特征,使基於行為的編程成為可能,同時減化了各種設計模式的實現方式,是Java有史以來最重要的更新。但是Androi

android開發如何使用JavaMail程序

有一個 pro 會話 jpg names prot get ext 會有  javaMail,是提供給開發者處理電子郵件相關的編程接口。它是Sun發布的用來處理email的API。它可以方便地執行一些常用的郵件傳輸。我們可以基於JavaMail開發出類似於Microsoft

Android開發的各種尺度單位

href roi 放大 pla blank 區別 csdn tro 自定義 px 像素(pixel),表示屏幕上一個物理像素點 不建議直接使用 px 繪制UI,因為受像素密度的影響,以 px 為單位繪制的UI在不同手機上顯示的實際大小會不同 dp (用於定義控件

android開發——Android開發的47小知識

環境 底部 枚舉 目前 mount ram 啟動 creat ica 1、判斷sd卡是否存在 boolean sdCardExist = Environment.getExternalStorageState().equals(android.os.Environm

Android開發幾種有用的的日歷控件實現

顯示 lec 外觀 翻頁 frame 時間 lean android平臺 星期 我們大家都知道,在Android平臺3.0中才新增了日歷視圖控件,可以顯示網格狀的日歷內容,那麽對於3.0以下的版本要使用日歷控件只能借助第三方,目前用的最多的是CalendarView。 先簡

Android開發java.lang.RuntimeException: Unable to start activity ComponentInfo{xxx}

net 控件 view etc spi pos rst ack data Android開發中java.lang.RuntimeException: Unable to start activity ComponentInfo{xxx}: java.lang.NullP

Android開發dp,sp和px之間的轉換

font col art gpo ati pan ext 同時 style 本文轉載於 http://blog.csdn.net/student9128/article/details/53932470 眾所周知,在Android開發中dp和px,sp和px之間的轉換時必不

Android開發以太坊錢包生成應用程序

運行時 try super 取地址 save ras parent sel cte Android應用程序以太坊錢包生成,要做的工作不少,不過如果我們一步一步來應該也比較清楚: 1.在app/build.gradle中集成以下依賴項: compile (‘org.web3j