1. 程式人生 > >【騰訊Bugly乾貨分享】Android減包 - 減少APK大小

【騰訊Bugly乾貨分享】Android減包 - 減少APK大小

本文是對Google官方文件 Reduce APK Size 的翻譯,點選“閱讀原文”可以檢視英文原文。

譯者簡介:damonxia(夏正冬),天天P圖Android工程師

使用者經常會避免下載看起來體積較大的應用,特別是在不穩定的2G、3G網路或者在以位元組付費的網路。這篇文章描述了怎樣減少你的APK大小,這會讓更多的使用者願意下載你的應用。

理解APK的結構

在討論怎樣減少應用大小之前,先了解APK的結構是有用的。一個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內容。打包工具將XML內容編譯成二進位制形式並壓縮。這些內容包含了語言字串和styles,還包含了那些內容雖然不直接儲存在resources.arsc檔案中,但是給定了該內容的路徑,比如佈局檔案和圖片。
  • classes.dex: 包含了能被Dalvik/Art虛擬機器理解的DEX檔案格式的類。
  • AndroidManifest.xml: 包含了主要的Android配置檔案。這個檔案列出了應用名稱、版本、訪問許可權、引用的庫檔案。該檔案使用二進位制XML格式儲存。(譯註:該檔案還能看到應用的minSdkVersion, targetSdkVersion等資訊)

譯註:使用APK Analyzer能夠清晰地看出以上檔案的內容,具體請看:使用APK Analyzer分析你的APK。

減少資源個數和尺寸

APK的大小會影響應用載入的速度,使用的記憶體大小,消耗的電量大小。一個最簡單的縮小APK大小的方式是減少資源的個數和大小。特別地,你能移除應用中不再使用的資源,你也能使用可縮放的Drawable物件代替圖片檔案。這節討論一些通過減少資源從而減少APK大小的方法。

譯註:減少資源個數和縮小資源大小的效果是很顯著的,比如有一天發現我組裡的專案中還包含了舊版本的引導頁視訊(1.5M),一下就就減少了1.5M,想想為了減少1.5M你得刪多少程式碼才能辦到。

移除不使用的資源

lint是Android Studio中的一個靜態程式碼分析工具,檢測在“res/”目錄中你的程式碼沒有引用的資源。當lint工具發現了專案中潛在的未使用的資源,它會列印以下類似資訊:

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

注意:lint工具不會掃描“asset/”目錄,這個目錄是通過反射引用的資源,或者連結到應用中的庫檔案。還有,lint不會移除資源,只會發出警告。

被引用的庫中可能會包含沒使用的資源。如果你在build.gradle檔案中啟用shrinkResources,則Gradle能自動移除這些資源。

為了使用shrinkResources,你必須要啟用程式碼混淆。在構建過程中,首先proguard移除了未使用的程式碼,然後gradle移除未使用的資源。

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

更多關於通過程式碼混淆和其他方式減包,請看Shrink Your Code and Resources。

在Gradle外掛0.7或更高版本,你能申明應用支援的配置。Gradle通過傳遞resConfigs和defaultConfig給構建系統,構建系統會防止不支援的配置出現在APK中,從而減少APK大小。更多資訊請看Remove unused alternative resources。

譯註:在hello world工程裡,resConfigs配置為“zh”和不配置resConfigs,resources.arsc檔案相差了80K。

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

當開發Android應用時,你經常使用第三方庫提升應用的可用性和靈活性。比如,你引用Android Support Library提升舊裝置的使用者體驗,或者使用Google Play服務實現文字自動翻譯。

如果一個第三方庫原本是為伺服器或普通電腦設計,會引入許多不需要的物件和方法。為了只引入應用需要的庫中的那部分,你可以編輯庫檔案(如果庫的license允許你這麼做)。你也能使用另外的針對手機的實現同樣功能的庫。

注意:程式碼混淆能清除庫中不被使用的程式碼,但是他不能移除庫的大量內部依賴。

只支援部分螢幕密度

Android支援很多裝置集,其中包含了各種不同的螢幕密度。在Android 4.4及更高版本,框架支援不同的密度:ldpi, mdpi, tvdpi, hdpi, xhdpi, xxhdpi和xxxhdpi。儘管Android支援所有這些螢幕密度,但你不需要為每個密度都配置相應的資源。

如果你知道某種特定螢幕密度已經很少有使用者使用了,那麼你可以考慮是否需要為這個螢幕密度配置資源。如果你不包含針對特定螢幕密度的資源,那麼Android會自動縮放原本針對其他密度的已有資源。

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

更多關於螢幕密度的資訊,請看Screen Sizes and Densities。

減少動畫幀數

使用幀動畫會大大增加APK的大小。圖1顯示了目錄中構成幀動畫的多個PNG檔案。每個圖片都是動畫的一幀。

對於加入動畫的每幀,你都增加了APK中圖片的個數。圖1中,幀動畫的幀率是30 FPS。如果幀率降到15 FPS,圖片數量將減少一半。

圖1:幀動畫的每一幀圖片。

譯註:還有一個常見的減包方案是刪除幀動畫中重複的圖片資源,比如第1幀和第3幀的圖片一樣,那麼只保留一個。

使用Drawable物件

一些圖片不需要靜態的圖片資源,框架能在執行時動態地繪製圖像。Drawable物件(XML的)只需要佔用APK中的一點空間。另外,XML形式的Drawable物件能夠產生遵循Material Design設計規範的影象。

重用資源

你能包含一張圖片的很多變種,比如染色、陰影、旋轉的版本。但是,我們推薦在執行時複用一張圖片來定製化他們。

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

你也能夠刪除那些只是對另一個資源做旋轉的資源。下面的程式碼片段提供了對一個箭頭旋轉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工具能夠在構建過程中通過無失真壓縮優化res/drawable/中的圖片資源。比如aapt工具能將需要顏色少於256色的PNG變為8位PNG圖,這樣能夠在保證圖片質量的同時減少記憶體使用。

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

  • aapt工具不會壓縮asset目錄的PNG檔案。
  • 通過aapt的優化,圖片檔案會使用少於256色。
  • aapt工具可能會影響已經被壓縮過的PNG檔案。為了防止這種情況,你可以在gradle檔案中設定cruncherEnabled為false禁用aapt對PNG的壓縮。
aaptOptions {
    cruncherEnabled = false
}

譯註:建議把cruncherEnabled設為false,然後通過tinypng手工壓縮PNG圖片。

壓縮PNG和JPEG檔案

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

pngcrush工具特別有效:這個工具通過迭代png過濾器和zlib引數,使用每種過濾器和引數的組合壓縮影象,並選擇最小的那個作為最後的輸出。

對於JPEG檔案,能使用packJPG壓縮JPEG檔案。

譯註:guetzli是Google最近推出的JPEG編碼器,官方宣稱相同圖片質量時,比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或者靜態GIF圖片轉換成WebP格式。更多資訊,請看Create WebP Images Using Android Studio。

使用向量圖

你能使用向量圖去建立一個解析度無關的圖示。使用向量圖能夠顯著減少APK大小。在Android中向量圖是以VectorDrawable物件形式存在的。使用VectorDrawable物件,一個100B的檔案能生成一個螢幕大小的清晰圖片。

但是,系統需要很長時間渲染VectorDrawable物件,更大的圖片需要更長的時間顯示在螢幕上。因此只有小圖片才考慮使用向量圖。

更多關於VectorDrawable物件的資訊,請看Working with Drawables。

減少Native和Java程式碼

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

減少不必要的生成程式碼

確保理解任何自動生成的程式碼。比如,許多protocol buffer工具生成了過多的方法和類,這會讓你的應用大小翻倍。

移除列舉

一個列舉能讓classes.dex檔案增加1–1.4K。列舉的加入會快速增加應用體積。我們可以使用@IntDef註解和Proguard代替列舉,它能提供和列舉一樣的型別安全轉換。

減少Native庫的大小

如果你的應用使用了Native程式碼和Android NDK,你也能通過優化程式碼減少應用體積,這裡介紹的兩個技巧是刪除除錯符號和避免抽取Native庫。

移除除錯符號

如果應用在開發中並且仍需要除錯,那麼我們能理解使用除錯符號。使用Android NDK提供的arm-eabi-strip工具,能從Native庫中刪除不必要的除錯符號,之後你再編譯release包。

避免抽取Native庫

在APK中儲存未壓縮的so檔案,並且在Manifest檔案的中設定android:extractNativeLibs為false,這會防止在安裝時PackageManager將APK中的so檔案拷貝到檔案系統,避免這種拷貝會讓應用在做增量更新時的更新包更小。

維持多個小的APK包

你的APK會包含使用者下載了但從未使用的內容,比如地區或語言資訊(譯註:比如我是中國人,我就不會用到其他語種的資源)。為了給使用者建立小的下載包,你能把你的應用拆分成多個APK,這些APK的差別在於一些因素(比如螢幕大小或者GPU紋理支援)。

當一個使用者下載了應用,裝置根據自身的特性和設定獲取正確的APK。這種方式能夠讓裝置不獲取裝置不需要的資源。比如,如果裝置是hdpi的,那麼他就不需要xxxhdpi的資源。

更多資訊請看Configure APK Splits和Maintaining Multiple APKs。

更多精彩內容歡迎關注騰訊 Bugly的微信公眾賬號:

騰訊 Bugly是一款專為移動開發者打造的質量監控工具,幫助開發者快速,便捷的定位線上應用崩潰的情況以及解決方案。智慧合併功能幫助開發同學把每天上報的數千條 Crash 根據根因合併分類,每日日報會列出影響使用者數最多的崩潰,精準定位功能幫助開發同學定位到出問題的程式碼行,實時上報可以在釋出後快速的瞭解應用的質量情況,適配最新的 iOS, Android 官方作業系統,鵝廠的工程師都在使用,快來加入我們吧!