APK瘦身探索
最近幾週一直在研究如何為APK瘦身,折騰了很久,是時候寫篇部落格總結一下了,雖然已經準備了下週一要在客戶端週會分享用的PPT:APK瘦身探索。
價值
雖然說APK瘦身對於Android對應用可分配記憶體的限制影響不大,但是還是有一些影響的,就以圖片
為例,將一些小圖示替換為iconfont能有效減小記憶體的分配,防止OOM的出現。
另外,無論是iOS開發者還是Android開發者都應該嘗試最好學會如何為IPA或APK瘦身,不僅僅是為了幫助使用者省流量、減少下載時間、減少佔用的儲存空間等等,更重要的是為了提高轉化率(注意:本文的轉化率均指下載轉化率)。
那轉化率是什麼呢?
舉個例子:你的應用大小是 18MB ,有100個潛在使用者想要去下載嘗試使用,結果有20個使用者嫌棄安裝包太大直接揚長而去,有20個使用者在等待下載的過程中取消下載,最終只有60個使用者真正下載安裝,那麼應用的轉化率就是 60/100 = 60% 。
那為什麼要提高轉化率呢?
因為使用者在糾結下載你的產品還是你的競品的時候,往往會選擇那個體驗最好、功能最多、效能最好、包最小的。
工具
如何有條理的為APK瘦身,必須知道APK的目錄結構,不過在講述這個之前,我這裡先介紹幾個工具,在後文會用到。
AndroidStudio2.2.3
AndroidStudio升級到版本2.2.3之後提供了Analyze APK的功能(不過作為AS粉想必已經升到AS2.3了吧,哈哈),我們可以藉助該工具清楚的瞭解APK的下載大小、解壓之後的大小、內部各個資料夾或檔案佔用的大小等資訊,進而得知那些地方可以優化。下圖為目前達人店APK的內部資訊:
另外使用該工具還可以反編譯資原始檔、還原layout中的資源id,分析DEX、顯示每一個資料夾或檔案的方法數,分析哪些第三方庫方法數很多但實際只用到了一部分等其他功能。
最後如何使用該工具?有以下兩種方式:
- 直接將APK拖拽進入AndroidStudio即可
- 點選選單欄Build-Analyze APK...選項,再選擇需要分析的APK即可
NimbleDroid
NimbleDroid 是美國哥倫比亞大學的博士創業團隊研發出來的自動化分析Android app效能指標的系統,分析的方式有靜態和動態兩種方式:
其中靜態分析可以分析出APK安裝包中大檔案排行榜,各種知名SDK的大小以及佔程式碼整體的比例,各種型別檔案的大小以及佔排行,各種知名SDK的方法數以及佔所有dex中方法數的比例,針對緩慢的方法,緩慢的第三方SDK和記憶體洩漏。
其中動態分析可以測量生成的速度、網路、記憶體和磁碟使用率。
我用了一下上面這個工具,感覺還是不錯的,下面我們來看幾張有逼格的圖(圖中資料來自於達人店APK):
APK內部各型別對應檔案佔用的大小
APK內部各個類庫的方法數
APK啟動過程中各個方法的執行時間
ClassShark
ClassShark 是一款檢視Android執行檔案(apk)的瀏覽工具,目前有兩個android App(Apk)和桌面(jar)的版本。
使用這款工具,可以很方便的開啟APK、Class、Jar、res等檔案和分析裡面的內容。
不過目前這個工具並不需要我們單獨使用,因為AS內部的分析工具就是用的這個。
通過以上工具,我們可以方便快速的分析APK,找出那些可以優化的部分,為了更加有條理的進行講解,下面我會按照APK的目錄結構來逐條闡述。
APK目錄結構
上述表格左邊部分是APK內部預設存在的資料夾或檔案,表格右邊對於各個資料夾或檔案進行了簡要的概述,詳細的說明會在之後根據左邊的順序一一講解。
assets目錄
assets目錄用於存放需要打包到應用程式的靜態檔案,它包含以下幾個特性:
- 使用AssetManager類管理資源
- assets目錄內部檔案不會被系統編譯
- assets目錄支援任意深度的子目錄
獲取資源需要使用/assets開始(不包含它)的相對路徑名,具體程式碼如下所示:
String fileNames[] = context.getAssets().list(path);複製程式碼
那麼,assets目錄下都可以放什麼檔案呢?
說實在的,assets目錄可以存放各種檔案,不過正常情況下,一般只存放以下幾種檔案:字型檔案、WEB頁面、配置檔案、某些圖片。
上述幾種檔案除了配置檔案之外,我們都可以進行適當的壓縮處理:
字型檔案
:可以使用字型資原始檔編輯神器Glyphs進行壓縮,其壓縮方式其實就是通過刪除不需要的字元從而減少APK的大小。
WEB頁面
:可以考慮使用7zip壓縮工具對該檔案進行壓縮,在正式使用的時候解壓
某些圖片
:可以使用tinypng進行圖片壓縮, 目前tinypng已經支援png和jpg圖片、.9圖的壓縮
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的體積。
上面的說明可能比較籠統,下面就拿淘寶、微信的APK來說明一下:
可以看出淘寶和微信也是這樣處理,所以我們其實也可以這樣操作。
res目錄
res目錄用於存放應用程式的資原始檔,主要包括佈局檔案、圖片、XML配置檔案等,它包含以下幾個特性:
- 使用Resources類管理資源
- res目錄內部檔案會被系統編譯
- res目錄不支援任意深度的子目錄
- 獲取資源不需要通過相對路徑找尋,因為檔案會被系統編譯,所以需要通過資源ID(注:資源ID被存放在resources.arsc檔案中)查詢
其基本目錄結構如下圖所示:
上圖比較全面的列舉了res目錄下經常包含的子目錄和檔案,並解釋了各個子目錄或檔案的含義。
根據上圖我們顯然可以發現,res目錄就是我們APK瘦身裡面的一大重要部分了,由於其包含的知識很大,下面我會分章節進行闡述,儘量一一闡述清楚。
只用一套圖
首先我給出這麼一個結論:一個APK儘量只用一套圖片,從記憶體佔用和適配的角度考慮,建議放在xhdpi資料夾下。
那麼為什麼要把圖片放在xhdpi資料夾下面呢?
由於此處知識涉及到Android螢幕適配方面的知識,比較複雜。本文是關於APK瘦身的,所以就不詳細講解了。下面進行必要的闡述,首先讓我們來看兩張圖:
從上面兩張圖我們可以得到以下兩點資訊:
- Android主要的裝置解析度為:1280*720、1920*1080
- iOS主要的裝置解析度為:1334*750、2208*1242、1136*640
可能有人要問了,得到這兩點資訊有什麼用?
恩,單單這樣看並不能知道什麼,為了幫助大家瞭解,我繪製了下面這樣表格。不過在展示表格之前,我先為大家灌輸兩個知識點:
1、ppi計算公式
2、在Android裝置中,dpi 等價於 ppi
恩,此處請停留10秒......下面展示我繪製的表格:
有兩個注意點:
- 表格中上兩行代表的是Android的裝置解析度及相關資料,下三行代表的是iOS的裝置解析度及相關資料
- Android螢幕密度為320或480是由谷歌規定的,當然這裡排除各大廠商自行修改的情況
根據上述表格我們可以清楚的知道iOS流行的裝置解析度正好對應Android流行的裝置解析度,那麼知道這又有什麼用?
除了一開始給出結論的記憶體和適配因素外,更重要的是可以節省設計資源和工作量。在現在的App開發中(iOS和Android),有些設計師為了保持iOS和Android的體驗互動一致,可能會以iPhone手機為基礎進行設計,包括後期的切圖之類的。
不過根據上述表格,我們不是應該使用兩套圖嗎?這裡由於在Android裝置中xhdpi和xxhdpi目錄下的圖片顯示效果差異不大,所以完全可以只使用一套圖。
現在我們的裝置中只有一套圖了,接下來該怎麼為APK瘦身呢?
處理圖片
當我們從設計師手中得到設計稿之後,之後的工作顯然就是切圖了,如果設計師切好圖就更好了,得到圖片之後我們一定要記得壓縮
。
如果圖片型別是.png或.jpg的話,我們可以使用tinypng進行壓縮;如果圖片型別是.gif的話,我建議使用PS進行壓縮或裁剪,如果你不會PS的話,可以讓設計師幫忙。
如果你對圖片壓縮質量不滿意的話,還可以考慮使用不帶alpha值的jpg圖片、9Patch圖片、同等質量下檔案更小的WebP圖片格式、或者使用SVG替換某些圖片資源等其他方式。
下面對於最後兩種方式進行簡要的闡述:
首先是WebP
:AndroidStudio自2.3版本之後提供了Convert to WebP的功能,選中res目錄後右擊滑到底部即可看到此功能,下面的引用講述了WebP的概念和支援度:
WebP是Google新推出的影像技術,它可讓網頁圖檔有效進行壓縮,同時在質量相同的情況下,WebP格式影像的體積要比JPEG格式影像小40%,進而讓整體網頁下載速度加快。為了改善JPEG的圖片壓縮技術,他們使用了一種基於VP8編碼的圖片壓縮器,利用預測編碼技術,同時還採用了一種基於RIFF的非常輕量級的容器。這種容器只會給每張圖片增加20位元組,但能讓圖片作者儲存他們想要儲存的後設資料。
android同樣作為google的產品,minsdk為4.0即api14以上就可以支援webp,但是對透明的圖片會存在一些問題,minsdk為4.2.1+即api17可以完美支援webp。
考慮目前市場4.2.1以下的手機佔比已經非常稀少,採用webp格式代替jpg、png的方案非常可行。
下面展示一下收益頁面背景圖片png形勢下和WebP形勢下各自的大小:
其次是SVG
:SVG是可縮放向量圖形(Scalable Vector Graphics),它使用XML格式定義影像,可用於替換APK中的圖示。
我們開發者不需要會如何製作,這是設計師的工作,當然設計師也不會傻乎乎通過寫程式碼的方式繪製圖示,顯然是使用軟體製作的。
目前達人店也使用了SVG,不過它的表現形式是阿里巴巴提供的iconfont,它不僅允許設計師自己製作SVG圖片上傳,也提供了百萬種圖示供選擇。
減少資源
經過上述的方式處理圖片,想必在圖片質量方面已經無計可施了,所以我們只能通過刪除無用圖片的方式來為APK瘦身了,當然接下來要介紹的兩種方式可不僅僅是減少圖片,應該稱之為減少資源
。
這裡有個問題:為什麼會出現無用的資源呢?我認為有以下幾種情況:
- 由於多人協作開發,沒有好的規範,導致個人思維嚴重,隨便新增或刪除資源
- 目前公司有好的規範,但之前沒有,歷史遺留問題,沒人願意去管理
- 上一版需要該資源,下一版不需要,然後沒有刪除
那麼,既然知道了原因,就應該想出辦法去解決,首先是最簡單的辦法,我們只需要在主模組的gradle檔案中配置shrinkResources
即可,具體配置方法如下圖所示:
這就是為什麼說它是最簡單的減少資源方法的原因。
當然,因為簡單必然存在缺陷,因為它只能從打包的應用程式和第三方程式碼庫中刪除未被引用的資源。如果在不同的資原始檔夾下面有同名的資原始檔,那麼就沒有辦法刪除了,即使該資源真的沒有被使用。
所以,沒有好的辦法了,目前我能想到的只能是手動刪除了,我們可以通過AndroidStudio提供的Remove Unused Resources功能預覽刪除真正未使用的資源,選中res目錄右擊選擇Refactor-Remove Unused Resources...選項之後會出現下面這樣的圖:
然後根據查詢到結果,逐條雙擊開啟檔案,按快捷鍵fn+option+F7進行查詢,然後在分析是否應該刪除,注意:此處定要慎重,務必自測!!!。
另外,我們還可以通過將某些圖片轉換網路圖片的方式解決,但是這個操作也是需要慎重的,最重要的一點是不能影響使用者的體驗。這邊路飛同學製作了一個Chrome外掛來幫助我們快速上傳圖片到cdn中,可以通過此處下載:joyuploader。
最後,還有一種辦法:使用AndroidStudio提供的Lint工具對工程做靜態程式碼檢查,它不僅可以找出我們在程式碼編寫上面的失誤,還能夠列出那些未被使用的資源,甚至還可以指出哪些地方可能存在記憶體洩漏等等,功能非常龐大,所以如果工程較大的話,還是比較耗時的,當然這並不是問題,因為我們可以針對某個模組執行靜態程式碼檢查。我們可以通過選中選單欄Analyze-Inspect Code...選項執行靜態程式碼檢查,執行完成的效果圖如下所示:
資源混淆
目前我們不僅在資源(特指圖片)質量方面做到了極致,在資源數量方面也做到了極致,看起來真的到極致了。其實不然,我們還可以使用資源混淆的方式為APK瘦身,通過壓縮檔案內容,減少檔名長度的方式實現。
目前比較出名的資源混淆方式是微信的AndResGuard和美團的修改AAPT,不過由於美團的資源混淆方法非常麻煩,還有如果需要通過getIdentifier
的方式獲取資源時需要保證這些資源的名字不被混淆,美團很難實現,所以目前大家都在用微信的資源混淆方式,因為微信使用方便,而且提供了白名單。
下面是達人店APK未使用微信資源混淆和使用了微信資源混淆的差異:
可以清楚的看到,在使用了微信資源混淆之後,APK減少了0.7MB左右,效果還是十分明顯的。
那麼為什麼使用了微信資源混淆之後可以實現APK瘦身呢?下面這兩張圖清楚的展示了瘦身的原因(摘自微信資源混淆官方文件):
總結,安裝包大小減少的原因以下四個:
相對的,可得到影響效果的因素有以下幾個:
可以說,直到現在,我們對於資源的壓縮猜到了一個極致,下面是一些針對於某些資源的壓縮辦法。
其他資源
- 如果raw資料夾下有音訊檔案,儘量不要使用無損的音訊格式,比如wav。可以考慮相比於mp3同等質量但檔案更小的opus音訊格式。
- 能不用圖片的就不用圖片,除了通過前面提過的使用9Patch圖、iconfont實現外,還可以使用shape程式碼實現,也可以考慮引進VectorDrawable(向量圖) ,從5.0(API等級21)開始,android開始支援向量圖。目前向量圖相容到API7,向量圖動畫相容到API11。這裡就不詳細講解了。
終於講完了res部分,碼字好累,接下來就相對輕鬆了,首先我們要講解的是classes.dex檔案。
classes.dex
首先,什麼是classes.dex?classes.dex檔案是Android系統的可執行檔案,包含應用程式的全部操作指令以及執行時資料。
這裡稍微介紹一下classes.dex檔案的生成過程及對應的命令:
java原始碼 -> class位元組碼:[javac -source 1.6 -target 1.6 com/package1/*.java com/package2/*.java]
class位元組碼 -> jar包:[jar cvf abc.jar com/package1/*.class com/package2/*.class]
jar包 -> dex檔案:[dx --dex --output ***/abc.jar ***/abc_dex.jar],***是abc.jar的絕對路徑。
從jar包到dex檔案的過程是通過dx工具完成的,它的目的是使各個類能夠共享資料,在一定程度上降低了冗餘,同時也使檔案結構更加緊湊,實驗表明,dex檔案是傳統jar檔案大小的50%左右,具體表現形式可以看下圖:
既然dx工具已經辦幫我們壓縮了那麼多,那我們還有什麼好壓縮的呢?
當然有,因為dx工具並沒有壓縮class的內容,所以我們的原始碼並沒有得到壓縮,下面我先介紹兩種常見的辦法來解決這個問題:
和之前最簡單的資源壓縮方式一樣,我們也可以在主模組的gradle檔案中配置
minifyEnabled
實現程式碼混淆,從而減小java檔案的大小。因為混淆後的程式碼將較長的檔名、例項、變數、方法名等等做了簡化,從而實現位元組長度上的優化。我們也可以使用之前所說的AndroidStudio提供的Lint工具執行靜態程式碼檢查,進而刪除無用的類、方法、變數等。
上面的兩種方式雖然能夠幫助我們減少APK的大小,但是實際上我們還可以做的更多。因為上面兩種方式是自動化的,所以必然不像人那麼智慧。
有時候我們可能遇到這樣的情況:我們引用了一個第三方類庫,但是隻用到了其中的幾個功能,其他的大部分功能一直不用,這不是白白的浪費了使用者的流量,降低了APK的下載轉化率麼?為了解決這樣的問題,我們必須藉助一些工具去找到這樣的類庫,比如在文章開頭介紹過的工具:NimbleDroid,下面是達人店APK目前存在的某個這樣的第三方類庫:
該類庫在達人店APK中只用到了掃一掃和生成二維碼這兩個功能,然而這個類庫定義的方法數竟然有1428
,顯然不合理,完全可以抽離其內部的掃一掃和生成二維碼程式碼,單獨實現。
ReDex
最後介紹一下Facebook開源的一個減少APK大小以提高效能的工具 -- ReDex,它通過內嵌以及清除殭屍程式碼這樣的優化方式來減少位元組碼,其主要是對DEX做了優化,能夠讓APK執行更快,不過需要多測試是否會崩潰。
下面分別是ReDex的教程地址和GitHub地址:
facebook/redex: A bytecode optimizer for Android apps
我個人由於時間問題,目前還未接入該工具,下面就展示一下網友u012124438的測試資料好了:
後來我在使用 Redex 壓縮和優化 Android APK一文的幫助下,成功的安裝並使用了ReDex,但是發現了以下兩個問題:
- 用ReDex處理過的APK,其內部的META-INF目錄會被刪除,雖然可以將ReDex處理過的APK用AndResGuard處理一下,會重新出現該目錄。
- 用ReDex處理過的APK,安裝好之後會出現Crash的問題,目前達人店APK要Crash三次之後才能正常使用,而且它對於達人店APK的貢獻只有20~25K,所以並未打算接入。
resources.arsc
最後就是對於resources.arsc檔案的壓縮處理了,其實這裡我們也不需要做什麼,首先因為該檔案記錄的是資原始檔和資源ID的對映關係,並沒有什麼值得壓縮的地方;另外如果真的有值得壓縮的地方,也只有資原始檔的名字了,不過這個早就在之前因為我們使用了微信資源混淆解決了,所以此處就不再多講了。
至此,針對於APK目錄結構,逐條分析如何為APK瘦身已經講完了,接下來介紹一下其他的壓縮方式。
其他方式
使用APK Splits構建APK
雖然我們上面很好的使用了resource shrinker可以回收一些未使用的資源(v7、v4、google Service 等Libarry資源),但有些資源仍然未被清除。
例如:那些未使用的多套替代資源,或者是library內部隱患著引用著的資源而我們卻沒有使用到。或者是我們要根據使用者的手機去提供不同版本的APK,如解析度(xxhdpi,mhdpi等),so庫等。那麼我們可以使用APK Splits大大的減少一些無用的資源,這裡我們主要參考了tools.android.com/tech-docs/n…文件。
使用多版本的APK
Multiple APK Support是一個在Google Play,可以釋出不同的應用程式,分別針對不同的裝置配置特徵。每個APK是一個完整的、獨立的應用程式版本,但他們分享在Google Play相同的應用程式清單,必須共享相同的包名和與簽名。Google Play 會自動給你匹配相應的APK,這樣我們的APK 就可以是分不同版本構建需要資原始檔,從而減小APK的大小。
資源動態載入
我們可以在專案中使用資源動態載入形式,例如:表情,語言,離線庫等資源動態載入,減小APK的大小。
支援外掛化
未來對於一些獨立業務模組,可以做成外掛化動態載入,使用者需要使用時,只需下載少部分外掛。
使用shape背景
特別是在扁平化盛行的當下,很多純色的漸變的圓角的圖片都可以用shape實現,程式碼靈活可控,省去了大量的背景圖片。
使用著色方案
相信你的工程裡也有很多selector檔案,也有很多相似的圖片只是顏色不同,通過著色方案我們能大大減輕這樣的工作量,減少這樣的檔案。
藉助於android support庫可實現一個全版本相容的著色方案,參考程式碼:DrawableLess.java
其他方式...
總結
如果你不是一個Android開發人員,對於之前的闡述可能非常模糊,並不能明顯的表現出APK瘦身這件事情的意義,下面我就來總結一下:
首先,針對於達人店APK做了哪些應用?
字型檔案使用Glyphs進行壓縮,圖片使用tinypng進行壓縮
只是用一套so檔案
只使用一套圖
使用WebP圖片或SVG圖片替換某些PNG或JPG圖片
使用Google提供的Gradle外掛實現程式碼混淆和資源混淆
藉助NimbleDroid、AS Anylze/Lint工具分析查詢刪除不需要的程式碼或資源
使用微信提供的資源混淆工具(處於穩定性測試階段)
使用Facebook提供的ReDex工具(處於調研階段)
其次,APK瘦身的效果如何?
這裡我們就直接看圖好了:
可以明顯的發現,達人店APK越來越小了,所以說,這個工作是很有意義的。
最後,做一下競品分析?
根據上面圖表,可以發現我們的APK的大小是十分有優勢的,能夠很好的提高下載轉化率。而iOS那邊顯然就比較大了,雖然兩者之間不能比較,但是還是可以進行一系列優化的。
忠告
最後的最後,我想對大家說:在APK瘦身的道路上,一定要掌握好度
,安排好事情的優先順序,如果目前要做的事情、要優化的方面比較複雜,不僅需要花費很長的時間,而且最終效果也不明顯,可以考慮之後再做,甚至不做。