使用Proto3作為開發(fā)相當(dāng)折騰矗钟,經(jīng)常會(huì)出現(xiàn)一些想不到的問題新症,網(wǎng)上的資源看著比較少,所以我都記錄一下開發(fā)中遇到的問題虐块,以便自己遇到可以馬上解決俩滥,或者幫助一下其他人,或者你們看見我的有誤可以幫我糾正或者優(yōu)化一下贺奠,互相進(jìn)步霜旧。
這次出現(xiàn)的問題是:隨著項(xiàng)目的越來越大,Protocol Buffer的結(jié)構(gòu)文件也補(bǔ)充越來越多儡率,編譯生成相關(guān)的java文件之后發(fā)現(xiàn)單個(gè)文件居然有1630多K了
有 5萬 多行
當(dāng)我拖進(jìn)As項(xiàng)目中去的時(shí)候挂据,要等好半天編輯器才開始反應(yīng)過來,
運(yùn)行的時(shí)候也是沒有問題的儿普,但是到打包的時(shí)候出現(xiàn)了這個(gè)錯(cuò)誤:
Error:Execution failed for task ':app:transformClassesWithDexForRelease'.
> 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
簡單的翻譯就是 配置方法數(shù)超過 64K
查了一下官方手冊(cè)崎逃,早期版本的構(gòu)建系統(tǒng)按如下方式報(bào)告這一錯(cuò)誤:
Conversion to Dalvik format failed:
Unable to execute dex: method ID not in [0, 0xffff]: 65536
較新版本的 Android 構(gòu)建系統(tǒng)雖然顯示的錯(cuò)誤不同,但指示的是同一問題:
trouble writing output:
Too many field references: 131000; max is 65536.
You may try using --multi-dex option.
這些錯(cuò)誤狀況都會(huì)顯示下面這個(gè)數(shù)字:65,536眉孩。這個(gè)數(shù)字很重要个绍,因?yàn)樗淼氖菃蝹€(gè)Dalvik Executable (DEX) 字節(jié)碼文件內(nèi)的代碼可調(diào)用的引用總數(shù)勒葱。本頁介紹如何通過啟用被稱為 Dalvik 可執(zhí)行文件分包的應(yīng)用配置來越過這一限制,使您的應(yīng)用能夠構(gòu)建并讀取 Dalvik 可執(zhí)行文件分包 DEX 文件巴柿。
這里普及一些知識(shí):
關(guān)于 64K 引用限制
Android 應(yīng)用 (APK) 文件包含 Dalvik Executable (DEX) 文件形式的可執(zhí)行字節(jié)碼文件凛虽,其中包含用來運(yùn)行您的應(yīng)用的已編譯代碼。Dalvik Executable 規(guī)范將可在單個(gè) DEX 文件內(nèi)可引用的方法總數(shù)限制在 65,536篮洁,其中包括 Android 框架方法涩维、庫方法以及您自己代碼中的方法。在計(jì)算機(jī)科學(xué)領(lǐng)域內(nèi)袁波,術(shù)語千(簡稱 K)表示 1024(或 2^10)瓦阐。由于 65,536 等于 64 X 1024,因此這一限制也稱為“64K 引用限制”篷牌。
Android 5.0 之前版本的 Dalvik 可執(zhí)行文件分包支持
Android 5.0(API 級(jí)別 21)之前的平臺(tái)版本使用 Dalvik 運(yùn)行時(shí)來執(zhí)行應(yīng)用代碼睡蟋。默認(rèn)情況下,Dalvik 限制應(yīng)用的每個(gè) APK 只能使用單個(gè) classes.dex
字節(jié)碼文件枷颊。要想繞過這一限制戳杀,您可以使用 Dalvik 可執(zhí)行文件分包支持庫,它會(huì)成為您的應(yīng)用主要 DEX 文件的一部分夭苗,然后管理對(duì)其他 DEX 文件及其所包含代碼的訪問信卡。
注:如果您的項(xiàng)目配置時(shí)所面向的 Dalvik 可執(zhí)行文件分包使用的是 minSdkVersion 20
或更低版本,并且您將其部署到運(yùn)行 Android 4.4(API 級(jí)別 20)或更低版本的目標(biāo)設(shè)備上题造,則 Android Studio 會(huì)停用 Instant Run傍菇。
Android 5.0 及更高版本的 Dalvik 可執(zhí)行文件分包支持
Android 5.0(API 級(jí)別 21)及更高版本使用名為 ART 的運(yùn)行時(shí),后者原生支持從 APK 文件加載多個(gè) DEX 文件界赔。ART 在應(yīng)用安裝時(shí)執(zhí)行預(yù)編譯丢习,掃描classesN.dex
文件,并將它們編譯成單個(gè) .oat 文件淮悼,供 Android 設(shè)備執(zhí)行咐低。因此,如果您的 minSdkVersion 為 21 或更高值袜腥,則不需要 Dalvik 可執(zhí)行文件分包支持庫见擦。
如需了解有關(guān) Android 5.0 運(yùn)行時(shí)的詳細(xì)信息,請(qǐng)參閱 ART 和 Dalvik瞧挤。
注:如果將應(yīng)用的 minSdkVersion 設(shè)置為 21 或更高值锡宋,使用 Instant Run 時(shí),Android Studio 會(huì)自動(dòng)將應(yīng)用配置為進(jìn)行 Dalvik 可執(zhí)行文件分包特恬。由于 Instant Run 僅適用于調(diào)試版本的應(yīng)用执俩,您仍需配置發(fā)布構(gòu)建進(jìn)行 Dalvik 可執(zhí)行文件分包,以規(guī)避 64K 限制癌刽。
規(guī)避 64K 限制
在將您的應(yīng)用配置為支持使用 64K 或更多方法引用之前役首,您應(yīng)該采取措施減少應(yīng)用代碼調(diào)用的引用總數(shù)尝丐,包括由您的應(yīng)用代碼或包含的庫定義的方法。下列策略可幫助您避免達(dá)到 DEX 引用限制:檢查您的應(yīng)用的直接和傳遞依賴項(xiàng) - 確保您在應(yīng)用中使用任何龐大依賴庫所帶來的好處大于為應(yīng)用添加大量代碼所帶來的弊端衡奥。一種常見的反面模式是爹袁,僅僅為了使用幾個(gè)實(shí)用方法就在應(yīng)用中加入非常龐大的庫。減少您的應(yīng)用代碼依賴項(xiàng)往往能夠幫助您規(guī)避 dex 引用限制矮固。
通過 ProGuard 移除未使用的代碼 - 為您的版本構(gòu)建啟用代碼壓縮以運(yùn)行 ProGuard失息。啟用壓縮可確保您交付的 APK 不含有未使用的代碼。
使用這些技巧使您不必在應(yīng)用中啟用 Dalvik 可執(zhí)行文件分包档址,同時(shí)還會(huì)減小 APK 的總體大小盹兢。
配置您的應(yīng)用進(jìn)行 Dalvik 可執(zhí)行文件分包
將您的應(yīng)用項(xiàng)目設(shè)置為使用 Dalvik 可執(zhí)行文件分包配置需要對(duì)您的應(yīng)用項(xiàng)目進(jìn)行以下修改,具體取決于應(yīng)用支持的最低 Android 版本守伸。
- 如果您的 minSdkVersion 設(shè)置為 21 或更高值绎秒,您只需在模塊級(jí) build.gradle 文件中將 multiDexEnabled 設(shè)置為 true,如此處所示:
android {
defaultConfig {
...
minSdkVersion 21
targetSdkVersion 26
multiDexEnabled true
}
...
}
- 但是尼摹,如果您的 minSdkVersion設(shè)置為 20 或更低值见芹,則您必須按如下方式使用 Dalvik 可執(zhí)行文件分包支持庫:
修改模塊級(jí) build.gradle
文件以啟用 Dalvik 可執(zhí)行文件分包,并將 Dalvik 可執(zhí)行文件分包庫添加為依賴項(xiàng)蠢涝,如此處所示:
android {
defaultConfig {
...
minSdkVersion 15
targetSdkVersion 26
multiDexEnabled true
}
...
}
dependencies {
compile 'com.android.support:multidex:1.0.1'
}
- 根據(jù)是否要替換 Application
類玄呛,執(zhí)行以下操作之一:
如果您沒有替換 Application
類,請(qǐng)編輯清單文件和二,按如下方式設(shè)置 <application>
標(biāo)記中的 android:name:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapp">
<application
android:name="android.support.multidex.MultiDexApplication" >
...
</application>
</manifest>
如果您替換了 Application
類把鉴,請(qǐng)按如下方式對(duì)其進(jìn)行更改以擴(kuò)展 MultiDexApplication
(如果可能):
public class MyApplication extends MultiDexApplication { ... }
或者,如果您替換了 Application
類儿咱,但無法更改基本類,則可以改為替換 attachBaseContext() 方法并調(diào)用 MultiDex.install(this) 來啟用 Dalvik 可執(zhí)行文件分包:
public class MyApplication extends SomeOtherApplication {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(context);
Multidex.install(this);
}
}
構(gòu)建應(yīng)用后场晶,Android 構(gòu)建工具會(huì)根據(jù)需要構(gòu)建主 DEX 文件 (classes.dex) 和輔助 DEX 文件(classes2.dex 和 classes3.dex 等)混埠。然后,構(gòu)建系統(tǒng)會(huì)將所有 DEX 文件打包到您的 APK 中诗轻。
運(yùn)行時(shí)钳宪,Dalvik 可執(zhí)行文件分包 API 使用特殊的類加載器來搜索適用于您的方法的所有 DEX 文件(而不是僅在主 classes.dex 文件中搜索)。
Dalvik 可執(zhí)行文件分包支持庫的局限性
Dalvik 可執(zhí)行文件分包支持庫具有一些已知的局限性扳炬,將其納入您的應(yīng)用構(gòu)建配置之中時(shí)吏颖,您應(yīng)該注意這些局限性并進(jìn)行針對(duì)性的測試:
啟動(dòng)期間在設(shè)備數(shù)據(jù)分區(qū)中安裝 DEX 文件的過程相當(dāng)復(fù)雜,如果輔助 DEX 文件較大恨樟,可能會(huì)導(dǎo)致應(yīng)用無響應(yīng) (ANR) 錯(cuò)誤半醉。在此情況下,您應(yīng)該通過 ProGuard 應(yīng)用代碼壓縮以盡量減小 DEX 文件的大小劝术,并移除未使用的那部分代碼缩多。
由于存在 Dalvik linearAlloc 錯(cuò)誤(問題 22586)呆奕,使用 Dalvik 可執(zhí)行文件分包的應(yīng)用可能無法在運(yùn)行的平臺(tái)版本早于 Android 4.0(API 級(jí)別 14)的設(shè)備上啟動(dòng)。如果您的目標(biāo) API 級(jí)別低于 14衬吆,請(qǐng)務(wù)必針對(duì)這些版本的平臺(tái)進(jìn)行測試梁钾,因?yàn)槟膽?yīng)用可能會(huì)在啟動(dòng)時(shí)或加載特定類群時(shí)出現(xiàn)問題。代碼壓縮可以減少甚至有可能消除這些潛在問題逊抡。
由于存在 Dalvik linearAlloc 限制(問題 78035)姆泻,因此,如果使用 Dalvik 可執(zhí)行文件分包配置的應(yīng)用發(fā)出非常龐大的內(nèi)存分配請(qǐng)求冒嫡,則可能會(huì)在運(yùn)行期間發(fā)生崩潰拇勃。盡管 Android 4.0(API 級(jí)別 14)提高了分配限制,但在 Android 5.0(API 級(jí)別 21)之前的 Android 版本上灯谣,應(yīng)用仍有可能遭遇這一限制潜秋。
聲明主 DEX 文件中需要的類
為 Dalvik 可執(zhí)行文件分包構(gòu)建每個(gè) DEX 文件時(shí),構(gòu)建工具會(huì)執(zhí)行復(fù)雜的決策制定來確定主要 DEX 文件中需要的類胎许,以便應(yīng)用能夠成功啟動(dòng)峻呛。如果啟動(dòng)期間需要的任何類未在主 DEX 文件中提供,那么您的應(yīng)用將崩潰并出現(xiàn)錯(cuò)誤java.lang.NoClassDefFoundError
辜窑。
該情況不應(yīng)出現(xiàn)在直接從應(yīng)用代碼訪問的代碼上钩述,因?yàn)闃?gòu)建工具能識(shí)別這些代碼路徑,但可能在代碼路徑可見性較低(如使用的庫具有復(fù)雜的依賴項(xiàng))時(shí)出現(xiàn)穆碎。例如牙勘,如果代碼使用自檢機(jī)制或從原生代碼調(diào)用 Java 方法,那么這些類可能不會(huì)被識(shí)別為主 DEX 文件中的必需項(xiàng)所禀。
因此方面,如果您收到java.lang.NoClassDefFoundError
,則必須使用構(gòu)建類型中的 multiDexKeepFile 或 multiDexKeepProguard
屬性聲明它們色徘,以手動(dòng)將這些其他類指定為主DEX
文件中的必需項(xiàng)恭金。如果類在multiDexKeepFile
或multiDexKeepProguard
文件中匹配,則該類會(huì)添加至主 DEX 文件褂策。
multiDexKeepFile 屬性
您在 multiDexKeepFile
中指定的文件應(yīng)該每行包含一個(gè)類横腿,并且采用 com/example/MyClass.class
的格式。例如斤寂,您可以創(chuàng)建一個(gè)名為multidex-config.txt
的文件耿焊,如下所示:
com/example/MyClass.class
com/example/MyOtherClass.class
然后,您可以按以下方式針對(duì)構(gòu)建類型聲明該文件:
android {
buildTypes {
release {
multiDexKeepFile file 'multidex-config.txt'
...
}
}
}
請(qǐng)記住遍搞,Gradle 會(huì)讀取相對(duì)于build.gradle
文件的路徑罗侯,因此如果 multidex-config.txt
與 build.gradle
文件在同一目錄中,以上示例將有效溪猿。
multiDexKeepProguard 屬性
multiDexKeepProguard
文件使用與 Proguard 相同的格式歇父,并且支持整個(gè) Proguard 語法蒂培。如需了解有關(guān) Proguard 格式和語法的詳細(xì)信息,請(qǐng)參閱 Proguard 手冊(cè)中的 Keep Options 一節(jié)榜苫。
您在 multiDexKeepProguard
中指定的文件應(yīng)該在任何有效的 ProGuard 語法中包含 -keep
選項(xiàng)护戳。例如,-keep com.example.MyClass.class
垂睬。您可以創(chuàng)建一個(gè)名為 multidex-config.pro
的文件媳荒,如下所示:
-keep class com.example.MyClass
-keep class com.example.MyClassToo
如果您想要指定包中的所有類,文件將如下所示:
-keep class com.example.** { *; } // All classes in the com.example package
然后驹饺,您可以按以下方式針對(duì)構(gòu)建類型聲明該文件:
android {
buildTypes {
release {
multiDexKeepProguard 'multidex-config.pro'
...
}
}
}
優(yōu)化開發(fā)構(gòu)建中的 Dalvik 可執(zhí)行文件分包
Dalvik 可執(zhí)行文件分包配置會(huì)大幅增加構(gòu)建處理時(shí)間钳枕,因?yàn)闃?gòu)建系統(tǒng)必須就哪些類必須包括在主 DEX 文件中以及哪些類可以包括在輔助 DEX 文件中作出復(fù)雜的決策。這意味著使用 Dalvik 可執(zhí)行文件分包的增量式構(gòu)建通常耗時(shí)更長赏壹,可能會(huì)拖慢您的開發(fā)進(jìn)度鱼炒。
為了縮短耗時(shí)更長的 Dalvik 可執(zhí)行文件分包輸出構(gòu)建時(shí)間,請(qǐng)利用 productFlavors
(一個(gè)開發(fā)定制和一個(gè)發(fā)布定制蝌借,具有不同的 minSdkVersion
值)創(chuàng)建兩個(gè)構(gòu)建變型昔瞧。
對(duì)于開發(fā)定制,將 minSdkVersion 設(shè)置為 21菩佑。該設(shè)置將啟用一個(gè)名為 pre-dexing 的構(gòu)建功能自晰,此功能使用僅適用于 Android 5.0(API 級(jí)別 21)和更高版本的 ART 格式更快生成 Dalvik 可執(zhí)行文件分包輸出。對(duì)于發(fā)布定制稍坯,將 minSdkVersion
設(shè)置為適于您的實(shí)際最低支持級(jí)別酬荞。此設(shè)置生成的 Dalvik 可執(zhí)行文件分包 APK 可兼容更多設(shè)備,但構(gòu)建時(shí)間更長瞧哟。
以下構(gòu)建配置示例展示了如何在 Gradle 構(gòu)建文件中設(shè)置這些定制:
android {
defaultConfig {
...
multiDexEnabled true
}
productFlavors {
dev {
// Enable pre-dexing to produce an APK that can be tested on
// Android 5.0+ without the time-consuming DEX build processes.
minSdkVersion 21
}
prod {
// The actual minSdkVersion for the production version.
minSdkVersion 14
}
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro'
}
}
}
dependencies {
compile 'com.android.support:multidex:1.0.1'
}
您完成此配置變更后混巧,可以為增量式構(gòu)建使用應(yīng)用的 devDebug變體,后者集 dev 產(chǎn)品定制與 debug構(gòu)建類型的屬性于一身勤揩。這將創(chuàng)建已啟用 Dalvik 可執(zhí)行文件分包且禁用 proguard 的可調(diào)試應(yīng)用(因?yàn)?minifyEnabled默認(rèn)為 false)牲剃。這些設(shè)置會(huì)使適用于 Gradle 的 Android 插件執(zhí)行以下操作:
- 執(zhí)行 pre-dexing:將每個(gè)應(yīng)用模塊和每個(gè)依賴項(xiàng)構(gòu)建為單獨(dú)的 DEX 文件。
- 將每個(gè) DEX 文件加入 APK雄可,并且不做任何修改(不執(zhí)行代碼壓縮)。
- 最重要的是缠犀,模塊 DEX 文件不執(zhí)行合并操作数苫,因此可以避免為確定主 DEX 文件的內(nèi)容而進(jìn)行長時(shí)間的計(jì)算。
這些設(shè)置的好處是辨液,可以進(jìn)行快速的增量式構(gòu)建虐急,因?yàn)橹挥行薷倪^的模塊的 DEX 文件才會(huì)在后續(xù)構(gòu)建期間重新計(jì)算并重新打包。但是滔迈,這些構(gòu)建的 APK 只能用于在 Android 5.0 設(shè)備上進(jìn)行測試止吁。不過被辑,由于是以定制形式實(shí)現(xiàn)配置,您保留了使用與發(fā)布相適的最低 API 級(jí)別和 ProGuard 代碼壓縮執(zhí)行正常構(gòu)建的能力敬惦。
您還可以構(gòu)建其他變體盼理,包括prodDebug
變體構(gòu)建,該變體雖然構(gòu)建時(shí)間更長俄删,但可用于開發(fā)以外的測試宏怔。在所示配置內(nèi),prodRelease變體將是最終測試和發(fā)布版本畴椰。如需了解有關(guān)使用構(gòu)建變體的詳細(xì)信息臊诊,請(qǐng)參閱配置構(gòu)建變體。
提示:由于您有適用于不同 Dalvik 可執(zhí)行文件分包需求的不同構(gòu)建變體斜脂,因此也可以為不同變體提供不同清單文件(這樣抓艳,只有適用于 API 級(jí)別 20 和更低版本的清單文件會(huì)更改 <application>標(biāo)記名稱),或者為每個(gè)變體創(chuàng)建不同的 Application子類(這樣帚戳,只有適用于 API 級(jí)別 20 和更低版本的清單文件會(huì)擴(kuò)展 MultiDexApplication 類或調(diào)用 MultiDex.install(this))玷或。
測試 Dalvik 可執(zhí)行文件分包應(yīng)用
編寫面向 Dalvik 可執(zhí)行文件分包應(yīng)用的儀器測試時(shí),無需進(jìn)行其他配置销斟。AndroidJUnitRunner直接支持 Dalvik 可執(zhí)行文件分包庐椒,前提是您使用MultiDexApplication
或替換您的自定義 Application
對(duì)象中的 attachBaseContext() 方法,并調(diào)用 MultiDex.install(this)
以啟用 Dalvik 可執(zhí)行文件分包蚂踊。
或者约谈,您可以替換 AndroidJUnitRunner 中的 onCreate()方法:
public void onCreate(Bundle arguments) {
MultiDex.install(getTargetContext());
super.onCreate(arguments);
...
}
注:目前不支持使用 Dalvik 可執(zhí)行文件分包來創(chuàng)建測試 APK。