最近幾周一直在研究如何為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大大的減少一些無用的資源,這里我們主要參考了http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits文檔。
使用多版本的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瘦身的道路上,一定要掌握好度
,安排好事情的優先級,如果目前要做的事情、要優化的方面比較復雜,不僅需要花費很長的時間,而且最終效果也不明顯,可以考慮之后再做,甚至不做。