完全解決Android 應用方法數不能超過65K的問題

 

做爲一名Android開發者,相信你對Android方法數不能超過65K的限制應該有所耳聞,隨着應用程序功能不斷的豐富,總有一天你會遇到一個異常:java

 

Conversion to Dalvik format failed:Unable toexecute dex: method ID not in [0, 0xffff]: 65536android

可能有些同窗會說,解決這個問題很簡單,咱們只須要在Project.proterty中配置一句話就Ok啦,git

dex.force.jumbo=true
github

是的,加入了這句話,確實可讓你的應用經過編譯,可是在一些2.3系統的機器上很容易出現微信

INSTALL_FAILED_DEXOPT異常
app

 

對於以上兩個異常,咱們先來分析一下緣由:eclipse

一、Android系統中,一個Dex文件中存儲方法id用的是short類型數據,因此致使你的dex中方法不能超過65kide

二、在2.3系統以前,虛擬機內存只分配了5Mgradle

 

知道了緣由,咱們就來一個個的解決上面的問題,首先對於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實現)》

http://blog.csdn.net/yuanzeyao/article/details/38565345

 

下面咱們重點介紹分包機制

咱們知道一個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也有好多作法,用的比較多的主要有一下幾種

一、最簡單的作法就是使用DexClassLoader進行加載,並將該DexClassLoader的父加載器設置爲PathClassLoader

二、使用DexClassLoader加載,並將DexClassLoader的父加載器設置成PathClassLoader的父加載器,將PahtClassLoader的父加載器設置成DexClassLoader,仔細品味一下1和2的區別

三、將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以上

 

一、若是你的工程在eclipse中,那麼你須要將該工程導入到Android中,此時須要你升級adt22以上

二、打開你工程的build.gradle文件,檢查gradle插件是不是0.14.2版本以後,由於0.14.2以後gradle插件才支持分包

 

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

四、去掉第三方jar包中重複的類

 

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

 

六、gradle構建項目時,貌似默認是不會將so庫加入工程的,因此爲了不此種狀況發生,咱們須要制定so庫目錄,對於從eclipse轉換過來的工程,還須要制定src和資源文件路徑

 

七、若是你的項目依賴了其餘庫, 分別在各個庫工程中加入 multiDexEnabled = true 和 jniLibs.srcDirs =['libs']兩個配置便可

八、若是你的項目沒有自定義Application,那麼你在AndroidManifest.xml中使用MultiDexApplication便可,若是你的項目有自定義Application,而且是繼承是Application,那麼只須要改成繼承MultiDexApplication便可,若是你的項目時繼承的其餘Application,那麼你須要重寫

attachBaseContext

 

[java]  view plain copy print ? 在CODE上查看代碼片 派生到個人代碼片
 
  1. @Override  
  2. protected void attachBaseContext(Context base) {  
  3.     super.attachBaseContext(base);  
  4.     MultiDex.install(this);  
  5. }  


通過上述配置,你的項目應該是已經成功分包了。若是分包成功,那麼你解壓你的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方法

 

[java]  view plain copy print ? 在CODE上查看代碼片 派生到個人代碼片
 
  1. /** 
  2.     @param loader 
  3.          PathClassLoader 
  4.     @additionalClassPathEntries  
  5.          要被加載的dex文件,這裏就是咱們的second.dex 
  6.     @optimizedDirectory 
  7.          就是dex文件解壓的目錄 
  8. */  
  9.  private static void install(ClassLoader loader, List<File> additionalClassPathEntries,  
  10.                 File optimizedDirectory)  
  11.                         throws IllegalArgumentException, IllegalAccessException,  
  12.                         NoSuchFieldException, InvocationTargetException, NoSuchMethodException {  
  13.             /* The patched class loader is expected to be a descendant of 
  14.              * dalvik.system.BaseDexClassLoader. We modify its 
  15.              * dalvik.system.DexPathList pathList field to append additional DEX 
  16.              * file entries. 
  17.              */  
  18.              //經過反射找到pathList的值  
  19.             Field pathListField = findField(loader, "pathList");  
  20.             Object dexPathList = pathListField.get(loader);  
  21.             ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();  
  22.             //將second.dex 加入到PathClassLoader的加載路徑中  
  23.             expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList,  
  24.                     new ArrayList<File>(additionalClassPathEntries), optimizedDirectory,  
  25.                     suppressedExceptions));  
  26.             if (suppressedExceptions.size() > 0) {  
  27.                 for (IOException e : suppressedExceptions) {  
  28.                     Log.w(TAG, "Exception in makeDexElement", e);  
  29.                 }  
  30.                 Field suppressedExceptionsField =  
  31.                         findField(loader, "dexElementsSuppressedExceptions");  
  32.                 IOException[] dexElementsSuppressedExceptions =  
  33.                         (IOException[]) suppressedExceptionsField.get(loader);  
  34.   
  35.                 if (dexElementsSuppressedExceptions == null) {  
  36.                     dexElementsSuppressedExceptions =  
  37.                             suppressedExceptions.toArray(  
  38.                                     new IOException[suppressedExceptions.size()]);  
  39.                 } else {  
  40.                     IOException[] combined =  
  41.                             new IOException[suppressedExceptions.size() +  
  42.                                             dexElementsSuppressedExceptions.length];  
  43.                     suppressedExceptions.toArray(combined);  
  44.                     System.arraycopy(dexElementsSuppressedExceptions, 0, combined,  
  45.                             suppressedExceptions.size(), dexElementsSuppressedExceptions.length);  
  46.                     dexElementsSuppressedExceptions = combined;  
  47.                 }  
  48.   
  49.                 suppressedExceptionsField.set(loader, dexElementsSuppressedExceptions);  
  50.             }  
  51.         }  

 

分包成功後,解壓apk文件,進入assert文件夾,咱們看到以下結構,libs.apk就是第三方jar編譯後造成的dex文件

對於上面提到的第二個問題INSTALL_FAILED_DEXOPT,根本緣由就是2.3版本以前dalvik虛擬機的內存只有5M,因此不管是插件方案仍是分包方案在某些手機上仍是會遇到該問題,畢竟咱們僅僅是減小了每一個dex中包的數量,可是方法總數是沒有減小的,因此解決此問題的根本方法就是修改虛擬機內存至8M,這個需求在Java層是沒法實現,可是能夠在c層實現,具體實現流程能夠參考開源項目:

https://github.com/viilaismonster/LinearAllocFix.git

 

 

 

至於該方法中用到的一些方法,能夠到android-support-multidex.jar中找到,這裏就不都貼出來了,若是那裏沒有寫清楚,歡迎留言討論...

 

 

原創 :http://blog.csdn.net/yuanzeyao/article/details/41809423

相關文章
相關標籤/搜索