本文是對Google官方文檔 Reduce APK Size 的翻譯l留與備用
用戶經(jīng)常會避免下載看起來體積較大的應用,特別是在不穩(wěn)定的2G、3G網(wǎng)絡或者在以字節(jié)付費的網(wǎng)絡。這篇文章描述了怎樣減少你的APK大小,這會讓更多的用戶愿意下載你的應用。
理解APK的結(jié)構(gòu)
在討論怎樣減少應用大小之前,先了解APK的結(jié)構(gòu)是有用的。一個APK文件就是ZIP包,其中包含了組成你的應用的所有文件,比如Java類文件,資源文件,和一個包含被編譯資源的文件。
一個APK包含了以下目錄:
META-INF/: 包含CERT.SF和CERT.RSA簽名文件,也包含了MANIFEST.MF文件。(譯注:校驗這個APK是否被人改動過)
assets/: 包含了應用的資源,這些資源能夠通過AssetManager對象獲得。
res/: 包含了沒被被編譯到resources.arsc的資源。
lib/: 包含了針對處理器層面的被編譯的代碼。這個目錄針對每個平臺類型都有一個子目錄,比如armeabi, armeabi-v7a, arm64-v8a, x86, x86_64和mips。
一個APK也包含了以下文件,其中只有AndroidManifest.xml是強制的:
resources.arsc: 包含了被編譯的資源。該文件包含了res/values目錄的所有配置的XML內(nèi)容。打包工具將XML內(nèi)容編譯成二進制形式并壓縮。這些內(nèi)容包含了語言字符串和styles,還包含了那些內(nèi)容雖然不直接存儲在resources.arsc文件中,但是給定了該內(nèi)容的路徑,比如布局文件和圖片。
classes.dex: 包含了能被Dalvik/Art虛擬機理解的DEX文件格式的類。
AndroidManifest.xml: 包含了主要的Android配置文件。這個文件列出了應用名稱、版本、訪問權(quán)限、引用的庫文件。該文件使用二進制XML格式存儲。(譯注:該文件還能看到應用的minSdkVersion, targetSdkVersion等信息)
譯注:使用APK Analyzer能夠清晰地看出以上文件的內(nèi)容,具體請看:使用APK Analyzer分析你的APK。
減少資源個數(shù)和尺寸
APK的大小會影響應用加載的速度,使用的內(nèi)存大小,消耗的電量大小。一個最簡單的縮小APK大小的方式是減少資源的個數(shù)和大小。特別地,你能移除應用中不再使用的資源,你也能使用可縮放的Drawable對象代替圖片文件。這節(jié)討論一些通過減少資源從而減少APK大小的方法。
譯注:減少資源個數(shù)和縮小資源大小的效果是很顯著的,比如有一天發(fā)現(xiàn)我組里的項目中還包含了舊版本的引導頁視頻(1.5M),一下就就減少了1.5M,想想為了減少1.5M你得刪多少代碼才能辦到。
移除不使用的資源
lint是Android Studio中的一個靜態(tài)代碼分析工具,檢測在“res/”目錄中你的代碼沒有引用的資源。當lint工具發(fā)現(xiàn)了項目中潛在的未使用的資源,它會打印以下類似信息:
res/layout/preferences.xml:Warning:The resource R.layout.preferences appears to be unused[UnusedResources]
注意:lint工具不會掃描“asset/”目錄,這個目錄是通過反射引用的資源,或者鏈接到應用中的庫文件。還有,lint不會移除資源,只會發(fā)出警告。
被引用的庫中可能會包含沒使用的資源。如果你在build.gradle文件中啟用shrinkResources,則Gradle能自動移除這些資源。
為了使用shrinkResources,你必須要啟用代碼混淆。在構(gòu)建過程中,首先proguard移除了未使用的代碼,然后gradle移除未使用的資源。
譯注:lint工具還能夠檢查出未使用的類、類中未使用的方法或變量。
更多關于通過代碼混淆和其他方式減包,請看Shrink Your Code and Resources。
在Gradle插件0.7或更高版本,你能申明應用支持的配置。Gradle通過傳遞resConfigs和defaultConfig給構(gòu)建系統(tǒng),構(gòu)建系統(tǒng)會防止不支持的配置出現(xiàn)在APK中,從而減少APK大小。更多信息請看Remove unused alternative resources。
譯注:在hello world工程里,resConfigs配置為“zh”和不配置resConfigs,resources.arsc文件相差了80K。
最小化第三方庫中資源的使用
當開發(fā)Android應用時,你經(jīng)常使用第三方庫提升應用的可用性和靈活性。比如,你引用Android Support Library提升舊設備的用戶體驗,或者使用Google Play服務實現(xiàn)文字自動翻譯。
如果一個第三方庫原本是為服務器或普通電腦設計,會引入許多不需要的對象和方法。為了只引入應用需要的庫中的那部分,你可以編輯庫文件(如果庫的license允許你這么做)。你也能使用另外的針對手機的實現(xiàn)同樣功能的庫。
注意:代碼混淆能清除庫中不被使用的代碼,但是他不能移除庫的大量內(nèi)部依賴。
只支持部分屏幕密度
Android支持很多設備集,其中包含了各種不同的屏幕密度。在Android 4.4及更高版本,框架支持不同的密度:ldpi, mdpi, tvdpi, hdpi, xhdpi, xxhdpi和xxxhdpi。盡管Android支持所有這些屏幕密度,但你不需要為每個密度都配置相應的資源。
如果你知道某種特定屏幕密度已經(jīng)很少有用戶使用了,那么你可以考慮是否需要為這個屏幕密度配置資源。如果你不包含針對特定屏幕密度的資源,那么Android會自動縮放原本針對其他密度的已有資源。
如果你的應用只需要縮放的圖片,你甚至可以把圖片存放在drawable-nodpi目錄,從而節(jié)省更多空間。我們推薦每個應用都應該至少包含xxhdpi的圖片。
更多關于屏幕密度的信息,請看Screen Sizes and Densities。
減少動畫幀數(shù)
使用幀動畫會大大增加APK的大小。圖1顯示了目錄中構(gòu)成幀動畫的多個PNG文件。每個圖片都是動畫的一幀。
對于加入動畫的每幀,你都增加了APK中圖片的個數(shù)。圖1中,幀動畫的幀率是30 FPS。如果幀率降到15 FPS,圖片數(shù)量將減少一半。
譯注:還有一個常見的減包方案是刪除幀動畫中重復的圖片資源,比如第1幀和第3幀的圖片一樣,那么只保留一個。
使用Drawable對象
一些圖片不需要靜態(tài)的圖片資源,框架能在運行時動態(tài)地繪制圖像。Drawable對象(XML的<shape>)只需要占用APK中的一點空間。另外,XML形式的Drawable對象能夠產(chǎn)生遵循Material Design設計規(guī)范的圖像。
重用資源
你能包含一張圖片的很多變種,比如染色、陰影、旋轉(zhuǎn)的版本。但是,我們推薦在運行時復用一張圖片來定制化他們。
Android提供了很多方式改變資源的顏色。對于Android 5.0及以上,使用android:tint和tintMode屬性。對于更低版本,使用ColorFilter類。
你也能夠刪除那些只是對另一個資源做旋轉(zhuǎn)的資源。下面的代碼片段提供了對一個箭頭旋轉(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"/>
通過代碼繪制
你也能通過代碼繪制圖像,從而減少APK大小。代碼方式繪制圖像不需要任何空間因為你不再需要在APK中存儲圖像文件。
壓縮PNG文件
AAPT工具能夠在構(gòu)建過程中通過無損壓縮優(yōu)化res/drawable/中的圖片資源。比如aapt工具能將需要顏色少于256色的PNG變?yōu)?位PNG圖,這樣能夠在保證圖片質(zhì)量的同時減少內(nèi)存使用。
需要注意aapt有以下局限性:
aapt工具不會壓縮asset目錄的PNG文件。
通過aapt的優(yōu)化,圖片文件會使用少于256色。
aapt工具可能會影響已經(jīng)被壓縮過的PNG文件。為了防止這種情況,你可以在gradle文件中設置cruncherEnabled為false禁用aapt對PNG的壓縮。
aaptOptions{cruncherEnabled=false}
譯注:建議把cruncherEnabled設為false,然后通過tinypng手工壓縮PNG圖片。
壓縮PNG和JPEG文件
你能使用一些工具(比如pngcrush, pngquant, zopflipng)在不降低圖像質(zhì)量的前提下減少PNG文件大小。所有這些工具都能保留圖像質(zhì)量的情況下減少PNG文件大小。
pngcrush工具特別有效:這個工具通過迭代png過濾器和zlib參數(shù),使用每種過濾器和參數(shù)的組合壓縮圖像,并選擇最小的那個作為最后的輸出。
對于JPEG文件,能使用packJPG壓縮JPEG文件。
譯注:guetzli是Google最近推出的JPEG編碼器,官方宣稱相同圖片質(zhì)量時,比libjpeg生成的圖片小20–30%。
使用WebP文件格式
你也能使用WebP文件格式存儲圖片而不是PNG或者JPEG。WebP格式是有損壓縮(像JPEG)且有透明通道(像PNG),且壓縮率高于JPEG或PNG。
使用WebP文件格式也有一些缺點。第一,低于Android 3.2的版本不支持WebP,第二,WebP的解碼時間比PNG長。
注意:Google Play的APK的應用啟動圖標只能使用PNG格式,而不支持其他格式。
在Android Studio中,能將BMP,JPG,PNG或者靜態(tài)GIF圖片轉(zhuǎn)換成WebP格式。更多信息,請看Create WebP Images Using Android Studio。
使用向量圖
你能使用向量圖去創(chuàng)建一個分辨率無關的圖標。使用向量圖能夠顯著減少APK大小。在Android中向量圖是以VectorDrawable對象形式存在的。使用VectorDrawable對象,一個100B的文件能生成一個屏幕大小的清晰圖片。
但是,系統(tǒng)需要很長時間渲染VectorDrawable對象,更大的圖片需要更長的時間顯示在屏幕上。因此只有小圖片才考慮使用向量圖。
更多關于VectorDrawable對象的信息,請看Working with Drawables。
減少Native和Java代碼
有許多方法能夠減少Java和Native的代碼量。
減少不必要的生成代碼
確保理解任何自動生成的代碼。比如,許多protocol buffer工具生成了過多的方法和類,這會讓你的應用大小翻倍。
移除枚舉
一個枚舉能讓classes.dex文件增加1–1.4K。枚舉的加入會快速增加應用體積。我們可以使用@IntDef注解和Proguard代替枚舉,它能提供和枚舉一樣的類型安全轉(zhuǎn)換。
減少Native庫的大小
如果你的應用使用了Native代碼和Android NDK,你也能通過優(yōu)化代碼減少應用體積,這里介紹的兩個技巧是刪除調(diào)試符號和避免抽取Native庫。
移除調(diào)試符號
如果應用在開發(fā)中并且仍需要調(diào)試,那么我們能理解使用調(diào)試符號。使用Android NDK提供的arm-eabi-strip工具,能從Native庫中刪除不必要的調(diào)試符號,之后你再編譯release包。
避免抽取Native庫
在APK中存儲未壓縮的so文件,并且在Manifest文件的<application>中設置android:extractNativeLibs為false,這會防止在安裝時PackageManager將APK中的so文件拷貝到文件系統(tǒng),避免這種拷貝會讓應用在做增量更新時的更新包更小。
維持多個小的APK包
你的APK會包含用戶下載了但從未使用的內(nèi)容,比如地區(qū)或語言信息(譯注:比如我是中國人,我就不會用到其他語種的資源)。為了給用戶創(chuàng)建小的下載包,你能把你的應用拆分成多個APK,這些APK的差別在于一些因素(比如屏幕大小或者GPU紋理支持)。
當一個用戶下載了應用,設備根據(jù)自身的特性和設置獲取正確的APK。這種方式能夠讓設備不獲取設備不需要的資源。比如,如果設備是hdpi的,那么他就不需要xxxhdpi的資源。
更多信息請看Configure APK Splits和Maintaining Multiple APKs。