當前位置:
首頁 > 減肥 > Android App包瘦身優化實踐

Android App包瘦身優化實踐

隨著業務的快速迭代增長,美團App里不斷引入新的業務邏輯代碼、圖片資源和第三方SDK,直接導致APK體積不斷增長。包體積增長帶來的問題越來越多,如CDN流量費用增加、用戶安裝成功率降低,甚至可能會影響用戶的留存率。APK的瘦身已經是不得不考慮的事情。在嘗試瘦身的過程中,我們借鑒了很多業界其他公司提供的方案,同時也針對自身特點,發現了一些新的技巧。本文將對其中的一些做詳細介紹。

在開始講瘦身技巧之前,先來講一下APK的構成。

APK的構成

可以用Zip工具打開APK查看。比如,美團App 7.8.6的線上版本的格式是這樣的:

可以看到APK由以下主要部分組成:

存放so文件,可能會有armeabi、armeabi-v7a、arm64-v8a、x86、x86_64、mips,大多數情況下只需要支持armabi與x86的架構即可,如果非必需,可以考慮拿掉x86的部分

該文件夾一般存放於已經簽名的APK中,它包含了APK中所有文件的簽名摘要等信息

classes文件是Java Class,被DEX編譯後可供Dalvik/ART虛擬機所理解的文件格式

Android的清單文件,格式為AXML,用於描述應用程序的名稱、版本、所需許可權、註冊的四大組件

當然還會有一些其它的文件,例如上圖中的org/、src/、push_version等文件或文件夾。這些資源是Java Resources,感興趣的可以結合編譯工作流中的流程圖以及MergeJavaResourcesTransform的源碼看看被打入APK包中的資源都有哪些,這裡不做過多介紹。

在充分了解了APK各個組成部分以及它們的作用後,我們針對自身特點進行了分析和優化。下面將從Zip文件格式、classes.dex、資源文件、resources.arsc等方面來介紹下我們發現的部分優化技巧。

Zip格式優化

前面介紹了APK的文件格式以及主要組成部分,通過aapt l -v xxx.apk或unzip -l xxx.apk來查看APK文件時會得到以下信息,見下面截圖:

通過上圖可以看到APK中很多資源是以Stored來存儲的,根據Zip的文件格式中對壓縮方式的描述Compression_methods可以看出這些文件是沒有壓縮的,那為什麼它們沒有被壓縮呢?從AAPT的源碼中找到以下描述:/* these formats are already compressed, or don"t compress well */static const char* kNoCompressExt[] = { ".jpg", ".jpeg", ".png", ".gif", ".wav", ".mp2", ".mp3", ".ogg", ".aac", ".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet", ".rtttl", ".imy", ".xmf", ".mp4", ".m4a", ".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2", ".amr", ".awb", ".wma", ".wmv", ".webm", ".mkv"};

可以看出AAPT在資源處理時對這些文件後綴類型的資源是不做壓縮的,那是不是可以修改它們的壓縮方式從而達到瘦身的效果呢?

在介紹怎麼做之前,先來大概介紹一下App的資源是怎麼被打進APK包里的。Android構建工具鏈使用AAPT工具來對資源進行處理,來看下圖(圖片來源於Build Workflow):

點擊圖片查看大圖

通過上圖可以看到Manifest、Resources、Assets的資源經過AAPT處理後生成R.java、Proguard Configuration、Compiled Resources。其中R.java大家都比較熟悉,這裡就不過多介紹了。我們來重點看看Proguard Configuration、Compiled Resources都是做什麼的呢?

Proguard Configuration是AAPT工具為Manifest中聲明的四大組件以及布局文件中(XML layouts)使用的各種Views所生成的ProGuard配置,該文件通常存放在${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/proguard-rules/${flavorName}/${buildType}/aapt_rules.txt,下面是項目中該文件的截圖,紅框標記出來的就是對AndroidManifest.xml、XML Layouts中相關Class的ProGuard配置。

Compiled Resources是一個Zip格式的文件,這個文件的路徑通常為${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/res/resources-${flavorName}-${buildType}-stripped.ap_。 通過下面經過Zip解壓後的截圖,可以看出這個文件包含了res、AndroidManifest.xml和resources.arsc的文件或文件夾。結合Build Workflow中的描述,可以看出這個文件(resources-${flavorName}-${buildType}-stripped.ap_)會被apkbuilder打包到APK包中,它其實就是APK的「資源包」(res、AndroidManifest.xml和resources.arsc)。

我們就是通過這個文件來修改不同後綴文件資源的壓縮方式來達到瘦身效果的,而在後面「resources.arsc的優化」一節中也是操作的這個文件。

筆者在自己的項目中是通過在package${flavorName}Task(感興趣的同學可以查看源碼)之前進行這個操作的。

下面是部分代碼片段:appPlugin.variantManager.variantDataList.each { variantData ->

當然也可以在其它構建步驟中採用更高壓縮率的方式來達到瘦身效果,例如採用7Zip壓縮等等。

本技巧的使用需要注意以下問題:如果音視頻資源被壓縮存放在APK中的話,在使用一些音頻、視頻API時尤其要注意,需要做好充分的測試。resources.arsc文件最好不要壓縮存儲,如果壓縮會影響一定的性能(尤其是冷啟動時間)。如果想在Android 6.0上開啟android:extractNativeLibs=」false」的話,.so 文件也不能被壓縮,android:extractNativeLibs的使用姿勢看這裡:App Manifest --- application。

classes.dex的優化

如何優化classes.dex的大小呢?大體有如下套路:

時刻保持良好的編程習慣和對包體積敏銳的嗅覺,去除重複或者不用的代碼,慎用第三方庫,選用體積小的第三方SDK等等。開啟ProGuard來進行代碼壓縮,通過使用ProGuard來對代碼進行混淆、優化、壓縮等工作。

針對第一種套路,因各個公司的項目的差異,共性的東西較少,需要case by case的分析,這裡不做過多的介紹。

壓縮代碼

可以通過開啟ProGuard來實現代碼壓縮,可以在build.gradle文件相應的構建類型中添加minifyEnabled true。

請注意,代碼壓縮會拖慢構建速度,因此應該儘可能避免在調試構建中使用。不過一定要為用於測試的最終APK啟用代碼壓縮,如果不能充分地自定義要保留的代碼,可能會引入錯誤。

例如,下面這段來自build.gradle文件的代碼用於為發布構建啟用代碼壓縮:android {

除了minifyEnabled屬性外,還有用於定義ProGuard規則的proguardFiles屬性:

getDefaultProguardFile(『proguard-android.txt")是從Android SDKtools/proguard/文件夾獲取默認ProGuard設置。proguard-rules.pro文件用於添加自定義ProGuard規則。默認情況下,該文件位於模塊根目錄(build.gradle文件旁)。

提示:要想做進一步的代碼壓縮,可嘗試使用位於同一位置的proguard-android-optimize.txt文件。它包括相同的ProGuard規則,但還包括其他在位元組碼一級(方法內和方法間)執行分析的優化,以進一步減小APK大小和幫助提高其運行速度。在Gradle Plugin 2.2.0及以上版本ProGuard的配置文件會自動解壓縮到${rootProject.buildDir}/${AndroidProject.FD_INTERMEDIATES}/proguard-files/目錄下,proguardFiles會從這個目錄來獲取ProGuard配置。

每次執行完ProGuard之後,ProGuard都會在${project.buildDir}/outputs/mapping/${flavorDir}/生成以下文件:

可以通過在usage.txt文件中看到哪些代碼被刪除了,如下圖中所示android.support.multidex.MultiDex已經被刪除了:

R Field的優化

除了對項目代碼優化和開啟代碼壓縮之外,筆者在《美團Android DEX自動拆包及動態載入簡介》這篇文章中提到了通過內聯R Field來解決R Field過多導致MultiDex 65536的問題,而這一步驟對代碼瘦身能夠起到明顯的效果。下面是筆者通過位元組碼工具在構建流程中內聯R Field的代碼片段(位元組碼的修改可以使用Javassist或者ASM,該步驟筆者採用的是Javassist)。ctBehaviors.each { CtBehavior ctBehavior -> if (!ctBehavior.isEmpty()) { try {

其它優化手段

針對代碼的瘦身還有很多優化的技巧,例如:

減少ENUM的使用(詳情可以參考:Remove Enumerations),每減少一個ENUM可以減少大約1.0到1.4 KB的大小;通過pmd cpd來檢查重複的代碼從而進行代碼優化;移除掉所有無用或者功能重複的依賴庫。

這些優化技巧就不展開介紹了。

資源的優化

圖片優化

為了支持Android設備DPI的多樣化([l|m|tv|h|x|xx|xxx]dpi)以及用戶對高質量UI的期待,美團App中使用了大量的圖片,在Android下支持很多格式的圖片,例如:PNG、JPG 、WebP,那我們該怎麼選擇不同類型的圖片格式呢? 在Google I/O 2016中提到了針對圖片格式的選擇,來看下圖(圖片來源於Image compression for Android developers):

通過上圖可以看出一個大概圖片格式選擇的方法。如果能用VectorDrawable來表示的話優先使用VectorDrawable,如果支持WebP則優先用WebP,而PNG主要用在展示透明或者簡單的圖片,而其它場景可以使用JPG格式。針對每種圖片格式也有各類的優化手段和優化工具。

使用矢量圖片

可以使用矢量圖形來創建獨立於解析度的圖標和其他可伸縮圖片。使用矢量圖片能夠有效的減少App中圖片所佔用的大小,矢量圖形在Android中表示為VectorDrawable對象。 使用VectorDrawable對象,100位元組的文件可以生成屏幕大小的清晰圖像,但系統渲染每個VectorDrawable對象需要大量的時間,較大的圖像需要更長的時間才能出現在屏幕上。 因此只有在顯示小圖像時才考慮使用矢量圖形。有關使用VectorDrawable的更多信息,請參閱 Working with Drawables。

使用WebP

如果App的minSdkVersion高於14(Android 4.0+)的話,可以選用WebP格式,因為WebP在同畫質下體積更小(WebP支持透明度,壓縮比比JPEG更高但顯示效果卻不輸於JPEG,官方評測quality參數等於75均衡最佳), 可以通過PNG到WebP轉換工具來進行轉換。當然Android從4.0才開始WebP的原生支持,但是不支持包含透明度,直到Android 4.2.1+才支持顯示含透明度的WebP,在筆者使用中是判斷當前App的minSdkVersion以及圖片文件的類型(是否為透明)來選用是否適用WebP。見下面的代碼片段:boolean isPNGWebpConvertSupported() { if (!isWebpConvertEnable()) { return false

選擇更優的壓縮工具

可以使用pngcrush、pngquant或zopflipng等壓縮工具來減少PNG文件大小,而不會丟失圖像質量。所有這些工具都可以減少PNG文件大小,同時保持圖像質量。

pngcrush工具特別有效:此工具在PNG過濾器和zlib(Deflate)參數上迭代,使用過濾器和參數的每個組合來壓縮圖像。然後選擇產生最小壓縮輸出的配置。

對於JPEG文件,你可以使用packJPG或guetzli等工具將JPEG文件壓縮的更小,這些工具能夠在保持圖片質量不變的情況下,把圖片文件壓縮的更小。guetzli工具更是能夠在圖片質量不變的情況下,將文件大小降低35%。

在Android構建流程中AAPT會使用內置的壓縮演算法來優化res/drawable/目錄下的PNG圖片,但也可能會導致本來已經優化過的圖片體積變大,可以通過在build.gradle中設置cruncherEnabled來禁止AAPT來優化PNG圖片。aaptOptions {

開啟資源壓縮

Android的編譯工具鏈中提供了一款資源壓縮的工具,可以通過該工具來壓縮資源,如果要啟用資源壓縮,可以在build.gradle文件中將shrinkResources true。例如:android {

需要注意的是目前資源壓縮器目前不會移除values/文件夾中定義的資源(例如字元串、尺寸、樣式和顏色),有關詳情,請參閱問題 70869。

Android構建工具是通過ResourceUsageAnalyzer來檢查哪些資源是無用的,當檢查到無用的資源時會把該資源替換成預定義的版本。詳看下面代碼片段(摘自com.android.build.gradle.tasks.ResourceUsageAnalyzer):public class ResourceUsageAnalyzer {

上面截圖中3個byte數組的定義就是資源壓縮工具為無用資源提供的預定義版本,可以看出對.png提供了TINY_PNG,對.9.png提供了TINY_9PNG以及對.xml提供了TINY_XML的預定義版本。

資源壓縮工具的詳細使用可以參考Shrink Your Code and Resources。資源壓縮工具默認是採用安全壓縮模式來運行,可以通過開啟嚴格壓縮模式來達到更好的瘦身效果。

如果想知道哪些資源是無用的,可以通過資源壓縮工具的輸出日誌文件${project.buildDir}/outputs/mapping/release/resources.txt來查看。如下圖所示res/layout/abc_activity_chooser_viewer.xml就是無用的,然後被預定義的版本TINY_XML所替換:

資源壓縮工具只是把無用資源替換成預定義較小的版本,那我們如何刪除這些無用資源呢?通常的做法是結合資源壓縮工具的輸出日誌,找到這些資源並把它們進行刪除。但在筆者的項目中很多無用資源是被其它組件或第三方SDK所引入的,如果採用這種優化方式會帶來這些SDK後期維護成本的增加,針對這種情況筆者是通過採用在resources.arsc中做優化來解決的,詳情看下面「resources.arsc的優化」一節的介紹。

語言資源優化

根據App自身支持的語言版本選用合適的語言資源,例如使用了AppCompat,如果不做任何配置的話,最終APK包中會包含AppCompat中消息的所有已翻譯語言字元串,無論應用的其餘部分是否翻譯為同一語言,可以通過resConfig來配置使用哪些語言,從而讓構建工具移除指定語言之外的所有資源。下圖是具體的配置示例:android {

針對為不同DPI所提供的圖片也可以採用相同的策略,需要針對自身的目標用戶和目標設備做一定的選擇,可以參考Support Only Specific Densities來操作。有關屏幕密度的詳細信息,請參閱Screen Sizes and Densities。對.so文件也可以採用類似的策略,比如筆者的項目中只保留了armeabi版本的.so文件。

resources.arsc的優化

針對resources.arsc,筆者嘗試過的優化手段如下:

開啟資源混淆;對重複的資源進行優化;對被shrinkResources優化掉的資源進行處理。

下面將分別對這些優化手段進行展開介紹。

資源混淆

在筆者另一篇《美團Android資源混淆保護實踐》文章中介紹了採用對資源混淆的方式來保護資源的安全,同時也提到了這種方式有顯著的瘦身效果。筆者當時是採用修改AAPT的相關源碼的方式,這種方式的痛點是每次升級Build Tools都要修改一次AAPT源碼,維護性較差。目前筆者採用了微信開源的資源混淆庫AndResGuard,具體的原理和使用幫助可以參考安裝包立減1M--微信Android資源混淆打包工具。

無用資源優化

在上一節中介紹了可以通過shrinkResources true來開啟資源壓縮,資源壓縮工具會把無用的資源替換成預定義的版本而不是移除,如果採用人工移除的方式會帶來後期的維護成本,這裡筆者採用了一種比較取巧的方式,在Android構建工具執行package${flavorName}Task之前通過修改Compiled Resources來實現自動去除無用資源。

具體流程如下:

收集資源包(Compiled Resources的簡稱)中被替換的預定義版本的資源名稱,通過查看資源包(Zip格式)中每個ZipEntry的CRC-32 checksum來尋找被替換的預定義資源,預定義資源的CRC-32定義在ResourceUsageAnalyzer,下面是它們的定義。// A 1x1 pixel PNG of type BufferedImage.TYPE_BYTE_GRAY

通過android-chunk-utils把resources.arsc中對應的定義移除;刪除資源包中對應的資源文件。

重複資源優化

目前美團App是由各個業務團隊共同開發完成,為了方便各業務團隊的獨立開發,美團App進行了平台化改造。改造時存在很多資源文件(如:drawable、layout等)被不同的業務團隊都拷貝到自己的Library下,同時為了避免引發資源覆蓋的問題,每個業務團隊都會為自己的資源文件名添加前綴。這樣就導致了這些資源文件雖然內容相同,但因為名稱的不同而不能被覆蓋,最終都會被集成到APK包中,針對這種問題筆者採用了和前面「無用資源優化」一節中描述類似的策略。

具體步驟如下:

通過資源包中的每個ZipEntry的CRC-32 checksum來篩選出重複的資源;通過android-chunk-utils修改resources.arsc,把這些重複的資源都重定向到同一個文件上;把其它重複的資源文件從資源包中刪除。

代碼片段:variantData.outputs.each { def apFile = it.packageAndroidArtifactTask.getResourceFile();

通過這種方式可以有效減少重複資源對包體大小的影響,同時這種操作方式對各業務團隊透明,也不會增加協調相同資源如何被不同業務團隊復用的成本。

總結

上述就是我們目前在APK瘦身方面的做的一些嘗試和積累,可以根據自身情況取捨使用。當然我們還可以採取一些按需載入的策略來減少安裝包的體積。最後提一點,砍掉不必要的功能才是安裝包瘦身的超級大招。一個好的App的標準有很多方面,但提供儘可能小的安裝包是其中一個重要的方面,這也是對我們Android開發者人員自身的提出的基本要求,要時刻保持良好的編程習慣和對包體積敏銳的嗅覺。

參考文獻

Android application package (APK)Zip (file format))Build WorkflowAndroid AAPT Source CodeReduce APK SizeShrink Your Code and ResourcesManage Your App"s MemoryVector DrawableJavassistASMpngcrushpngquantzopflipngandroid-chunk-utils安裝包立減1M--微信Android資源混淆打包工具減少APK 的大小,Android 官方這樣說Google I/O 2016 筆記:APK 瘦身的正確姿勢

作者簡介

建帥,Android技術專家,2015年3月加入美團點評,目前就職於到店餐飲技術部信息與交易技術中心。

到店餐飲技術部交易與信息技術中心,負責美團點評美食用戶端業務,服務於數以億計用戶,通過更好的榜單、真實的評價和完善的信息為用戶提供更好的決策支持,致力於提升用戶體驗;同時承載所有餐飲商戶端線上流量,為餐飲商戶提供多種營銷工具,提升餐飲商戶營銷效率,最終達到讓國人「Eat Better、Live Better」的美好願景!我們的團隊包含且不限於Android、iOS、FE、Java、PHP等技術方向,已完備覆蓋前後端技術棧。只要你來,就能點亮全棧開發技能樹。

不想錯過技術博客更新?想給文章評論、和作者互動?第一時間獲取技術沙龍信息?

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 瘦身 的精彩文章:

木耳跟這道蔬菜搭配炒來吃,益脾和胃不說還能瘦身養生,好吃到爆!
燕麥粥助你成功瘦身 這樣喝效果最佳
久坐容易胖,一杯醋泡蘋果能瘦身 製作簡單又好喝 你不可錯過
被國人忽略的瘦身好動作:「瘦身、矯正身型兩不誤」

TAG:瘦身 |

您可能感興趣

Android 瘦身之道——so文件
XLS Medical Direct 天然水果消脂瘦身沖劑90包,size加大碼到小碼全靠它……
跑步瘦身故事:I just wanna run
瘦身幹了這杯Green Smoothie!歐美明星都愛的ins網紅慕斯雪製作攻略
Angelababy產後瘦身18斤的秘密全在這裡
超營養瘦身Smoothie配方公開
Slenderiiz智能體重管理系統助您輕鬆瘦身不反彈!
撇代孕疑雲!楊穎Angelababy產後神回A4腰 瘦身獻3秘訣
90斤女生的瘦身果汁食譜,一周搭配不重樣 ‖ Yoga LfieStyle
Angelababy產後瘦身秘籍只需四套減肥操輕鬆瘦腰豐臀
Tina Malone瘦身成功後活得更加自信!
如何瘦身成功?聯想ideapad 710s評測體驗
歌手Ailee的瘦身經歷
小米5X:你有freestyle嗎?MIUI 9:瘦身很流暢
揭秘 新晉辣媽Angelababy孕後健康瘦身秘籍
揭秘,新晉辣媽Angelababy孕後健康瘦身秘籍,其實你也可以和Baby一樣美!
伐!開!心!沒錢買包包,但是也要攢錢買 FreezeFrame Tummy Tuck瘦身減肥收腹霜
七日瘦身飲,這就是膳魔師的freestyle
速看!Angelababy產後暴瘦18斤!瘦身秘訣大公開!