<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                #### **背景** 隨著應用不斷迭代,業務線的擴展,應用越來越大(比如集成了各種第三方sdk或者公共支持的jar包,項目耦合性高,重復作用的類越來越多),相信很多人都遇到過如下的錯誤: ~~~ UNEXPECTED TOP-LEVEL EXCEPTION: java.lang.IllegalArgumentException: method ID not in [0, 0xffff]: 65536 at com.android.dx.merge.DexMerger$6.updateIndex(DexMerger.java:501) at com.android.dx.merge.DexMerger$IdMerger.mergeSorted(DexMerger.java:282) at com.android.dx.merge.DexMerger.mergeMethodIds(DexMerger.java:490) at com.android.dx.merge.DexMerger.mergeDexes(DexMerger.java:167) at com.android.dx.merge.DexMerger.merge(DexMerger.java:188) at com.android.dx.command.dexer.Main.mergeLibraryDexBuffers(Main.java:439) at com.android.dx.command.dexer.Main.runMonoDex(Main.java:287) at com.android.dx.command.dexer.Main.run(Main.java:230) at com.android.dx.command.dexer.Main.main(Main.java:199) at com.android.dx.command.Main.main(Main.java:103) ~~~ 沒錯,**你的應用中的Dex 文件方法數超過了最大值65536的上限,簡單來說,應用爆棚了**。 那么讓我們看一下為什么會引起這種錯誤: 在Android系統中,一個App的所有代碼都在一個Dex文件里面。Dex是一個類似Jar的存儲了多有Java編譯字節碼的歸檔文件。因為Android系統使用Dalvik虛擬機,所以需要把使用Java Compiler編譯之后的class文件轉換成Dalvik能夠執行的class文件。這里需要強調的是,Dex和Jar一樣是一個歸檔文件,里面仍然是Java代碼對應的字節碼文件。當Android系統啟動一個應用的時候,有一步是對Dex進行優化,這個過程有一個專門的工具來處理,叫DexOpt。DexOpt的執行過程是在第一次加載Dex文件的時候執行的。這個過程會生成一個ODEX文件,即Optimised Dex。執行ODex的效率會比直接執行Dex文件的效率要高很多。 但是在早期的Android系統中,DexOpt的LinearAlloc存在著限制: Android 2.2和2.3的緩沖區只有5MB,Android 4.x提高到了8MB或16MB。**當方法數量過多(方法數可能并沒有超過65536上限)導致超出緩沖區大小時,會造成dexopt崩潰,導致無法安裝**。 另外由于DEX文件格式限制,一個DEX文件中method個數采用使用原生類型short來索引文件中的方法,也就是最多表達65536個method,field/class的個數也均有此限制。對于DEX文件,則是將工程所需全部class文件合并且壓縮到一個DEX文件期間,也就是**Android打包的DEX過程中, 單個DEX文件可被引用的方法總數被限制為65536(自己開發以及所引用的Android Framework和第三方類庫的代碼)**。 #### **解決方案** 目前比較常用的方法: (1) 應用插件化,比如使用[任玉剛](http://blog.csdn.net/singwhatiwanna)參與開發的插件化框架 :[DL : Apk動態加載框架](https://github.com/singwhatiwanna/dynamic-load-apk ) (2) 分割Dex,多工程: 把所需要的.class文件或者是Jar文件和一些源碼一起編譯生成一個Jar文件。然后使用Android SDK提供的dx工具把Jar文件轉成Dex文件。(可參考facebook:[Under the Hood: Dalvik patch for Facebook for Android](https://www.facebook.com/notes/facebook-engineering/under-the-hood-dalvik-patch-for-facebook-for-android/10151345597798920),這里邊還可以看到在2.3上動態改變LinearAlloc緩沖的解決思路) 這兩種方法并不沖突,**插件化除了解決應用爆棚,還有很多其他的優點**。 當然,Google看來也意識到了目前應用方法數爆棚的問題, 目前在已經在API 21(Android 5.0)中提供了通用的解決方案,那就是android-support-multidex.jar. 這個jar包最低可以支持到API 4的版本(Android L及以上版本會默認支持mutidex). #### **Dex自動拆包及動態加載** **MultiDex帶來的問題** 在第一版本采用MultiDex方案上線后,在Dalvik下[MultiDex](https://android.googlesource.com/platform/frameworks/MultiDex/)帶來了下列幾個問題: 1. 在冷啟動時因為需要安裝DEX文件,如果DEX文件過大時,處理時間過長,很容易引發ANR(Application Not Responding); 2. 采用MultiDex方案的應用可能不能在低于Android 4.0 (API level 14) 機器上啟動,這個主要是因為Dalvik linearAlloc的一個bug (Issue 22586); 3. 采用MultiDex方案的應用因為需要申請一個很大的內存,在運行時可能導致程序的崩潰,這個主要是因為Dalvik linearAlloc 的一個限制(Issue 78035). 這個限制在 Android 4.0 (API level 14)已經增加了, 應用也有可能在低于 Android 5.0 (API level 21)版本的機器上觸發這個限制; 而在ART下MultiDex是不存在這個問題的,這主要是因為ART下采用Ahead-of-time (AOT) compilation技術,系統在APK的安裝過程中會使用自帶的dex2oat工具對APK中可用的DEX文件進行編譯并生成一個可在本地機器上運行的文件,這樣能提高應用的啟動速度,因為是在安裝過程中進行了處理這樣會影響應用的安裝速度,對ART感興趣的可以參考一下[ART和Dalvik的區別](https://source.android.com/devices/tech/dalvik/index.html). MultiDex的基本原理是把通過DexFile來加載Secondary DEX,并存放在BaseDexClassLoader的DexPathList中。 **BaseDexClassLoader:findClass** ~~~ @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; } ~~~ 下面代碼片段為怎么通過DexFile來加載Secondary DEX并放到BaseDexClassLoader的DexPathList中: **下面的這個方法暫時還沒有找到出處,不知道是不是美團的Robust框架中** ~~~ private static void install(ClassLoader loader, List<File> 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. */ Field pathListField = findField(loader, "pathList"); Object dexPathList = pathListField.get(loader); ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>(); expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList, new ArrayList<File>(additionalClassPathEntries), optimizedDirectory, suppressedExceptions)); try { 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); } } catch(Exception e) { } } ~~~ **Dex自動拆包及動態加載方案簡介** 通過查看MultiDex的源碼,我們發現MultiDex在冷啟動時容易導致ANR的瓶頸, 在2.1版本之前的Dalvik的VM版本中, MultiDex的安裝大概分為幾步,第一步打開apk這個zip包,第二步把MultiDex的dex解壓出來(除去Classes.dex之外的其他DEX,例如:classes2.dex, classes3.dex等等),因為android系統在啟動app時只加載了第一個Classes.dex,其他的DEX需要我們人工進行安裝,第三步通過反射進行安裝,這三步其實都比較耗時, 為了解決這個問題我們**考慮是否可以把DEX的加載放到一個異步線程中,這樣冷啟動速度能提高不少,同時能夠減少冷啟動過程中的ANR,對于Dalvik linearAlloc的一個缺陷([Issue 22586](http://b.android.com/22586))和限制([Issue 78035](http://b.android.com/78035)),我們考慮是否可以人工對DEX的拆分進行干預,使每個DEX的大小在一定的合理范圍內,這樣就減少觸發Dalvik linearAlloc的缺陷和限制**; 為了實現這幾個目的,我們需要解決下面三個問題: 1. 在打包過程中如何產生多個的DEX包? 2. 如果做到動態加載,怎么決定哪些DEX動態加載呢? 3. 如果啟動后在工作線程中做動態加載,如果沒有加載完而用戶進行頁面操作需要使用到動態加載DEX中的class怎么辦? 我們首先來分析如何解決第一個問題,在使用MultiDex方案時,我們知道BuildTool會自動把代碼進行拆成多個DEX包,并且可以通過配置文件來控制哪些代碼放到第一個DEX包中, 下圖是Android的打包流程示意圖: ![](https://tech.meituan.com/img/mt_android_auto_split_dex/android_packaging.png) 圖 1 Android的打包流程示意圖 為了實現產生多個DEX包,我們可以在生成DEX文件的這一步中, 在Ant或gradle中自定義一個Task來干預DEX產生的過程,從而產生多個DEX,下圖是在ant和gradle中干預產生DEX的自定task的截圖: ~~~ tasks.whenTaskAdded { task -> if (task.name.startsWith('proguard') && (task.name.endsWith('Debug') || task.name.endsWith('Release'))) { task.doLast { makeDexFileAfterProguardJar(); } task.doFirst { delete "${project.buildDir}/intermediates/classes-proguard"; String flavor = task.name.substring('proguard'.length(), task.name.lastIndexOf(task.name.endsWith('Debug') ? "Debug" : "Release")); generateMainIndexKeepList(flavor.toLowerCase()); } } else if (task.name.startsWith('zipalign') && (task.name.endsWith('Debug') || task.name.endsWith('Release'))) { task.doFirst { ensureMultiDexInApk(); } } } ~~~ 上一步解決了如何打包出多個DEX的問題了,那我們**該怎么該根據什么來決定哪些class放到Main DEX,哪些放到Secondary DEX呢**(這里的Main DEX是指在2.1版本的Dalvik VM之前由android系統在啟動apk時自己主動加載的Classes.dex,而**Secondary DEX是指需要我們自己安裝進去的DEX,例如:Classes2.dex, Classes3.dex等)**, 這個需要分析出**放到Main DEX中的class依賴,需要確保把Main DEX中class所有的依賴都要放進來,否則在啟動時會發生ClassNotFoundException**, 這里我們的**方案是把Service、Receiver、Provider涉及到的代碼都放到Main DEX中,而把Activity涉及到的代碼進行了一定的拆分,把首頁Activity、Laucher Activity、歡迎頁的Activity、城市列表頁Activity等所依賴的class放到了Main DEX中,把二級、三級頁面的Activity以及業務頻道的代碼放到了Secondary DEX中,為了減少人工分析class的依賴所帶了的不可維護性和高風險性,我們編寫了一個能夠自動分析Class依賴的腳本, 從而能夠保證Main DEX包含class以及他們所依賴的所有class都在其內,這樣這個腳本就會在打包之前自動分析出啟動到Main DEX所涉及的所有代碼,保證Main DEX運行正常。** 隨著第二個問題的迎刃而解,我們來到了比較棘手的**第三問題,如果我們在后臺加載Secondary DEX過程中,用戶點擊界面將要跳轉到使用了在Secondary DEX中class的界面, 那此時必然發生ClassNotFoundException**, 那怎么解決這個問題呢,在所有的Activity跳轉代碼處添加判斷Secondary DEX是否加載完成?這個方法可行,但工作量非常大; 那有沒有更好的解決方案呢?我們**通過分析Activity的啟動過程,發現Activity是由ActivityThread 通過Instrumentation來啟動的,我們是否可以在Instrumentation中做一定的手腳呢?通過分析代碼ActivityThread和Instrumentation發現,Instrumentation有關Activity啟動相關的方法大概有:execStartActivity、newActivity等等,這樣我們就可以在這些方法中添加代碼邏輯進行判斷這個Class是否加載了,如果加載則直接啟動這個Activity,如果沒有加載完成則啟動一個等待的Activity顯示給用戶,然后在這個Activity中等待后臺Secondary DEX加載完成,完成后自動跳轉到用戶實際要跳轉的Activity;這樣在代碼充分解耦合,以及每個業務代碼能夠做到顆粒化的前提下,我們就做到Secondary DEX的按需加載了**, 下面是Instrumentation添加的部分關鍵代碼: ~~~ public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode) { ActivityResult activityResult = null; String className; if (intent.getComponent() != null) { className = intent.getComponent().getClassName(); } else { ResolveInfo resolveActivity = who.getPackageManager().resolveActivity(intent, 0); if (resolveActivity != null && resolveActivity.activityInfo != null) { className = resolveActivity.activityInfo.name; } else { className = null; } } if (!TextUtils.isEmpty(className)) { boolean shouldInterrupted = !MeituanApplication.isDexAvailable(); if (MeituanApplication.sIsDexAvailable.get() || mByPassActivityClassNameList.contains(className)) { shouldInterrupted = false; } if (shouldInterrupted) { Intent interruptedIntent = new Intent(mContext, WaitingActivity.class); activityResult = execStartActivity(who, contextThread, token, target, interruptedIntent, requestCode); } else { activityResult = execStartActivity(who, contextThread, token, target, intent, requestCode); } } else { activityResult = execStartActivity(who, contextThread, token, target, intent, requestCode); } return activityResult; } public Activity newActivity(Class<?> clazz, Context context, IBinder token, Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, Object lastNonConfigurationInstance) throws InstantiationException, IllegalAccessException { String className = ""; Activity newActivity = null; if (intent.getComponent() != null) { className = intent.getComponent().getClassName(); } boolean shouldInterrupted = !MeituanApplication.isDexAvailable(); if (MeituanApplication.sIsDexAvailable.get() || mByPassActivityClassNameList.contains(className)) { shouldInterrupted = false; } if (shouldInterrupted) { intent = new Intent(mContext, WaitingActivity.class); newActivity = mBase.newActivity(clazz, context, token, application, intent, info, title, parent, id, lastNonConfigurationInstance); } else { newActivity = mBase.newActivity(clazz, context, token, application, intent, info, title, parent, id, lastNonConfigurationInstance); } return newActivity; } ~~~ 實際應用中我們還遇到另外一個比較棘手的問題, 就是**Field的過多的問題**,Field過多是由我們目前采用的代碼組織結構引入的,我們為了方便多業務線、多團隊并發協作的情況下開發,我們采用的aar的方式進行開發,并同時在aar依賴鏈的最底層引入了一個通用業務aar,而這個通用業務aar中包含了很多資源,而ADT14以及更高的版本中對Library資源處理時,Library的R資源不再是static final的了,[詳情請查看google官方說明](http://tools.android.com/tips/non-constant-fields),這樣在最終打包時Library中的R沒法做到內聯,這樣**帶來了R field過多的情況,導致需要拆分多個Secondary DEX**,**為了解決這個問題**我們采用的是**在打包過程中利用腳本把Libray中R field(例如ID、Layout、Drawable等)的引用替換成常量,然后刪去Library中R.class中的相應Field**。 #### **總結** 上面就是我們在使用MultiDex過程中進化而來的DEX自動化拆包的方案, 這樣我們就可以通過腳本控制來進行自動化的拆分DEX,然后在運行時自由的加載Secondary DEX,既能保證冷啟動速度,又能減少運行時的內存占用。 #### **參考文章** [美團Android DEX自動拆包及動態加載簡介](https://tech.meituan.com/mt-android-auto-split-dex.html) [Android 使用android-support-multidex解決Dex超出方法數的限制問題,讓你的應用不再爆棚](http://blog.csdn.net/t12x3456/article/details/40837287) [使用Multidex來解決方法數越界](http://www.hmoore.net/alex_wsc/artist/481985) [Google官網——配置方法數超過 64K 的應用](https://developer.android.com/tools/building/multidex.html#mdex-gradle) [Multi-dex to rescue from the infamous 65536 methods limit](http://blog.osom.info/2014/10/multi-dex-to-rescue-from-infamous-65536.html) [dex分包變形記](http://dev.qq.com/topic/5913db5c29d8be2a14b64da8) [Android拆分與加載Dex的多種方案對比](http://mp.weixin.qq.com/s?__biz=MzAwNDY1ODY2OQ==&mid=207151651&idx=1&sn=9eab282711f4eb2b4daf2fbae5a5ca9a&3rd=MzA3MDU4NTYzMw==&scene=6#rd)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看