天天看點

APK瘦身——更全面的方案

前陣子做了一次減包體的工作,過後覺得這項工作還是套路性挺強的,于是綜合當時的經驗以及廣泛浏覽了網上的各種大牛小牛的部落格,實踐後特總結了以下的一些思路和做法,不求内容最豐富,隻求方案更全面。

APK瘦身的目的

瘦身的目的最明顯的一個就是:提高下載下傳轉化率。怎麼了解呢?舉個例子來說,假如你的應用包12MB,有100個潛在使用者想要去下載下傳嘗試使用你的應用,結果有20個使用者嫌棄安裝包太大而直接放棄,有20個使用者在等待下載下傳的過程中取消下載下傳,最終隻有60個使用者下載下傳安裝了應用。這時你的應用的實際的下載下傳轉化率其實是 60/100 = 60%。

簡單的總結:安裝包越小,使用者下載下傳等待的時間越短,對手機配置要求的也越小,裝置的體驗愈佳,應用的下載下傳轉化率也就越高。

APK包體的組成

開始瘦身前,需要先了解一下APK都主要由哪些成分組成

  1. classes.dex

    編寫的所有的Java代碼(包括各種引入的sdk代碼)最終轉化成在Android虛拟機上運作需要的位元組碼(和java的位元組碼有一定的差別)

  2. res檔案夾

    存放所有資源的檔案夾(除了裡面raw檔案夾的檔案不會被編譯,其他都會被編譯)

  3. resources.arsc

    編譯後的二進制資源檔案

  4. assets檔案夾

    用于儲存需要保持原始檔案的資源檔案(這部分資源不會被編譯)

  5. lib檔案夾

    用于存放應用需要的native庫檔案

  6. AndroidManifest.xml

    程式全局配置檔案

  7. META-INF檔案夾

    存放幾個簽名校驗相關的檔案,用于保證APK的完整性和安全性

  8. 其他

    其他一些配置生成的檔案

分析現APK各成分的比例

了解清楚APK的各個組成成分後,就需要有針對性地對自己的APK各個成分做一個比例分析。

工具1:Android Studio 2.2及其以上的版本

Android Studio自2.2版本以來就引入了分析APK各成分的比例的功能,用法也挺簡單的,主要有2種操作方法,如下:

  1. 導航欄的Build →Analyze APK…→選擇APK檔案的路徑→選擇OK打開即可
    APK瘦身——更全面的方案
    APK瘦身——更全面的方案
  2. 可以直接把APK包拖進IDE,也可以得到如下的比例分析構成圖(比例會自動按從大到小排好序呈現)
    APK瘦身——更全面的方案
    可以看出,在其中一般占比比較大的一般都是dex檔案,res檔案夾,assets檔案夾,lib檔案夾以及resource.arsc檔案。是以接下來的工作就是有針對性地讓這些檔案和檔案夾盡量地變小。在開始下一步之前,要簡要介紹另外一個工具。

工具2:NimbleDroid

NimbleDroid是美國哥倫比亞大學的博士創業團隊研發出來的分析Android app性能名額的系統,分析的方式有靜态和動态兩種方式,其中靜态分析可以分析出APK安裝包中大檔案排行榜,各種知名SDK的大小以及占代碼整體的比例,各種類型檔案的大小以及占排行,各種知名SDK的方法數以及占所有dex中方法數的比例。這個外國網站的工具目前比Android Studio自帶的功能更加全面和強大。這裡僅作簡要介紹,讀者若有興趣可自行去研究。

有針對性地對各部分做縮減工作

一、減classes.dex檔案——壓縮代碼

代碼壓縮通過 ProGuard 提供,ProGuard 會檢測和移除封裝應用中未使用的類、字段、方法和屬性,包括自帶代碼庫中的未使用項(這使其成為以變通方式解決 64k 引用限制的有用工具)。ProGuard 還可優化位元組碼,移除未使用的代碼指令,以及用短名稱混淆其餘的類、字段和方法。混淆過的代碼可令您的 APK 難以被逆向工程。

要啟用通過 ProGuard 實作的代碼壓縮,要在對應moudle(一般情況下是主moudle,build.gradle檔案第一行為apply plugin: ‘com.android.application’)的build.gradle 檔案相應的建構類型中添加 minifyEnabled true。

注意,ProGuard會拖慢建構速度,是以應該盡可能避免在調試版本建構中使用它。另:Android Studio 會在使用 Instant Run 時停用 ProGuard。

如下為建構release版本的build.gradle的示例片段:

android {
    buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile(‘proguard-android.txt'),
                    'proguard-rules.pro'
        }
    }
    ...
}
           

其中的proguardFiles 屬性:用于定義 ProGuard 規則

getDefaultProguardFile(‘proguard-android.txt’) 為擷取預設的 ProGuard規則檔案,它位于Android SDK tools/proguard/ 檔案夾。(提示:要想做進一步的代碼壓縮,可嘗試使用位于同一位置的 proguard-android-optimize.txt 檔案。它包括相同的 ProGuard 規則,除此之外還包括其他在位元組碼一級(方法内和方法間)執行分析的優化,以進一步減小 APK 大小和幫助提高其運作速度。)

proguard-rules.pro 檔案用于添加自定義的 ProGuard 規則。預設情況下,該檔案位于moudle根目錄(build.gradle 檔案旁)

每次建構時 ProGuard 都會輸出下列檔案,這些檔案儲存在 /build/outputs/mapping/release/

  • dump.txt

    說明 APK 中所有類檔案的内部結構。

  • mapping.txt

    列出了原始的類,方法和字段名與混淆後代碼間的映射。(這個檔案很重要,當你從release版本中收到一個bug報告時,可以用它來翻譯被混淆的代碼)

  • seeds.txt

    列出未進行混淆的類和成員

  • usage.txt

    列出從 APK 移除的代碼

  • resources.txt

    列出resource被保留的資源

自定義混淆規則

對于某些情況,預設 ProGuard 配置檔案 (proguard-android.txt) 足以滿足需要,ProGuard 會移除所有(并且隻會移除)未使用的代碼。不過,ProGuard 難以對許多情況進行正确分析,可能會移除應用真正需要的代碼。舉例來說,它可能錯誤移除代碼的情況包括:

  • 當應用引用的類隻來自 AndroidManifest.xml 檔案時
  • 當應用調用的方法來自 Java 原生接口 (JNI) 時
  • 當應用在運作時(例如使用反射或自檢)操作代碼時

要修正錯誤并強制 ProGuard 保留特定代碼,可以在 ProGuard 配置檔案中添加一行 keep 代碼。例如:

-keep public class MyClass

還可以在想保留的代碼添加 @Keep 注解。在類上添加 @Keep 可原樣保留整個類。在方法或字段上添加它可完整保留方法/字段(及其名稱)以及類名稱。(請注意,隻有在使用注解支援庫時,才能使用此注解。)

有關自定義proguard-rules.pro檔案的更多資訊,可以參考 ProGuar手冊.這裡問題排查列出了一些常見的問題。本文不講解這部分知識。

解碼混淆過的堆疊追蹤

本來本章主要講解壓縮代碼的,一般都不會涉及到如何解碼混淆過的堆疊追蹤的。考慮到網上很多參考資料隻是講了如何壓縮混淆代碼卻沒有告訴我們如何去解碼混淆過的堆疊追蹤以便快速定位出現崩潰的代碼的。是以當你發現剛好差某樣東西的時候,這恰恰就是你的機會了。

在 ProGuard 壓縮代碼後,讀取堆疊追蹤變得困難(即使并非不可行),因為方法名稱經過了混淆處理。幸運的是,ProGuard 每次運作都會建立一個 mapping.txt 檔案,其中顯示了與混淆過的名稱對應的原始類名稱、方法名稱和字段名稱。ProGuard 将該檔案儲存在應用的 /build/outputs/mapping/release/ 目錄中。

請注意,每次使用 ProGuard 建立釋出建構時都會覆寫 mapping.txt 檔案,是以每次釋出新版本時都要必須小心地儲存一個副本。通過為每個釋出建構保留一個 mapping.txt 檔案副本,就可以在使用者送出的已混淆堆疊追蹤來自舊版本應用時對問題進行調試。

如果是在Google Play 上釋出應用,可以上傳每個 APK 版本的 mapping.txt 檔案。Google Play 将根據使用者報告的問題對收到的堆疊追蹤進行去混淆處理,以便你在 Google Play Developer Console 中進行檢查。

如果要自行将混淆過的堆疊追蹤轉換成可讀的堆疊追蹤,請使用 retrace 腳本(在 Windows 上為 retrace.bat;在 Mac/Linux 上為 retrace.sh)。它位于 /tools/proguard/ 目錄中。該腳本利用 mapping.txt 檔案和您的堆疊追蹤生成新的可讀堆疊追蹤。使用 retrace 工具的文法如下:

retrace.bat|retrace.sh [-verbose] mapping.txt []

例如:

retrace.bat -verbose mapping.txt obfuscated_trace.txt

如果不指定堆疊追蹤檔案,retrace 工具會從标準輸入讀取

其實,最簡單的方法還是自己先檢視混淆後的堆疊追蹤檔案,根據自己的經驗找出最後導緻崩潰的追蹤資訊,然後拿這個追蹤資訊去mapping.txt檔案中直接按查找鍵查找。例如,假如我最後出錯的代碼定位的資訊為com.go.cj.b.c(b.java 1234),那我們可以拿com.go.cj.b去mapping.txt檔案中直接按查找鍵查找。

對于壓縮代碼,以上是工具能為我們做的,但是我們在平時的開發中養成一些習慣,也會有利于我們的代碼縮減,一些良好的習慣建議如下:

  • 移除廢棄功能的代碼

    不要隻是注釋,擔心可能以後會用到。因為一般的開發都會使用版本控制工具VCS,是以這種擔心是多餘的。

  • 定期review項目代碼

    發現有重複功能實作的代碼或者架構要及時重構,删除重複部分的代碼和架構。出現重複功能代碼的情景大多如下:已經有了的功能代碼或架構,團隊成員不知道自己又寫了一套或者引入另外的相同功能的架構。這種情況的發生也反映出你們團隊之間的溝通存在一定的問題。有可能是個人經驗問題,也有可能是整個團隊的規範,共識問題,導緻大家都不重視。

  • 謹慎引入第三方架構

    這裡僅從包體大方面來考慮,如果你僅僅是隻需要解析幾個json字段的值,那麼就沒有必要引入json解析架構,android和java本身封裝的api就可以完全應付需求了。殺雞不用牛刀,其他方面的需求也是一樣的。

  • 選擇小而精的庫,而不是大而全的

    比如減小對 Support 相容包的依賴,Support-V4 包非常大,項目引入無疑會增大 dex 檔案的大小,Google 已經意識到這個問題,是以 Support-V7 一開始就做了拆分,并且開始對 Support-V4 做拆分,雖然目前成果還不明顯,不過還是挺值得期待的,特别是發現你少了 Support-V4 包後,可能就從 2 個 dex 變成 1 個 dex 了。

    又比如,如果隻用到了谷歌統計,那麼就不要把整個google play services都內建進來,隻內建需要的那部分。現在優秀的庫都是小而精的,不追求什麼大而全。

  • 業務子產品采用插件化架構,代碼動态從雲端拉取

    插件化,一種懶加載思想的展現,先讓使用者能夠安裝宿主包,對于一些功能子產品做插件化,在特定的時機再下載下傳安裝。

二、減res檔案夾

res檔案夾裡面主要就是包括各種布局檔案,value檔案,圖檔檔案,原生檔案。這一塊的處理方案主要采用2種方式,一是壓縮資源,二是删除未使用的資源。

1. 壓縮資源

壓縮資源又分2種思路,一是對資源進行壓縮,二是使用更小的資源來替換目前的資源。

  • 壓縮圖檔

    一般而言圖檔壓縮對減小Apk大小所産生的效果占到你所有減小Apk努力的效果50%以上。下面推薦一款目前所知圖檔壓縮效果最好的網站TinyPing。目前除了要求美工在提供圖檔的時候就把圖檔放上網站去壓縮,我們也可以使用别人利用tinypng提供的jar包做的批量處理本地圖檔的tinyPIC gradle plugin。它在build 中插入一個新的tinyPicPlugin task.周遊尋找項目res中以drawable開頭的檔案夾中的圖檔資源,調用tiny API進行壓縮工作并替換原來的檔案。它的接入方法可以參考這裡TinyPIC Gradle Plugin。

  • 有損編碼格式的音頻檔案代替無損格式的音頻檔案

    從下面這篇官方文檔Supported Media Formats可以看到 Android 平台支援的音視訊格式,下面列出有損和無損常用的格式(不要認為有損編碼就是音質很差):

    無損格式:WAV,PCM,ALS,ALAC,TAK,FLAC,APE,WavPack ( WV )

    有損格式:MP3,AAC,WMA,Ogg Vorbis

    實際開發中需要使用音頻檔案盡量采用 MP3、Ogg 這種有損格式,盡量不要用 WAV、PCM 這種無損音頻。

  • 盡量隻儲存一份圖檔資源

    開發目錄下會有個drawable或者mipmap目錄用于适配不同dpi的螢幕,目前市面上絕大部分機型都處于xxhdpi的适配範圍,是以可以考慮隻保留xxhdpi目錄下一份圖檔資源,具體保留哪個目錄下的資源和保留幾份資源還得依照應用自身的實際機型分布決定。至于為什麼可以考慮一份圖檔資源,可以去了解一下以下2篇文章:

    Android drawable微技巧,你所不知道的drawable的那些細節

    那些值得你去細細研究的Drawable适配

  • 使用 Drawable XML、Color代替PNG圖檔

    一些情況下,我們可以考慮使用 Drawable XML 來代替 PNG,如:漸變的背景圖,用幾行 XML 就可以描繪出來,何必使用幾十到上百K的 PNG 檔案。

    用 Color 代替 PNG,如:純色的背景。

    從性能上看,比起使用圖檔資源需要先将其生成 Bitmap 再傳到底層交由 GPU 渲染,用Drawable XML和Color則更加高效,它是直接将 Shape 資訊傳到底層由 GPU 進行渲染,CPU和記憶體的占用會更少。

  • 不需要透明度時使用JPG代替PNG

    當不需要透明度的圖檔時,可以考慮用JPG代替PNG,由于JPG沒有Alpha 通道,是以檔案更小。

  • 考慮使用WEBP圖檔資源格式

    WebP是谷歌研發出來的一種圖檔資料格式,它是一種支援有損壓縮和無損壓縮的圖檔檔案格式,如果應用支援到Android 4.0+,那麼我們可以使用WebP格式代替PNG,我們的資源大小能降低50%多。

    不過就目前來說,對于4.0+ 到 4.2.1 ,原生隻支援完全不透明的webp圖,4.2.1+ 對于webp的是完全支援的(包含半透明的webp圖)。是以說對于4.2.2(API17)以下的版本,還是需要引入相容庫來解決。但是另外一點,引入相容庫又會導緻包體變大。不過這種增量或許和把所有png圖換成webp所帶來的減量比較或許不值得一提,特别是圖檔特别多的應用,這種增量幾乎可以不計。另外也要注意的是,某些國産rom會代理類Resource為自己定義的,例如小米2刷成4.xx的手機上,小米機器代理了類Resource為MIUIResource,但是這個MIUIResource未能正确識别webp資源,會導緻加載資源檔案失敗而出現崩潰。是以應該考慮自己使用者的機型分布,考慮使用WEBP圖檔資源格式替換png格式。

    谷歌也在緻力于推動WEBP圖檔資源的使用,目前Android studio 2.3中加入了對圖檔壓縮的工具,可以直接将PNG,BMP,JPG和靜态的Gif圖檔檔案轉成Webp格式。以下為詳細轉換方法:

    Android studio 2.3 Webp使用

    詳細的使用方法:

    Android Webp 完全解析 快來縮小apk的大小吧

  • 考慮使用SVG格式圖檔去替換一些icon

    SVG的全稱是Scalable Vector Graphics,叫可縮放矢量圖形。它和位圖(Bitmap)相對,SVG不會像位圖一樣因為縮放而讓圖檔品質下降。它的優點在于體積小,不用考慮螢幕适配問題。Android 5.0中引入了 VectorDrawable 來支援矢量圖(SVG),同時還引入了AnimatedVectorDrawable 來支援矢量圖動畫。但是5.0以前的版本還是需要引入支援庫,也一定程度上增加了包體。是以說可以考慮使用。

2. 移除無用的資源

這裡的移除無用的資源,主要是指2個方面,一是在工程裡面直接删除沒有使用的資源,二是不打包沒有使用的資源。

2.1 在工程裡面直接删除沒有使用的資源

這一點主要是使用lint檢查并清除備援資源。如果你的資源是通過資源名稱使用Resources的getIdentifier(String name, String defType, String defPackage)方法去擷取到資源的id來使用資源(這種方式是通過反射的方法根據資源名稱去擷取資源的id),而不是直接通過R檔案自動生成的id來使用資源的,lint會檢測判定你這個檔案并沒有被使用,而作為未使用的檔案列出來。這時候就不能使用一鍵删除的功能,需要确認後自己手動删除。例如我要使用doodle.png這個資源,一般情況下我們是通過R.drawable.doodle去使用的,如下代碼

Resources res = context.getResources();
Drawable doodleDrawable = res.getDrawable(R.drawable.doodle);
           

但是,有些時候我們需要在代碼中動态地根據資源名稱去使用資源,這時候就要用到getIdentifier()去擷取到資源的id,然後再使用這個id去擷取資源,示例代碼如下:

Resources res = context.getResources();
int doodleDrawableId = res.getIdentifier("doodle", "drawable", context.getPackageName());
Drawable doodleDrawable = res.getDrawable(doodleDrawableId);
           

這種情況就不能使用一鍵删除的功能,需要确認後自己手動删除。

具體使用步驟:

Android Studio 選中項目右鍵 => Analyze => Run Inspection by Name => 輸入 unused resuroces

2.2 不打包不需要使用的資源

這個方面也有2個可行的思想,一是利用Android Plugin開啟gradle 的Resource shrinking進行建構打包,這時候沒有被使用的資源将不會打進包裡。二是不打包未使用(不需要)的替代資源。

(1) 利用Android Plugin開啟gradle 的Resource shrinking

Resource shrinking的使用

Resource shrinking 需要和Code shrinking 一起使用。在代碼中删除所有未使用的代碼後,Resource shrinking才可以知道哪些資源APK程式仍然使用,你必須先删除未使用的代碼,Resource才會成為無用的,進而被清除掉。Code shrinking部分在上面的減classes.dex檔案——壓縮代碼 一節中已經講過了。下面為Resource shrinking的使用具體步驟:

添加shrinkResources true屬性在你的 build.gradle檔案中,相應代碼塊如下:

android {
    buildTypes {
        release {
            shrinkResources true
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'),
                    'proguard-rules.pro'
        }
    }
}
           

resource shrinker 目前還不支援移除定義在values/目錄下的資源檔案(strings,dimensions,styles,colors),因為Android Asset Packaging Tool(AAPT)不允許Gradle Plugin指定預定義的版本資源[issue 70869]

指定要忽略的資源檔案或者一定要删除的資源檔案

如果我們希望保留或丢棄特定的資源,需要在項目中建立一個XML檔案,并使用resources标簽,并使用tools:keep屬性明确指定每個資源保留,或者使用tools:discard屬性明确指定這個資源将要被舍棄移除。兩個屬性都可以使用逗号(,)分隔符聲明資源名稱清單。也可以使用* 作為比對符,比對名稱。

例如,XML檔案,命名為keep.xml,這個檔案需要儲存在:res/raw/keep.xml,這樣build的時候該檔案才不會被打包到APK裡面,示例代碼塊如下:

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
           tools:discard="@drawable/home"
           tools:keep="@drawable/doodle, @layout/doodle, @drawable/doodle_*">
</resources>
           

啟用嚴格的檢測

resource shrinker 也通過搜尋代碼中是否包含資源名或者具有比對符相同的名稱來判斷是否在build的時候删除資源。是以,通常情況下,resource shrinker可以準确地确定資源使用。但是如果你在代碼中使用Resources.getIdentifier()顯式通過資源名稱動态擷取指定資源的Id,在預設情況下,這樣資源具有比對名稱的格式為潛在的使用,無法去除。

例如,下面的代碼将導緻所有img_字首的資源都無法去除。

String name = String.format("img_%1d", angle + );
res = getResources().getIdentifier(name, "drawable", getPackageName());
           

這是因為,Gradle 在處理該資源檔案時候的方式,遇到被判斷為潛在使用的情況下,預設的值為 safe。這時候就需要在上面的keep.xml檔案中指定 shrinkMode為strict(隻會保留有明确引用的資源,以及處理被 tools:keep 和 tools:discard 标注的資源)。示例代碼塊如下:

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
          tools:shrinkMode="strict">
</resources>
           

但是這裡面還有另外一種特殊情況就是,當上面代碼中的檔案名name沒有顯式地出現“img_”這個字元的時候,不需要指定 shrinkMode為strict模式建構的時候Gradle也能删除具有字首為“img_”字元名稱的資源。也就是說,當我把name寫在其他非代碼類的檔案中,然後在代碼中讀取的時候,這時候resource shrinker 通過搜尋代碼是掃描不出相關的資源名稱“img_”字元來的,是以這個時候具有字首為“img_”字元名稱的資源都會被删除了。是以要特别注意這種特殊情況。

(2) 不打包未使用(不需要)的替代資源

Gradle resource shrinker 隻删除你在代碼中未使用資源,這意味着它不會删除不同的裝置配置的可替代資源。如果有必要,可以使用Android Gradle plugin 的resconfigs屬性删除替代資源檔案。

例如:我們項目中适配10種國家語言,而項目依賴了v7、v4等其他support包裡面包含20種國家語言,那麼我們可以通過resconfigs 删除剩餘的可替代資源檔案,這對于我們APK大小可減少不少。

以下代碼說明了如何限制語言資源:

android {
    defaultConfig {
        ...
        resConfigs "en", "fr"
    }
}
           

以上resconfigs 屬性隻指定了英語和法語這2個語言的資源會被打進包裡。未指定的語言的任何資源都被删除。當然如果不設定resconfigs屬性,預設會把所有的語言資源都會打進包裡。

同樣的思想我們可以運用在圖檔資源上,但是我們不能做到幾種螢幕密度的資源打在同一個包裡面,另外的幾種不要了。我們隻能做到打包一個隻有1種螢幕密度圖檔資源的包。就是我們下面要講的使用APK Splits建構不同替代資源的APK。

(3)使用APK Splits建構不同替代資源的多個APK

APK Splits比起使用 flavors,能讓應用程式更有效地建構一些形式的多個apk。注意:這裡的多個APK和使用flavors建構出來的多個APK是不同的。使用Splits建構出來的APK是隻含有不同的單套資源但功能用途一樣的APK,而flavors建構出來的APK是含有同樣的資源但卻是功能用途不一樣的APK。

APK Splits多APK隻支援以下類型:

  • 螢幕密度
  • ABI

正是由于Splits建構出來的APK隻含有單套可替換資源,是以它的适用情景就是我們要根據使用者的手機去提供基于不同螢幕分辨率(xxhdpi,mhdpi等),so庫版本的單個APK,并且應用市場支援釋出這種多個APK的功能(即要求應用市場能根據使用者的手機的螢幕分辨率,CPU的架構而為使用者選擇對應的版本的APK提供下載下傳)。而目前隻有GooglePlay支援這種Multiple APK Support釋出功能,是以你的應用如果不是在GooglePlay上面釋出的話,這種拆分多個APK釋出的做法,你就了解一下就行了。下面繼續介紹

按螢幕密度拆分,配置代碼如下:

android {
  ...
  splits {
    density {
      enable true
      exclude "ldpi", "tvdpi", "xxxhdpi"
      compatibleScreens 'small', 'normal', 'large', 'xlarge'
    }
  }
           

enable: 啟用螢幕密度拆分機制

exclude: 預設情況下,不設定這個屬性所有螢幕密度都包括在内,如果設定,則顯式聲明移除一些密度。

include: 表示要包括哪些螢幕密度

reset( ): 重置螢幕密度清單為隻包含一個空字元串 (這能夠實作,在與include一起使用時可以表示使用哪一個螢幕密度,而不是要忽略哪一些螢幕密度)

compatibleScreens:表示相容螢幕的清單。這将會注入到manifest中比對的 節點。這個設定是可選的。

建構完成後可以在out/apk/目錄下看到多個版本的APK。

按 ABI 拆分,配置代碼如下:

android {
  ...
  splits {
    abi {
      enable true
      reset()
      include 'x86', 'armeabi-v7a', 'mips'
      universalApk true
    }
  }
}
           

enable: 啟用ABI拆分機制

exclude: 不使用這個屬性預設情況下所有ABI都包括在内,可以指明移除一些ABI。

include:指明要包含哪些ABI

reset():重置ABI清單為隻包含一個空字元串(這可以實作,在與include一起使用來可以表示要使用哪一個ABI,而不是要忽略哪一些ABI)

universalApk:訓示是否打包一個通用版本(包含所有的ABI)。預設值為 false。

Splits隻支援這2種類型的分類,更多的多版本支援的知識是下一節的内容。

(5)使用多版本的APK釋出

Multiple APK Support是一個在Google Play,可以釋出不同的應用程式,分别針對不同的裝置配置特征。每個APK是一個完整的、獨立的應用程式版本,但他們分享在Google Play相同的應用程式清單,必須共享相同的包名和與簽名。Google Play 會自動給你比對相應的APK,這樣我們的APK 就可以是分不同版本建構需要資源檔案,進而減小APK的大小。

Multiple APK Support支援以下:

  • 支援不同OpenGL的APK
  • 支援不同的螢幕尺寸和密度的APK
  • 支援不同的裝置功能的APK
  • 支援不同的平台版本的APK
  • 支援不同的CPU架構,每個apk(如ARM、x86,MIPS等)的APK
  • 支援不同的平台版本的APK

更多相關資訊請參考Multiple APK Support,這裡不詳細叙述。

三、減resources.arsc檔案

簡單介紹下resources.arsc檔案來源與作用:除了assets和res/raw資源被原裝不動地打包進APK之外,其它的資源都會被編譯或者處理。除了assets資源之外,其它的資源都會被賦予一個資源ID。打包工具負責編譯和打包資源,編譯完成之後,會生成一個resources.arsc檔案和一個R.java,前者儲存的是一個資源索引表,後者定義了各個資源ID常量,供在代碼中索引資源。

所有的png檔案是以STORE的方式存儲到apk裡的,關于zip裡的STORE和DEFLATE,詳見:Zip (file format)

通俗的說,當檔案是STORED的方式存儲到zip,表示這個檔案并沒有經過壓縮,如果是Defl:N的方式,表示通過DEFLATED normal的方式壓縮存儲到zip。

現在業内有一個開源的插件針對以上原理進行了一定的壓縮,就是下面要講的

微信資源壓縮插件:AndResGuard

其原理就是:

  • (1)對資源(png, xml, jpg等)名稱混淆,資源路徑名稱混淆以及名稱長度壓縮
  • (2)将原來以STORED形式存儲到zip中的png檔案改成DEFLATED(普通壓縮存儲)方式。

其Gitghub位址為(裡面有詳細的接入流程):Android資源混淆工具使用說明

四、減assets檔案夾

assets目錄可以存放各種檔案,正常情況下,一般隻存放以下幾種檔案:字型檔案、WEB頁面、配置檔案、圖檔檔案。

上述幾種檔案除了配置檔案之外,我們都可以進行适當的壓縮處理:

字型檔案:可以使用字型資源檔案編輯神器Glyphs進行壓縮,其壓縮方式其實就是通過删除不需要的字元進而減少APK的大小。

WEB頁面:可以考慮使用7zip壓縮工具對該檔案進行壓縮,在正式使用的時候解壓

圖檔檔案:可以使用tinypng進行圖檔壓縮

五、減lib檔案夾

lib目錄用于存放通過C或C++編寫編譯生成的so檔案(native庫/JNI開發)。

因為目前市場上主流的架構還主要是arm架構,是以如果不是必要的話,可以考慮不支援x86和mips架構,但這并不意味着CPU是x86或mips架構的手機就不能正常安裝使用APK了,因為放在arm目錄下的so庫是可以相容到其他架構的;

另外arm架構中的eabi-v7a相比于eabi隻是在圖形渲染方面有了很大的改進,是以如果so庫對圖形渲染沒有很高的要求的話,完全可以把so庫隻存放在arm eabi目錄中,這樣可以大大減小APK的體積。

lib瘦身主要是減小對 CPU 架構的支援,配置起來很簡單,在 build.gradle 使用 abiFilters 配置需要用到的 CPU 架構,并将不需要相容的 so 檔案從項目中移除即可。

示例代碼塊如下:

defaultConfig {
        ndk{
            // 設定支援的so庫
            abiFilters 'armeabi', 'x86'
        }
}
           

總結

以上所述的方法,并不需要全部都應用,而是應該根據實際情況來選用其中适合實際情況的最有效的方法。根據筆者使用過的實際情況來說,以下幾種方式對于減少包體是效果最明顯的且施行起來也不難的:

  • 1.對代碼進行ProGuard混淆
  • 2.對圖檔資源進行壓縮
  • 3.使用Gradle resource shrinker 在打包時删除不需要的資源
  • 4.使用微信資源壓縮插件對資源進行混淆
  • 5.在可行的情況下使用WEBP圖檔資源替換現有的PNG圖檔

歡迎大家斧正、交流、補充!

繼續閱讀