日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > Android >内容正文

Android

Android dex分包方案 (多dex)

發布時間:2025/3/15 Android 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Android dex分包方案 (多dex) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

原文地址:?http://my.oschina.net/853294317/blog/308583

當一個app的功能越來越復雜,代碼量越來越多,也許有一天便會突然遇到下列現象

1. 生成的apk在2.3以前的機器無法安裝,提示INSTALL_FAILED_DEXOPT

2. 方法數量過多,編譯時出錯,提示:

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


出現這種問題的原因是

1. Android2.3及以前版本用來執行dexopt(用于優化dex文件)的內存只分配了5M

2. 一個dex文件最多只支持65536個方法。


針對上述問題,也出現了諸多解決方案,使用的最多的是插件化,即將一些獨立的功能做成一個單獨的apk,當打開的時候使用DexClassLoader動態加載,然后使用反射機制來調用插件中的類和方法。這固然是一種解決問題的方案:但這種方案存在著以下兩個問題:

1. 插件化只適合一些比較獨立的模塊;

2. 必須通過反射機制去調用插件的類和方法,因此,必須搭配一套插件框架來配合使用;


由于上述問題的存在,通過不斷研究,便有了dex分包的解決方案。簡單來說,其原理是將編譯好的class文件拆分打包成兩個dex,繞過dex方法數量的限制以及安裝時的檢查,在運行時再動態加載第二個dex文件中。faceBook曾經遇到相似的問題,具體可參考:

https://www.facebook.com/notes/facebook-engineering/under-the-hood-dalvik-patch-for-facebook-for-android/10151345597798920

文中有這么一段話:

However, there was no way we could break our app up this way--too many of our classes are accessed directly by the Android framework. Instead, we needed to inject our secondary dex files directly into the system class loader。

文中說得比較簡單,我們來完善一下該方案:除了第一個dex文件(即正常apk包唯一包含的Dex文件),其它dex文件都以資源的方式放在安裝包中,并在Application的onCreate回調中被注入到系統的ClassLoader。因此,對于那些在注入之前已經引用到的類(以及它們所在的jar),必須放入第一個Dex文件中。


下面通過一個簡單的demo來講述dex分包方案,該方案分為兩步執行:


整個demo的目錄結構是這樣,我打算將SecondActivity,MyContainer以及DropDownView放入第二個dex包中,其它保留在第一個dex包。

一、編譯時分包

整個編譯流程如下:



除了框出來的兩Target,其它都是編譯的標準流程。而這兩個Target正是我們的分包操作。首先來看看spliteClasses target。



由于我們這里僅僅是一個demo,因此放到第二個包中的文件很少,就是上面提到的三個文件。分好包之后就要開始生成dex文件,首先打包第一個dex文件:?



由這里將${classes}(該文件夾下都是要打包到第一個dex的文件)打包生成第一個dex。接著生成第二個dex,并將其打包到資資源文件中:



可以看到,此時是將${secclasses}中的文件打包生成dex,并將其加入ap文件(打包的資源文件)中。到此,分包完畢,接下來,便來分析一下如何動態將第二個dex包注入系統的ClassLoader。


二、將dex分包注入ClassLoader

這里談到注入,就要談到Android的ClassLoader體系。



由上圖可以看出,在葉子節點上,我們能使用到的是DexClassLoader和PathClassLoader,通過查閱開發文檔,我們發現他們有如下使用場景:

1. 關于PathClassLoader,文檔中寫到:?Android uses this class for its system class loader and for its application class loader(s),

由此可知,Android應用就是用它來加載;

2. DexClass可以加載apk,jar,及dex文件,但PathClassLoader只能加載已安裝到系統中(即/data/app目錄下)的apk文件。


知道了兩者的使用場景,下面來分析下具體的加載原理,由上圖可以看到,兩個葉子節點的類都繼承BaseDexClassLoader中,而具體的類加載邏輯也在此類中:

BaseDexClassLoader: ?

[java]?view plaincopy

  • @Override??

  • protected?Class<?>?findClass(String?name)?throws?ClassNotFoundException?{??

  • ????List<Throwable>?suppressedExceptions?=?new?ArrayList<Throwable>();??

  • ????Class?c?=?pathList.findClass(name,?suppressedExceptions);??

  • ????if?(c?==?null)?{??

  • ????????ClassNotFoundException?cnfe?=?new?ClassNotFoundException("Didn't?find?class?\""?+?name?+?"\"?on?path:?"?+?pathList);??

  • ????????for?(Throwable?t?:?suppressedExceptions)?{??

  • ????????????cnfe.addSuppressed(t);??

  • ???????}??

  • ????????throw?cnfe;??

  • ????}??

  • ?????return?c;??

  • }??


  • 由上述函數可知,當我們需要加載一個class時,實際是從pathList中去需要的,查閱源碼,發現pathList是DexPathList類的一個實例。ok,接著去分析DexPathList類中的findClass函數,

    DexPathList:

    [java]?view plaincopy

  • public?Class?findClass(String?name,?List<Throwable>?suppressed)?{??

  • ????for?(Element?element?:?dexElements)?{??

  • ????????DexFile?dex?=?element.dexFile;??

  • ????????if?(dex?!=?null)?{??

  • ????????????Class?clazz?=?dex.loadClassBinaryName(name,?definingContext,?suppressed);??

  • ????????????if?(clazz?!=?null)?{??

  • ????????????????return?clazz;??

  • ????????????}??

  • ????????}??

  • ???}??

  • ????if?(dexElementsSuppressedExceptions?!=?null)?{??

  • ????????suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));??

  • ????}??

  • ????return?null;??

  • }??

  • 上述函數的大致邏輯為:遍歷一個裝在dex文件(每個dex文件實際上是一個DexFile對象)的數組(Element數組,Element是一個內部類),然后依次去加載所需要的class文件,直到找到為止。

    看到這里,注入的解決方案也就浮出水面,假如我們將第二個dex文件放入Element數組中,那么在加載第二個dex包中的類時,應該可以直接找到。

    帶著這個假設,來完善demo。

    在我們自定義的BaseApplication的onCreate中,我們執行注入操作:

    [java]?view plaincopy

  • public?String?inject(String?libPath)?{??

  • ????boolean?hasBaseDexClassLoader?=?true;??

  • ????try?{??

  • ????????Class.forName("dalvik.system.BaseDexClassLoader");??

  • ????}?catch?(ClassNotFoundException?e)?{??

  • ????????hasBaseDexClassLoader?=?false;??

  • ????}??

  • ????if?(hasBaseDexClassLoader)?{??

  • ????????PathClassLoader?pathClassLoader?=?(PathClassLoader)sApplication.getClassLoader();??

  • ????????DexClassLoader?dexClassLoader?=?new?DexClassLoader(libPath,?sApplication.getDir("dex",?0).getAbsolutePath(),?libPath,?sApplication.getClassLoader());??

  • ????????try?{??

  • ????????????Object?dexElements?=?combineArray(getDexElements(getPathList(pathClassLoader)),?getDexElements(getPathList(dexClassLoader)));??

  • ????????????Object?pathList?=?getPathList(pathClassLoader);??

  • ????????????setField(pathList,?pathList.getClass(),?"dexElements",?dexElements);??

  • ????????????return?"SUCCESS";??

  • ????????}?catch?(Throwable?e)?{??

  • ????????????e.printStackTrace();??

  • ????????????return?android.util.Log.getStackTraceString(e);??

  • ????????}??

  • ????}??

  • ????return?"SUCCESS";??

  • }???

  • 這是注入的關鍵函數,分析一下這個函數:

    參數libPath是第二個dex包的文件信息(包含完整路徑,我們當初將其打包到了assets目錄下),然后將其使用DexClassLoader來加載(這里為什么必須使用DexClassLoader加載,回顧以上的使用場景),然后通過反射獲取PathClassLoader中的DexPathList中的Element數組(已加載了第一個dex包,由系統加載),以及DexClassLoader中的DexPathList中的Element數組(剛將第二個dex包加載進去),將兩個Element數組合并之后,再將其賦值給PathClassLoader的Element數組,到此,注入完畢。


    現在試著啟動app,并在TestUrlActivity(在第一個dex包中)中去啟動SecondActivity(在第二個dex包中),啟動成功。這種方案是可行。


    但是使用dex分包方案仍然有幾個注意點:

    1. 由于第二個dex包是在Application的onCreate中動態注入的,如果dex包過大,會使app的啟動速度變慢,因此,在dex分包過程中一定要注意,第二個dex包不宜過大。

    2. 由于上述第一點的限制,假如我們的app越來越臃腫和龐大,往往會采取dex分包方案和插件化方案配合使用,將一些非核心獨立功能做成插件加載,核心功能再分包加載。

    總結

    以上是生活随笔為你收集整理的Android dex分包方案 (多dex)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。