編輯:關於Android編程
作為一名Android開發者,相信你對Android方法數不能超過65K的限制應該有所耳聞,隨著應用程序功能不斷的豐富,總有一天你會遇到一個異常:
Conversion to Dalvik format failed:Unable toexecute dex: method ID not in [0, 0xffff]: 65536
可能有些同學會說,解決這個問題很簡單,我們只需要在Project.proterty中配置一句話就Ok啦,
dex.force.jumbo=true
是的,加入了這句話,確實可以讓你的應用通過編譯,但是在一些2.3系統的機器上很容易出現
INSTALL_FAILED_DEXOPT異常
對於以上兩個異常,我們先來分析一下原因:
1、Android系統中,一個Dex文件中存儲方法id用的是short類型數據,所以導致你的dex中方法不能超過65k
2、在2.3系統之前,虛擬機內存只分配了5M
知道了原因,我們就來一個個的解決上面的問題,首先對於65k的問題,我們在應用層是無法改變android系統的結構的,所以我們無法將數據類型從short改變為int或者其他類型,也就是說一個dex中的方法數不能超過65k是我們無法逾越的鴻溝,我們只能減少一個dex中的方法數,首先最容易想到的方案就是去掉一些無用的Jar包,以及將一些屬性設置為public,從而可以去掉get/set方法,這種方法只能臨時解決問題,隨著時間的推移,總有一天還是會出現方法數超過65k的,畢竟一個應用一般是在加功能,不會減功能。
下面我來向大家介紹兩種主流的解決方案,一種是以微信為代表的,將一些功能做成插件,動態加載,另一種方案是以facebook為代表的分包方案,將一個apk中的dex文件分割成多個dex文件,然後動態的去加載dex文件。其實這兩種方案的核心思想是一樣的,插件是把未來要開發的新功能做成apk和dex動態加載,而分包方案是將已經完成的功能分成多個dex文件動態加載,其實我個人覺得插件方案比分包方案更好的解決了65k的問題,因為插件方案不僅能夠解決65k問題,還能讓我們的應用體積減小,而分包只能解決65k的問題。
關於插件開發,做成動態加載,我在很早之前一篇文章中就寫過其基本思想,有興趣的同學可以看看
《實現Android 動態加載APK(Fragment or Activity實現)》
下面我們重點介紹分包機制
我們知道一個apk文件裡面有一個dex文件,這個dex文件裡面都是經過優化了的class文件,所謂分包,就是講一個dex文件分成多個dex文件,這裡我們約定一下,第一個dex叫做main.dex,第二個叫做second.dex,通常在分包的時候,我們需要將應用啟動就需要使用的類放入到main.dex中,把不是立馬就需要使用的類放入到second.dex中,對於Android系統,他只會默認加載main.dex的,second.dex對於他來說可能只是一個資源文件,它是不會主動去加載second.dex,所以我在應用啟動的過程中,我們需要為second.dex創建好一個類加載器,便於我在使用second.dex中的類時,能夠裡面加載該類。
關於如何加載second.dex也有好多做法,用的比較多的主要有一下幾種
1、最簡單的做法就是使用DexClassLoader進行加載,並將該DexClassLoader的父加載器設置為PathClassLoader
2、使用DexClassLoader加載,並將DexClassLoader的父加載器設置成PathClassLoader的父加載器,將PahtClassLoader的父加載器設置成DexClassLoader,仔細品味一下1和2的區別
3、將second.dex的路徑放入到PathClassLoader的加載路徑中
對於第2中方案,在有一種情況下是不能使用的,比如當second.dex通過DexClassLoader加載,但是second.dex中使用了一個類,這個類在main.dex中,這個時候就會拋出類找不到的異常,所以這種方案只能擁有second.dex不會用到main.dex類的時候
以上說的都是理論,下面我們來實戰一下
我這裡會介紹兩種方案,一種是基於gradle構建Android項目,一種是基於Ant構建Android項目
方案一:基於gradle構建Android項目,並實現分包
環境要求:AndroidStudio0.9以上,gradle插件0.14.2以上
1、如果你的工程在eclipse中,那麼你需要將該工程導入到Android中,此時需要你升級adt22以上
2、打開你工程的build.gradle文件,檢查gradle插件是否是0.14.2版本之後,因為0.14.2之後gradle插件才支持分包

3、打開工程下某一個Moudle的build.gradle文件,添加對android-support-multidex.jar的依賴

4、去掉第三方jar包中重復的類

5、設置虛擬機堆內存空間大小,避免在編譯期間OOM

6、gradle構建項目時,貌似默認是不會將so庫加入工程的,所以為了避免此種情況發生,我們需要制定so庫目錄,對於從eclipse轉換過來的工程,還需要制定src和資源文件路徑

7、如果你的項目依賴了其他庫, 分別在各個庫工程中加入 multiDexEnabled = true 和 jniLibs.srcDirs =['libs']兩個配置即可
8、如果你的項目沒有自定義Application,那麼你在AndroidManifest.xml中使用MultiDexApplication即可,如果你的項目有自定義Application,並且是繼承是Application,那麼只需要改為繼承MultiDexApplication即可,如果你的項目時繼承的其他Application,那麼你需要重寫
attachBaseContext
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
}
經過上述配置,你的項目應該是已經成功分包了。如果分包成功,那麼你解壓你的apk文件,會發現有兩個dex文件,通過上述的配置過程,我們發現此方案我們無法控制哪些類在main.dex中,哪些類在second.dex中,通過此種方案配置分包,可以兼容API4-API20.其加載second.dex采用的是上述方案中的3

下面我們來看看基於Ant構建Android項目,並實現分包過程
在上述方案中,由於我們無法看到gradle構建項目的腳本,所以我們無法控制哪些類在第一個dex,哪些類在第二個dex,此方案中,我們采用Ant構建,Ant是允許用戶自己定義構建方案的,比如我們可以通過自定義構建方案,將項目中某些第三方jar包放入到second.dex中,關於這個如何實現,請參考開源項目吧
https://github.com/mmin18/Dex65536.git
由於該項目加載second.dex所采用的方案是上述方案2,比如second.dex中的某些第三方jar包依賴main.dex中的某些類,這種方案就會實現,所以在此我將此方案去掉,換成了方案3,也就是將second.dex的路徑設置到PathClassLoader的加載路徑中
我只給出Android 4.4中的解決方案,其他系統大同小異
加載second.dex方法
/**
@param loader
PathClassLoader
@additionalClassPathEntries
要被加載的dex文件,這裡就是我們的second.dex
@optimizedDirectory
就是dex文件解壓的目錄
*/
private static void install(ClassLoader loader, List 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.
*/
//通過反射找到pathList的值
Field pathListField = findField(loader, "pathList");
Object dexPathList = pathListField.get(loader);
ArrayList suppressedExceptions = new ArrayList();
//將second.dex 加入到PathClassLoader的加載路徑中
expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList,
new ArrayList(additionalClassPathEntries), optimizedDirectory,
suppressedExceptions));
if (suppressedExceptions.size() > 0) {
for (IOException e : suppressedExceptions) {
Log.w(TAG, "Exception in makeDexElement", e);
}
Field suppressedExceptionsField =
findField(loader, "dexElementsSuppressedExceptions");
IOException[] dexElementsSuppressedExceptions =
(IOException[]) suppressedExceptionsField.get(loader);
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(loader, dexElementsSuppressedExceptions);
}
}
分包成功後,解壓apk文件,進入assert文件夾,我們看到如下結構,libs.apk就是第三方jar編譯後形成的dex文件

至於該方法中用到的一些方法,可以到android-support-multidex.jar中找到,這裡就不都貼出來了,如果那裡沒有寫清楚,歡迎留言討論...
=================================================================================================================================
我只做了一個操作竟然神奇i地解決了eclipse中的這個問題:
工程右鍵-->Build Path-->Configure Build Path-->Order and Export中把原來選中的Android Private Library改為不選中。。。。。
估計這樣不會徹底解決此問題,想要徹底解決還是用分包什麼的
Android 動畫系列之屬性(Property)動畫詳解
前言今天有時間來繼續寫寫屬性動畫。簡介眾所周知,屬性動畫是Android3.0版本開始的,一個東西的推出肯定是有它的道理的,那為什麼前面已經有逐幀和補間動畫了還要推出屬性
Android的下拉刷新/上拉加載控件
事實上之所以會有之前的那篇博文的出現,是起因於前段時間自己在寫一個練手的App時很快就遇到這種需求。其實我們可以發現類似這樣下拉刷新、上拉加載的功能正在變得越來越普遍,可
【黑馬Android】(07)多線程下載的原理/開源項目xutils/顯示意圖/隱式意圖/人品計算器/開啟activity獲取返回值
多線程下載的原理司馬光砸缸,多開幾個小水管,搶救小朋友。 import java.io.BufferedReader;import java.io.File;i
Qt on Android: Qt Quick 簡單教程
上一篇《Qt on Android: Qt Quick 之 Hello World 圖文詳解》我們已經分別在電腦和 Android 手機上運行了第一個 Qt Quick