Android減少APK大小

本文翻譯自Google官方文檔 Reduce APK Size
英文鏈接

用戶經(jīng)常會(huì)避免下載看起來體積較大的應(yīng)用,特別是在不穩(wěn)定的2G、3G網(wǎng)絡(luò)或者在以字節(jié)付費(fèi)的網(wǎng)絡(luò)。這篇文章描述了怎樣減少你的APK大小,這會(huì)讓更多的用戶愿意下載你的應(yīng)用。

理解APK的結(jié)構(gòu)

在討論怎樣減少應(yīng)用大小之前,讓我們先了解下APK的結(jié)構(gòu)。一個(gè)APK文件就是ZIP包,其中包含了組成你的應(yīng)用的所有文件,比如Java類文件,資源文件,和一個(gè)包含被編譯資源的文件。

一個(gè)APK包含了以下目錄:

  • META-INF/: 包含CERT.SF和CERT.RSA簽名文件,也包含了MANIFEST.MF文件。(譯注:校驗(yàn)這個(gè)APK是否被人改動(dòng)過)
  • assets/: 包含了應(yīng)用的資源,這些資源能夠通過AssetManager對象獲得。
  • res/: 包含了沒被被編譯到resources.arsc的資源。
    lib/: 包含了針對處理器層面的被編譯的代碼。這個(gè)目錄針對每個(gè)平臺類型都有一個(gè)子目錄,比如armeabi, armeabi-v7a, arm64-v8a, x86, x86_64和mips。

一個(gè)APK也包含了以下文件,其中只有AndroidManifest.xml是強(qiáng)制的:

  • resources.arsc: 包含了被編譯的資源。該文件包含了res/values目錄的所有配置的XML內(nèi)容。打包工具將XML內(nèi)容編譯成二進(jìn)制形式并壓縮。這些內(nèi)容包含了語言字符串和styles,還包含了那些內(nèi)容雖然不直接存儲在resources.arsc文件中,但是給定了該內(nèi)容的路徑,比如布局文件和圖片。
  • classes.dex: 包含了能被Dalvik/Art虛擬機(jī)理解的DEX文件格式的類。
  • AndroidManifest.xml: 包含了主要的Android配置文件。這個(gè)文件列出了應(yīng)用名稱、版本、訪問權(quán)限、引用的庫文件。該文件使用二進(jìn)制XML格式存儲。(譯注:該文件還能看到應(yīng)用的minSdkVersion, targetSdkVersion等信息)

譯注:使用APK Analyzer能夠清晰地看出以上文件的內(nèi)容,具體請看:使用APK Analyzer分析你的APK。

減少資源個(gè)數(shù)和尺寸

APK的大小會(huì)影響應(yīng)用加載的速度,使用的內(nèi)存大小,消耗的電量大小。一個(gè)最簡單的縮小APK大小的方式是減少資源的個(gè)數(shù)和大小。特別地,你能移除應(yīng)用中不再使用的資源,你也能使用可縮放的Drawable對象代替圖片文件。這節(jié)討論一些通過減少資源從而減少APK大小的方法。

1.移除不使用的資源

lint是Android Studio中的一個(gè)靜態(tài)代碼分析工具,檢測在“res/”目錄中你的代碼沒有引用的資源。當(dāng)lint工具發(fā)現(xiàn)了項(xiàng)目中潛在的未使用的資源,它會(huì)打印以下類似信息:

res/layout/preferences.xml: Warning: The resource R.layout.preferences appears to be unused [UnusedResources]

注意:lint工具不會(huì)掃描“asset/”目錄,這個(gè)目錄是通過反射引用的資源,或者鏈接到應(yīng)用中的庫文件。還有,lint不會(huì)移除資源,只會(huì)發(fā)出警告。

被引用的庫中可能會(huì)包含沒使用的資源。如果你在build.gradle文件中啟用shrinkResources,則Gradle能自動(dòng)移除這些資源。

為了使用shrinkResources,你必須要啟用代碼混淆。在構(gòu)建過程中,首先proguard移除了未使用的代碼,然后gradle移除未使用的資源。

譯注:lint工具還能夠檢查出未使用的類、類中未使用的方法或變量。

更多關(guān)于通過代碼混淆和其他方式減包,請看Shrink Your Code and Resources。

在Gradle插件0.7或更高版本,你能申明應(yīng)用支持的配置。Gradle通過傳遞resConfigs和defaultConfig給構(gòu)建系統(tǒng),構(gòu)建系統(tǒng)會(huì)防止不支持的配置出現(xiàn)在APK中,從而減少APK大小。更多信息請看Remove unused alternative resources。

譯注:在hello world工程里,resConfigs配置為“zh”和不配置resConfigs,resources.arsc文件相差了80K。

2.最小化第三方庫中資源的使用

當(dāng)開發(fā)Android應(yīng)用時(shí),你經(jīng)常使用第三方庫提升應(yīng)用的可用性和靈活性。比如,你引用Android Support Library提升舊設(shè)備的用戶體驗(yàn),或者使用Google Play服務(wù)實(shí)現(xiàn)文字自動(dòng)翻譯

如果一個(gè)第三方庫原本是為服務(wù)器或普通電腦設(shè)計(jì),會(huì)引入許多不需要的對象和方法。為了只引入應(yīng)用需要的庫中的那部分,你可以編輯庫文件(如果庫的license允許你這么做)。你也能使用另外的針對手機(jī)的實(shí)現(xiàn)同樣功能的庫。

注意:代碼混淆能清除庫中不被使用的代碼,但是他不能移除庫的大量內(nèi)部依賴。

3.只支持部分屏幕密度

Android支持很多設(shè)備集,其中包含了各種不同的屏幕密度。在Android 4.4及更高版本,框架支持不同的密度:ldpi, mdpi, tvdpi, hdpi, xhdpi, xxhdpi和xxxhdpi。盡管Android支持所有這些屏幕密度,但你不需要為每個(gè)密度都配置相應(yīng)的資源。

如果你知道某種特定屏幕密度已經(jīng)很少有用戶使用了,那么你可以考慮是否需要為這個(gè)屏幕密度配置資源。如果你不包含針對特定屏幕密度的資源,那么Android會(huì)自動(dòng)縮放原本針對其他密度的已有資源。

如果你的應(yīng)用只需要縮放的圖片,你甚至可以把圖片存放在drawable-nodpi目錄,從而節(jié)省更多空間。我們推薦每個(gè)應(yīng)用都應(yīng)該至少包含xxhdpi的圖片。

更多關(guān)于屏幕密度的信息,請看Screen Sizes and Densities。

4.減少動(dòng)畫幀數(shù)

使用幀動(dòng)畫會(huì)大大增加APK的大小。圖1顯示了目錄中構(gòu)成幀動(dòng)畫的多個(gè)PNG文件。每個(gè)圖片都是動(dòng)畫的一幀。

對于加入動(dòng)畫的每幀,你都增加了APK中圖片的個(gè)數(shù)。圖1中,幀動(dòng)畫的幀率是30 FPS。如果幀率降到15 FPS,圖片數(shù)量將減少一半。

幀動(dòng)畫的每一幀圖片。
幀動(dòng)畫的每一幀圖片。

譯注:還有一個(gè)常見的減包方案是刪除幀動(dòng)畫中重復(fù)的圖片資源,比如第1幀和第3幀的圖片一樣,那么只保留一個(gè)。

5.使用Drawable對象

一些圖片不需要靜態(tài)的圖片資源,框架能在運(yùn)行時(shí)動(dòng)態(tài)地繪制圖像。Drawable對象(XML的<shape>)只需要占用APK中的一點(diǎn)空間。另外,XML形式的Drawable對象能夠產(chǎn)生遵循Material Design設(shè)計(jì)規(guī)范的圖像。

6.重用資源

你能包含一張圖片的很多變種,比如染色、陰影、旋轉(zhuǎn)的版本。但是,我們推薦在運(yùn)行時(shí)復(fù)用一張圖片來定制化他們。

Android提供了很多方式改變資源的顏色。對于Android 5.0及以上,使用android:tint和tintMode屬性。對于更低版本,使用ColorFilter類。

你也能夠刪除那些只是對另一個(gè)資源做旋轉(zhuǎn)的資源。下面的代碼片段提供了對一個(gè)箭頭旋轉(zhuǎn)180度。

<?xml version="1.0" encoding="utf-8"?><rotate xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/ic_arrow_expand"
    android:fromDegrees="180"
    android:pivotX="50%"
    android:pivotY="50%"
    android:toDegrees="180" />

7.通過代碼繪制

你也能通過代碼繪制圖像,從而減少APK大小。代碼方式繪制圖像不需要任何空間因?yàn)槟悴辉傩枰贏PK中存儲圖像文件。

8.壓縮PNG文件

AAPT工具能夠在構(gòu)建過程中通過無損壓縮優(yōu)化res/drawable/中的圖片資源。比如aapt工具能將需要顏色少于256色的PNG變?yōu)?位PNG圖,這樣能夠在保證圖片質(zhì)量的同時(shí)減少內(nèi)存使用。

需要注意aapt有以下局限性:

  • aapt工具不會(huì)壓縮asset目錄的PNG文件。
  • 通過aapt的優(yōu)化,圖片文件會(huì)使用少于256色。
  • aapt工具可能會(huì)影響已經(jīng)被壓縮過的PNG文件。為了防止這種情況,你可以在gradle文件中設(shè)置cruncherEnabled為false禁用aapt對PNG的壓縮。
aaptOptions {
    cruncherEnabled = false
}

譯注:建議把cruncherEnabled設(shè)為false,然后通過tinypng手工壓縮PNG圖片。

9. 壓縮PNG和JPEG文件

你能使用一些工具(比如pngcrush, pngquant, zopflipng)在不降低圖像質(zhì)量的前提下減少PNG文件大小。所有這些工具都能保留圖像質(zhì)量的情況下減少PNG文件大小。

pngcrush工具特別有效:這個(gè)工具通過迭代png過濾器和zlib參數(shù),使用每種過濾器和參數(shù)的組合壓縮圖像,并選擇最小的那個(gè)作為最后的輸出。

對于JPEG文件,能使用packJPG壓縮JPEG文件。

譯注:guetzli是Google最近推出的JPEG編碼器,官方宣稱相同圖片質(zhì)量時(shí),比libjpeg生成的圖片小20–30%。

10.使用WebP文件格式

你也能使用WebP文件格式存儲圖片而不是PNG或者JPEG。WebP格式是有損壓縮(像JPEG)且有透明通道(像PNG),且壓縮率高于JPEG或PNG。

使用WebP文件格式也有一些缺點(diǎn)。第一,低于Android 3.2的版本不支持WebP,第二,WebP的解碼時(shí)間比PNG長。

注意:Google Play的APK的應(yīng)用啟動(dòng)圖標(biāo)只能使用PNG格式,而不支持其他格式。

在Android Studio中,能將BMP,JPG,PNG或者靜態(tài)GIF圖片轉(zhuǎn)換成WebP格式。更多信息,請看Create WebP Images Using Android Studio。

11.使用向量圖

你能使用向量圖去創(chuàng)建一個(gè)分辨率無關(guān)的圖標(biāo)。使用向量圖能夠顯著減少APK大小。在Android中向量圖是以VectorDrawable對象形式存在的。使用VectorDrawable對象,一個(gè)100B的文件能生成一個(gè)屏幕大小的清晰圖片。

但是,系統(tǒng)需要很長時(shí)間渲染VectorDrawable對象,更大的圖片需要更長的時(shí)間顯示在屏幕上。因此只有小圖片才考慮使用向量圖。

更多關(guān)于VectorDrawable對象的信息,請看Working with Drawables。

12.減少Native和Java代碼

有許多方法能夠減少Java和Native的代碼量。

13.減少不必要的生成代碼

確保理解任何自動(dòng)生成的代碼。比如,許多protocol buffer工具生成了過多的方法和類,這會(huì)讓你的應(yīng)用大小翻倍。

14.移除枚舉

一個(gè)枚舉能讓classes.dex文件增加1–1.4K。枚舉的加入會(huì)快速增加應(yīng)用體積。我們可以使用@IntDef注解和Proguard代替枚舉,它能提供和枚舉一樣的類型安全轉(zhuǎn)換。

15.減少Native庫的大小

如果你的應(yīng)用使用了Native代碼和Android NDK,你也能通過優(yōu)化代碼減少應(yīng)用體積,這里介紹的兩個(gè)技巧是刪除調(diào)試符號和避免抽取Native庫。

16.移除調(diào)試符號

如果應(yīng)用在開發(fā)中并且仍需要調(diào)試,那么我們能理解使用調(diào)試符號。使用Android NDK提供的arm-eabi-strip工具,能從Native庫中刪除不必要的調(diào)試符號,之后你再編譯release包。

17.避免抽取Native庫

在APK中存儲未壓縮的so文件,并且在Manifest文件的<application>中設(shè)置android:extractNativeLibs為false,這會(huì)防止在安裝時(shí)PackageManager將APK中的so文件拷貝到文件系統(tǒng),避免這種拷貝會(huì)讓應(yīng)用在做增量更新時(shí)的更新包更小。

維持多個(gè)小的APK包

你的APK會(huì)包含用戶下載了但從未使用的內(nèi)容,比如地區(qū)或語言信息(譯注:比如我是中國人,我就不會(huì)用到其他語種的資源)。為了給用戶創(chuàng)建小的下載包,你能把你的應(yīng)用拆分成多個(gè)APK,這些APK的差別在于一些因素(比如屏幕大小或者GPU紋理支持)。

當(dāng)一個(gè)用戶下載了應(yīng)用,設(shè)備根據(jù)自身的特性和設(shè)置獲取正確的APK。這種方式能夠讓設(shè)備不獲取設(shè)備不需要的資源。比如,如果設(shè)備是hdpi的,那么他就不需要xxxhdpi的資源。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容