前言:
Android 開發(fā)中,一個(gè)成熟的App沒有了前期快速迭代的試錯(cuò),并且業(yè)務(wù)需求逐漸穩(wěn)定,功能穩(wěn)定,必然會(huì)面臨著包體增大的問題。導(dǎo)致的這樣的問題即與前期快速迭代無暇梳理app有關(guān),也與用戶增多功能日漸增加有關(guān),而不斷增大的包體,每次更新耗費(fèi)了用戶更多的流量,影響到用戶的用戶體驗(yàn),就會(huì)導(dǎo)致下載量的下降,成為公司的損失,在用戶獲取成本高的互聯(lián)網(wǎng)行業(yè),這種影響更為沉重;所以我個(gè)人希望減小App包體大小應(yīng)該是我們App開發(fā)者職業(yè)目標(biāo);
Android Apk包的組成:
一個(gè)Apk包的簡而言之有8部分組成:
classes.dex
編寫的所有的Java代碼(包括各種引入的sdk代碼)最終轉(zhuǎn)化成在Android虛擬機(jī)上運(yùn)行需要的字節(jié)碼(和java的字節(jié)碼有一定的區(qū)別)
res文件夾
存放所有資源的文件夾(除了里面raw文件夾的文件不會(huì)被編譯,其他都會(huì)被編譯)
resources.arsc
編譯后的二進(jìn)制資源文件
assets文件夾
用于保存需要保持原始文件的資源文件(這部分資源不會(huì)被編譯)
lib文件夾
用于存放應(yīng)用需要的native庫文件
AndroidManifest.xml
程序全局配置文件
META-INF文件夾
存放幾個(gè)簽名校驗(yàn)相關(guān)的文件,用于保證APK的完整性和安全性
其他
其他一些配置生成的文件
分析包體成分占比工具
優(yōu)化包體之前,我必須要了解到自己項(xiàng)目包體的中各個(gè)成分的大小,因?yàn)槊總€(gè)部分的優(yōu)化方案各不相同,只有充分了解包體的情況才能制定有效的壓縮方案,才能做到有的放矢。
通常來說分析包體有三種方式:
1、直接使用反編譯或壓縮工具解壓正式APK包,使用操作系統(tǒng)的文件命令排序顯示各個(gè)文件夾的大小
2、如果android studio是2.1.2之后地版本,可以直接將Apk文件拖進(jìn)studio中,studio會(huì)自動(dòng)進(jìn)行分析,列出排序表
3、使用NimbleDroid(點(diǎn)擊可跳轉(zhuǎn))在線工具,此工具是美國哥倫比亞一名教授帶領(lǐng)團(tuán)隊(duì)開發(fā),功能更全面,更強(qiáng)大。它除了了可以分析包體成分占比,還可以幫我分析第三放SDK的大小,以及方法數(shù)的多少,可以幫助我慎重選擇第三方庫(包體的大小有很大程度上就是選擇過多第三方庫導(dǎo)致的)
下面是我接手過的一個(gè)App沒有優(yōu)化之前的包體結(jié)構(gòu)以及成分占比圖:

壓縮包體的方法
一、壓縮資源文件
1、壓縮圖片
壓縮圖片,一般是指對圖片進(jìn)行質(zhì)量、尺寸壓縮和對圖片格式進(jìn)行轉(zhuǎn)換。前者通過在線圖片壓縮工具或者Linux 的ImgPack批處理腳本完成(現(xiàn)在比較好用的在線圖片的工具TinyPing),后者需要根據(jù)Jpg、png、gif等圖片格式的優(yōu)缺點(diǎn)進(jìn)行相應(yīng)的轉(zhuǎn)換,已達(dá)在保證顯示效果的情況下減少文件大小,如果對jpg等格式不理解,可以讓UI將一張圖同事生成三種格式,對比大小和顯示效果就可以了。
2、壓縮音視頻
多媒體文件通常有兩種壓縮方式,即有損壓縮與無損壓縮,對于有損壓縮網(wǎng)上有一些誤解,以為有損壓縮的音質(zhì)畫質(zhì)一定會(huì)很差,其實(shí)有損壓縮只是采用壓縮算法是忽略了原始數(shù)據(jù)中的一些數(shù)據(jù),但是都是人的生理極限為前提忽略的。比如視網(wǎng)膜屏做到了每英寸326的像素停止更精細(xì)的追求,高清電影的幀率達(dá)到60fps就沒在進(jìn)步,這些不是因?yàn)榧夹g(shù)達(dá)不到,而是人眼對分辨像素的極限但是每英寸326ps,對動(dòng)畫頻率的識別的極限是60fps,沒有必要再去要第327個(gè)像素和第61幀。圖片壓縮就是盡量壓縮掉人眼不可識別的冗余數(shù)據(jù),既保證顯示有照顧到圖片大小。
無損格式:WAV,PCM,ALS,ALAC,TAK,F(xiàn)LAC,APE,WavPack ( WV )
有損格式:MP3,AAC,WMA,Ogg Vorbis
實(shí)際開發(fā)中需要使用音頻文件盡量采用 MP3、Ogg 這種有損格式,盡量不要用 WAV、PCM 這種無損音頻。
3、在工程中保存一份圖片資源
開發(fā)目錄下會(huì)有個(gè)drawable或者mipmap目錄用于適配不同dpi的屏幕,目前市面上絕大部分機(jī)型都處于xxhdpi的適配范圍,所以可以考慮只保留xxhdpi目錄下一份圖片資源,具體保留哪個(gè)目錄下的資源和保留幾份資源還得依照應(yīng)用自身的實(shí)際機(jī)型分布決定
此種做法的理論依據(jù)可以瀏覽我收藏這兩篇博客:
Android drawable微技巧,你所不知道的drawable的那些細(xì)節(jié)
4、使用 Drawable XML、Color代替PNG圖片
一些情況下,我們可以考慮使用 Drawable XML 來代替 PNG,如:漸變的背景圖,用幾行 XML 就可以描繪出來,何必使用幾十到上百K的 PNG 文件。
用 Color 代替 PNG,如:純色的背景。
從性能上看,比起使用圖片資源需要先將其生成 Bitmap 再傳到底層交由 GPU 渲染,用Drawable XML和Color則更加高效,它是直接將 Shape 指令傳到底層由 GPU 進(jìn)行渲染,CPU和內(nèi)存的占用會(huì)更少。
5、不需要透明度時(shí)使用JPG代替PNG(這點(diǎn)是對1點(diǎn)的一個(gè)補(bǔ)充)
當(dāng)不需要透明度的圖片時(shí),可以考慮用JPG代替PNG,由于JPG沒有Alpha 通道,所以文件更小。
6、考慮使用SVG格式圖片去替換一些icon
SVG的全稱是Scalable Vector Graphics,叫可縮放矢量圖形。它和位圖(Bitmap)相對,SVG不會(huì)像位圖一樣因?yàn)榭s放而讓圖片質(zhì)量下降。它的優(yōu)點(diǎn)在于體積小,不用考慮屏幕適配問題。Android 5.0中引入了 VectorDrawable 來支持矢量圖(SVG),同時(shí)還引入了AnimatedVectorDrawable 來支持矢量圖動(dòng)畫。但是5.0以前的版本還是需要引入支持庫,也一定程度上增加了包體。所以說可以考慮使用。
二、移除無用資源
這一點(diǎn)主要是使用lint檢查并清除冗余資源(Android Studio 選中項(xiàng)目右鍵 => Analyze => Run Inspection by Name => 輸入 unused resuroces)。
如果你的資源是通過資源名稱使用Resources的getIdentifier(String name, String defType, String defPackage)方法去獲取到資源的id來使用資源(這種方式是通過反射的方法根據(jù)資源名稱去獲取資源的id),而不是直接通過R文件自動(dòng)生成的id來使用資源的,lint會(huì)檢測判定你這個(gè)文件并沒有被使用,而作為未使用的文件列出來。這時(shí)候就不能使用一鍵刪除的功能,需要確認(rèn)后自己手動(dòng)刪除。例如我要使用doodle.png這個(gè)資源,一般情況下我們是通過R.drawable.doodle去使用的,如下代碼
Resources res = context.getResources();Drawable doodleDrawable = res.getDrawable(R.drawable.doodle);
但是,有些時(shí)候我們需要在代碼中動(dòng)態(tài)地根據(jù)資源名稱去使用資源,這時(shí)候就要用到getIdentifier()去獲取到資源的id,然后再使用這個(gè)id去獲取資源,示例代碼如下:
Resources res = context.getResources();int doodleDrawableId = res.getIdentifier("doodle","drawable", context.getPackageName());Drawable doodleDrawable = res.getDrawable(doodleDrawableId);
這種情況就不能使用一鍵刪除的功能,需要確認(rèn)后自己手動(dòng)刪除。
三、使用APK Splits對APK進(jìn)行拆分
APK Splits能讓應(yīng)用程序更有效地構(gòu)建一些形式的多個(gè)apk。使用Splits構(gòu)建出來的APK是只含有不同的單套資源但功能用途一樣的APK。
APK Splits支持兩個(gè)維度:
屏幕密度 ABI
簡而言之,APK Splits就是我們要根據(jù)用戶的手機(jī)去提供基于不同屏幕分辨率(xxhdpi,mhdpi等),so庫版本的單個(gè)APK,并且應(yīng)用市場支持發(fā)布這種多個(gè)APK的功能(即要求應(yīng)用市場能根據(jù)用戶的手機(jī)的屏幕分辨率,CPU的架構(gòu)而為用戶選擇對應(yīng)的版本的APK提供下載)。這做法目前只適用于海外市場,因?yàn)槟壳爸挥蠫ooglePlay支持這種Multiple APK Support發(fā)布功能。不過我們可以個(gè)根據(jù)它的知識做一些我自己想做的事:
按屏幕密度拆分,配置代碼如下:
android {
splits {??
? density {?
? ? ?enable true? ?
? ?exclude "ldpi", "tvdpi", "xxxhdpi"
compatibleScreens 'small' , 'normal' , 'large' , 'xlarge'
}?
?}
enable: 啟用屏幕密度拆分機(jī)制
exclude: 默認(rèn)情況下,不設(shè)置這個(gè)屬性所有屏幕密度都包括在內(nèi),如果設(shè)置,則顯式聲明移除一些密度。
include: 表示要包括哪些屏幕密度
reset( ): 重置屏幕密度列表為只包含一個(gè)空字符串 (這能夠?qū)崿F(xiàn),在與include一起使用時(shí)可以表示使用哪一個(gè)屏幕密度,而不是要忽略哪一些屏幕密度)
compatibleScreens:表示兼容屏幕的列表。這將會(huì)注入到manifest中匹配的 節(jié)點(diǎn)。這個(gè)設(shè)置是可選的。
構(gòu)建完成后可以在out/apk/目錄下看到多個(gè)版本的APK。
按 ABI 拆分:
android {
splits {
abi {
enable true
reset()
include'x86','armeabi-v7a','mips'universalApk true
}
}
}
enable: 啟用ABI拆分機(jī)制
exclude: 不使用這個(gè)屬性默認(rèn)情況下所有ABI都包括在內(nèi),可以指明移除一些ABI。
include:指明要包含哪些ABI
reset():重置ABI列表為只包含一個(gè)空字符串(這可以實(shí)現(xiàn),在與include一起使用來可以表示要使用哪一個(gè)ABI,而不是要忽略哪一些ABI)
universalApk:指示是否打包一個(gè)通用版本(包含所有的ABI)。默認(rèn)值為 false。
四、減resources.arsc文件
簡單介紹下resources.arsc文件來源與作用:除了assets和res/raw資源被原裝不動(dòng)地打包進(jìn)APK之外,其它的資源都會(huì)被編譯或者處理。除了assets資源之外,其它的資源都會(huì)被賦予一個(gè)資源ID。打包工具負(fù)責(zé)編譯和打包資源,編譯完成之后,會(huì)生成一個(gè)resources.arsc文件和一個(gè)R.java,前者保存的是一個(gè)資源索引表,后者定義了各個(gè)資源ID常量,供在代碼中索引資源。
所有的png文件是以STORE的方式存儲(chǔ)到apk里的,關(guān)于zip里的STORE和DEFLATE,詳見:Zip (file format)
通俗的說,當(dāng)文件是STORED的方式存儲(chǔ)到zip,表示這個(gè)文件并沒有經(jīng)過壓縮,如果是Defl:N的方式,表示通過DEFLATED normal的方式壓縮存儲(chǔ)到zip。
現(xiàn)在業(yè)內(nèi)有一個(gè)開源的插件針對以上原理進(jìn)行了一定的壓縮,就是下面要講的
微信資源壓縮插件:AndResGuard(這推薦使用,根據(jù)我們上一個(gè)項(xiàng)目的經(jīng)驗(yàn),10MApk壓縮掉了將近1M)
其原理就是:
(1)對資源(png, xml, jpg等)名稱混淆,資源路徑名稱混淆以及名稱長度壓縮
(2)將原來以STORED形式存儲(chǔ)到zip中的png文件改成DEFLATED(普通壓縮存儲(chǔ))方式。
其Gitghub地址為(里面有詳細(xì)的接入流程):Android資源混淆工具使用說明
五、壓縮assets文件夾
assets目錄可以存字體文件、WEB頁面、配置文件、圖片文件。除了配置文件之外,其它的文件我們都可以進(jìn)行適當(dāng)?shù)膲嚎s處理:
字體文件:可以使用字體資源文件編輯神器Glyphs進(jìn)行壓縮,其壓縮方式其實(shí)就是通過刪除不需要的字符從而減少APK的大小。
WEB頁面:可以考慮使用7zip壓縮工具對該文件進(jìn)行壓縮,在正式使用的時(shí)候解壓
圖片文件:還是使用我們的Tinypng在線工具
六、減少lib文件夾
目前市場上移動(dòng)端主流的架構(gòu)主要是arm架構(gòu),所以可以考慮不支持x86和mips架構(gòu),而CPU是x86或mips架構(gòu)的手機(jī)使用放在arm目錄下的兼容so庫:
還有arm架構(gòu)中的eabi-v7a相比于eabi只是在圖形渲染方面有了很大的改進(jìn),所以如果so庫對圖形渲染沒有很高的要求的話,完全可以把so庫只存放在arm eabi目錄中,減少eabi-v7a
下面說下配置起,在 build.gradle 使用 abiFilters DSL 配置需要用到的 CPU 架構(gòu),并將不需要兼容的 so 文件從項(xiàng)目中移除。
defaultConfig {? ?
?? ? ndk{
? ? ?// 設(shè)置支持的so庫
? ? ? abiFilters 'armeabi', 'x86'
? ? ?}
? }
七、資源聯(lián)網(wǎng)加載
這種方式本質(zhì)是通過將APK里面的資源文件外置到服務(wù)器上,在需要時(shí)候再進(jìn)行加載。這種方案最常用的應(yīng)用的是游戲;如果是工具性應(yīng)用或者業(yè)務(wù)型應(yīng)用需要一些變通:
1、將一些對性能要求不高,或者UI變換頻繁的頁面考慮使用H5實(shí)現(xiàn)。這種做法的前提是需要優(yōu)化WebVIew的緩存功能,以便從第二次開始,每次開啟頁面都可以做到快速渲染,不影響交互體驗(yàn);
2、使用插件化開發(fā)。插件化開發(fā),在動(dòng)態(tài)加載和動(dòng)態(tài)修復(fù)技術(shù)框架基礎(chǔ)上,將業(yè)務(wù)抽成多個(gè)獨(dú)立的插件APk;