APK瘦身-是時候給App進行減負了

RyaneLee發表於2019-06-04

前言

APK瘦身即是對APK大小進行壓縮策略,減小APK安裝包大小,更小的安裝包更有助於吸引使用者安裝。前一段時間我司某一App進行APK的瘦身,最終也達到了減小10M的目標,現做一個簡單的總結記錄。

如何著手這個問題?

需要對一個App進行瘦身,首先最重要的就是對App大小有一個大致的瞭解,最直觀看到App的大小就是通過Android Studio自帶的Analyzer進行APK的分析。使用方法:

1、將一個apk拖動到Android Studio的編輯器視窗 
2、在Project視窗中,雙擊build/output/apks/目錄下的apk 
3、在選單欄中選擇選擇Build > Analyze APK,然後選擇要分析的apk
複製程式碼

圖1

獲取如上圖1所示的APK Size分析圖之後,我們就可以針對這裡面的目錄進行鍼對性的優化。

如圖最上方所示的APK Size就是我們應用打包之後的大小,Download Size則是上傳到Google Play之後,使用者下載的大小。所以我們一般可以只針對前一項的APK Size進行對比。

分析問題,發掘優化點

從上圖Analyzer可以發現,一個APK主要包含如下目錄:

  • lib:包含了一些區分於處理器的編譯程式碼,主要是SO檔案,一般裡面包含很多子目錄,例如armeabi, armeabi-v7a, arm64-v8a, x86, x86_64, and mips。
  • res:包含了一些不會被編譯到resources.arsc的資原始檔。如drawable檔案、layout檔案等。
  • assets:包含了一些通過AssetManager能夠檢索到的資源。如MP3、字型、webp等資原始檔。
  • META-INF:包含了CERT.SF和CERT.RSA簽名檔案,還有MANIFEST.MF檔案。

除此之外,還包含了如下的檔案:

  • resources.arsc:包括了所有可以被編譯的位於res/values/目錄下的XML資源。打包工具在打包過程中會把XML的內容編譯成二進位制的形式,亦或者把相關資源的引用路徑編譯成二進位制,然後整合到該檔案裡面。例如string檔案、layout的路徑、圖片的路徑等。

  • classes.dex:包含了所有的Java檔案編譯後的class檔案,class檔案最終轉化成該dex檔案。一般檔案都比較大,有的App有幾個dex檔案,這是因為單個DEX檔案限制方法數在65536,所以當程式碼量過大時,就需要通過multiDex進行分包,拆分成多個dex檔案,解決這個問題。

  • AndroidManifest.xml:整合了多個module的AndroidMainifest檔案的許可權、宣告等配置到該檔案。

知道了APK的組成部分,那麼我們就可以針對這些檔案/資料夾進行鍼對性的優化,每個App都不一樣,但是方法都是大同小異,本文講述瘦身策略也是針對這些目錄和檔案進行優化,這樣可以顯得更加有條理性。

1. lib目錄優化

Android系統現在支援7種CPU架構,每一種都關聯著對應的ABI(二進位制介面,Application Binary Interface),而每一種ABI都定義了二進位制檔案(尤其是.so檔案)如何執行在相應的系統平臺上,從使用的指令集,記憶體對齊到可用的系統函式庫等。所以如果我們的App需要適配不同的CPU架構的話,如下圖2所示,就需要放入不同架構的資料夾下都放入不同的so檔案,在打包時,這些so都會放在lib目錄下。由此,如果我們想減小lib目錄的大小,無非就如下一些常見的策略。

圖2

  • so裁剪、刪除

對App引入的so檔案進行確認哪些是不需要的,哪些是可以進行裁剪壓縮的,哪些是可以避免引入的。例如如果引入的so需要下載上傳功能而多引入了一個cURL庫導致so增大,這時就可以讓Java層程式碼定義介面,讓so來呼叫,從而避免引入cURL庫;再如Fresco庫,如果不需要webP圖,或者不需要webP動圖功能,然後減少Fresco庫的依賴,同樣可以減小so的大小。

  • 只保留armeabi或者armeabi-v7a

Android系統現在支援很多種CPU架構(如mips、arm、x86等),市面上主流機型都是arm架構,x86和mips型別極少。所以可以有選擇地保留某些架構的so,從而降低lib資料夾的大小。但是我建議你在開始前先對使用者手機的cpu型號進行一次統計,分析自身App對應架構手機的佔有率,這樣你才能大膽的進行操作,一般只保留armeabi或者armeabi-v7a即可。操作也是比較簡單,只需要在根目錄的build.gradle下配置:

android {
    buildTypes {
        ndk {
            abiFilters "armeabi-v7a"
        }
    }
}
複製程式碼

如果你的App需要支援多種架構,那麼就可以在abiFilters裡面把多種架構加進去,當然你也可以只保留一種,然後分渠道打包,如Google Play就支援arm和x86等多個渠道打包。

2.res目錄優化

res目錄一般也是佔APK Size大頭的一個目錄,如下圖,這個目錄一般都是圖片資源佔空間比較多,尤其當App為了適配多種解析度而存放了多套圖時,這時候就會導致res目錄打下會非常大。而這個目錄的優化方式也是比較多,下面就簡單列舉一下:

圖3

  • 只保留一套圖

因為Android裝置在載入圖片時會優先載入對應解析度資料夾下的圖片,如果對應解析度檔案下沒有所要的圖片,則找高解析度對應資料夾下的圖片。那是不是我們把圖片放在最高解析度的資料夾下就可以了呢?不是的!因為如果這樣會導致低解析度手機載入圖片時會消耗更多的記憶體,而且是指數級別增長的,所以如果盲目地放在一個目錄是不合適的。目前不同解析度對應優先載入的資料夾中圖片如下,如果是針對國內使用者的App可以只保留xxhdpi目錄,而如果是東南亞市場的App則可以只保留xhdpi。

320*240        ldpi

480*320        mdpi

800*480        hdpi

1280*720       xhdpi

1920*1080      xxhdpi
複製程式碼
  • 非重要圖片動態載入

針對一些非重要的圖片,可以選擇動態線上載入,嚴格來說,非首頁的圖片都可以動態載入,當然,為了提升使用者體驗,我們會把圖片放在本地。但是,一些使用場景非常小或者大小較大的圖片,大膽刪掉,選擇動態載入吧!

  • 保真壓縮圖片

可以使用一些圖片壓縮網站或者工具壓縮你的資原始檔吧,例如TinyPng、ImageOptim、Zopfli、智圖等。

  • 使用webp替換png

如果你的App只支援Android4.0以上的話,可以把png格式的圖片轉為webp,相同畫質下體積更小。

  • 使用lint刪除無用資源

在多人開發過程中,通常都會有漏刪無用資源的問題,圖片資源也不例外,例如需要刪除一個模組的程式碼時,很容易就會漏刪資原始檔,所以可以定期使用lint檢測出無用的資原始檔,原理這裡不作介紹,使用方法非常簡單,可以直接在AS裡面使用,如下圖所示。注意:lint檢查出來的資源都是無直接引用的,所以如果我們通過getIdentifier()方法引用檔案時,lint也會標記為無引用,所以刪除時注意不要刪除通過getIdentifier()引用的資源。

Analyze -> Run Inspection by Name -> 輸入:Unused resources -> 跳出彈框選擇範圍即可
複製程式碼
  • 開啟shrinkResources

shrinkResources是在編譯過程中用來檢測並刪除無用資原始檔,也就是沒有引用的資源,minifyEnabled 這個是用來開啟刪除無用程式碼,比如沒有引用到的程式碼,所以如果需要知道資源是否被引用就要配合minifyEnabled使用,只有兩者都為true時才會起到真正的刪除無效程式碼和無引用資源的目的。開啟方式也是非常簡單,在build.gralde檔案裡面開啟即可:

android {
    buildTypes{
        minifyEnabled true
        shrinkResources true
    }
}
複製程式碼

3.assests目錄優化

assests目錄存放的通常是一些通過AssetManager能夠檢索到的資源,包括MP3、視訊、字型、webp的資源,各個App存放內容都很大不相同,列舉一些常用的優化方案。

  • 刪除無用字型

中文字型一般都比較大,因為字型檔案包含了中文好幾千個漢字,但是我們實際上在App中並不會全部都使用,甚至我們只用到其中的幾個字,這時候我們就可以把字型檔案進行刪減,在Github上面有一個字型提取工具FontZip,使用方法也是非常簡單,有興趣可以去star一下。

圖4

  • 動態下載

一些MP3、視訊、Webp等資源可以在使用到時再進行下載,不需要放在本地。

  • 對資源進行壓縮

一些MP3、視訊、Webp等資源如果必須放在本地,可以壓縮成zip檔案或者使用7zip進行壓縮,在使用到時再進行解壓,減小空間的佔用。

4.META-INF目錄

該目錄下的MANIFEST.MF、CERT.SF、INDEX.LIST、CERT.RSA等檔案主要是存放一些APK檔案加密後的資訊,用以校驗APK的完整性和安全性,這個目錄沒有太好的優化方式,而且檔案一般也比較小,不會超過1M。

5.resources.arsc檔案壓縮

這個檔案包含所有可以被編譯的位於res/values/目錄下的XML資源,如下圖5所示是淘寶APK的resources.arsc檔案,像圖片的引用名字、layout檔案的引用名字、string資源等都被編譯到了這個檔案裡面。

圖5

所以如果我們需要對resources.arsc檔案進行優化,無非就是對路徑名字進行混淆,刪除無用的資源對映,前者可以使用AndResGuard,後者可以使用lint等進行檢測。

  • 刪除無用的語言

大部分應用都不需要支援幾十種上百種語言,所以在我們引用一些第三方庫時(如Google、Facebook的庫),它們往往帶有上百種多語言資源,而大部分多語言對於我們自己的應用是沒有用處的,我們只需要在build.gralde裡面進行如下配置即可完成無用語言資源的刪除,這樣在打包的時候就會排除私有專案、android系統庫和第三方庫中非中文的資原始檔了,效果還是比較顯著的。

android {
    //...
    defaultConfig {
        // 只保留中文
        resConfigs "zh"
    }
}
複製程式碼
  • 使用AndResGuard壓縮

AndResGuard是一個幫助你縮小APK大小的工具,他的原理類似Java Proguard,但是隻針對資源。他會將原本冗長的資源路徑變短,例如將res/drawable/wechat變為r/d/a。詳細使用方法參照Github,很簡單有效地減小resources.arsc檔案大小。如下圖6和圖7所示,圖6是壓縮前的效果,圖7是壓縮完的效果,如果是資源比較多的App,壓縮效果也是立竿見影。

圖6
圖7

使用方法也是非常簡單,在build.gradle檔案中進行如下配置即可:

andResGuard {
    mappingFile = null
    use7zip = true
    useSign = true
    keepRoot = false
    whiteList = [
        //for your icon
        "R.drawable.icon",
        //for fabric
        "R.string.com.crashlytics.*",
        //for umeng update
        "R.string.umeng*",
        "R.string.UM*",
        "R.layout.umeng*",
        "R.drawable.umeng*",
        //umeng share for sina
        "R.drawable.sina*"
    ]
    compressFilePattern = [
        "*.png",
        "*.jpg",
        "*.jpeg",
        "*.gif",
        "resources.arsc"
    ]
     sevenzip {
         artifact = 'com.tencent.mm:SevenZip:1.1.9'
         //path = "/usr/local/bin/7za"
    }
}
複製程式碼

6.dex檔案壓縮

Dalvik是Android平臺執行時的環境,但是Dalvik虛擬不支援直接執行Java的位元組碼,所以會對編譯生成的 .class 檔案進行翻譯、重構、解釋、壓縮等處理,這個處理過程是由 dx 進行處理,處理完成後生成的產物會以 .dex 結尾,稱為Dex檔案。

像淘寶、微信這些App,如果我們分析它們的APK可以發現,它們有多個Dex檔案,如下圖8所示,這是因為單個Dalvik Excutable(DEX)位元組碼檔案內的方法數不可以超過65536個,所以需要DEX分包配置來避免這個限制,使應用能夠構建並讀取DEX檔案。

圖8

  • Proguard程式碼混淆

Proguard是一款免費的Java類檔案壓縮器、優化器和混淆器,Android Studio已經整合了這個工具,只要經過簡單的配置,即可完成,如下程式碼所示,在build.gradle裡面設定minifyEnabled為ture,同時在proguardFiles指向proguard的規則檔案即可。

android {
    buildTypes{
        minifyEnabled true
        proguardFiles 'proguard.cfg'
    }
}
複製程式碼

總結

App瘦身是一個長期的過程,建議可以進行每個版本對APK大小進行監控,列出增加和減小的點,做到持續的統計和追蹤,從而給公司帶來效益。

相關文章