因爲文章特別長,建議先收藏再閱讀。java
在 Android 性能優化的知識體系當中,包體積優化一直被排在優先級比較低的位置,從而致使不少開發同窗對自身應用的大小並不重視。在項目發展的歷程中,通常可劃分爲以下三個階段:
初創期 => 成長期 => 成熟期
一般來講,當應用處於成長期的中後階段時,纔會考慮去作系統的包體積優化,所以,只有在這個階段及以後,包體積優化帶來的收益纔是可觀的。android
那麼,包體積優化可以給咱們帶來哪些 收益 呢?如何全面對應用的包體積進行 系統分析 及 針對性優化呢?在這篇文章中,咱們將一塊兒進行深刻地分析與探索。git
一、瘦身優化及 Apk 分析方案介紹
一、瘦身優點github
咱們首先來介紹下,爲何咱們須要作 APK 的瘦身優化?web
主要有 三個方面 的緣由:算法
(1)下載轉化率數據庫
APK 瘦身優化在實際的項目中優先級是比較低的,由於作了以後它的好處不是那麼明顯,尤爲是那些尚未到 穩按期 的項目,咱們都知道,App 的發展歷程是從 項目初期 => 成長期 => 穩按期,對於處於 發展初期與成長期 的項目而言,可能會作 啓動優化、卡頓優化,可是通常不會作 瘦身優化,瘦身優化 最主要的好處是對應用 下載轉化率 的影響,它是 App 業務運營的重要指標之一,在項目精細化運營的階段是很是重要的。xcode
由於若是你的 App 與其它同類型的 App 相比 Apk 體積要更小的話,那麼你的 App 下載率就可能要高一些。並且,包體積越小,用戶下載等待的時間也會越短,因此下載轉換成功率也就越高。因此,安裝包大小與下載轉化率的關係 大體是成反比 的,即安裝包越大,下載轉換率就越小。性能優化
一個 80MB 的應用,用戶即便點了下載,也可能由於網絡速度慢、忽然反悔致使下載失敗。而對於一個 20MB 的應用,用戶點了下載以後,在猶豫要不要下的時候可能就已經下載完了。服務器
2應用市場
Google Play 應用市場強制要求超過 100MB 的應用只能使用 APK 擴展文件方式 上傳。當使用 APK 擴展文件方式 上傳時,Google Play 會爲咱們的應用 託管 擴展文件,並將其 免費提供 給設備。
擴展文件將保存到設備的共享存儲位置(SD 卡或可安裝 USB 的分區;也稱爲「外部」存儲),應用能夠在其中訪問它們。在大多數設備上,Google Play 會在下載 APK 的同時下載擴展文件,所以應用在用戶首次打開時便擁有了所需的一切。
可是,在某些狀況下,咱們的應用必須在應用啓動時從 Google Play 下載文件。若是您想避免使用擴展文件,而且想要應用程序的下載大小大於100 MB,則應該使用 Android App Bundles 上傳應用程序,此時應用程序最多可提供150 MB的壓縮下載大小。
三、渠道合做商的要求
此外,還有一個緣由,當咱們的 App 作大以後,可能須要跟各個手機廠商合做預裝,這些 渠道合做商會對你的 App 作詳細的要求,只有達到相應的要求後才容許你的 App 預裝到手機上。並且,越大的 App 其單價成本也會越高。因此,瘦身也是咱們項目作大以後必定會遇到的一個問題。
1)安裝時間:好比 文件拷貝、Library 解壓,而且,在編譯 ODEX 的時候,特別是對於 Android 5.0 和 6.0 系統來講,耗費的時間比較久,而 Android 7.0 以後有了 混合編譯,因此還能夠接受。最後,App 變大後,其 簽名校驗 的時間也會變長。
2)運行時內存:Resource 資源、Library 以及 Dex 類加載都會佔用應用的一部份內存。
3)ROM 空間:若是應用的安裝包大小爲 50MB,那麼啓動解壓以後極可能就已經超過 100MB 了。而且,若是 閃存空間不足,極可能出現「寫入放大」的狀況,它是閃存和固態硬盤(SSD)中一種不良的現象,閃存在可從新寫入數據前必須先擦除,而擦除操做的粒度與寫入操做相比低得多,執行這些操做就會屢次移動(或改寫)用戶數據和元數據。
所以,要改寫數據,就須要讀取閃存某些已使用的部分,更新它們,並寫入到新的位置,若是新位置在以前已被使用過,還需連同先擦除;因爲閃存的這種工做方式,必須擦除改寫的閃存部分比新數據實際須要的大得多。即最終可能致使實際寫入的物理資料量是寫入資料量的多倍。
二、APK 組成
咱們都知道,Android 項目最終會編譯成一個 .apk 後綴的文件,實際上它就是一個 壓縮包。所以,它內部還有不少不一樣類型的文件,這些文件,按照大小,共分爲以下幾類:
1)代碼相關:classes.dex,咱們在項目中所編寫的 java 文件,通過編譯以後會生成一個 .class 文件,而這些全部的 .class 文件呢,它最終會通過 dx 工具編譯生成一個 classes.dex。
2)資源相關:res、assets、編譯後的二進制資源文件 resources.arsc 和 清單文件 等等。res 和 assets 的不一樣在於 res 目錄下的文件會在 .R 文件中生成對應的資源 ID,而 assets 不會自動生成對應的 ID,而是經過 AssetManager 類的接口來獲取。此外,每當在 res 文件夾下放一個文件時,aapt 就會自動生成對應 id 並保存在 .R 文件中,但 .R 文件僅僅只是保證編譯程序不會報錯,實際上在應用運行時,系統會根據 ID 尋找對應的資源路徑,而 resources.arsc 文件就是用來記錄這些 ID 和 資源文件位置對應關係 的文件。
3)So 相關:lib 目錄下的文件,這塊文件的優化空間其實很是大。
此外,還有 META-INF,它存放了應用的 簽名信息,其中主要有 3個文件,以下所示:
MANIFEST.MF:其中每個資源文件都有一個對應的 SHA-256-Digest(SHA1) 簽名,MANIFEST.MF 文件的 SHA256(SHA1) 通過 base64 編碼的結果即爲 CERT.SF 中的 SHA256(SHA1)-Digest-Manifest 值。
CERT.SF:除了開頭處定義的 SHA256(SHA1)-Digest-Manifest 值,後面幾項的值是對 MANIFEST.MF 文件中的每項再次 SHA256(SHA1) 通過 base64 編碼後的值。
CERT.RSA:其中包含了公鑰、加密算法等信息。首先,對前一步生成的MANIFEST.MF使用了SHA256(SHA1)-RSA算法,用開發者私鑰簽名。而後,在安裝時使用公鑰解密。最後,將其與未加密的摘要信息(MANIFEST.MF文件)進行對比,若是相符,則代表內容沒有被修改。
代碼瘦身方案探索
在講解如何對 Dex 進行優化以前,可能有不少同窗對 Dex 尚未足夠的瞭解,這裏咱們就先詳細地瞭解下 Dex。
一、Dex 探祕
1)Dex 是 Android 系統的可執行文件,包含 應用程序的所有操做指令以及運行時數據。由於 Dalvik 是一種針對嵌入式設備而特殊設計的 Java 虛擬機,因此 Dex 文件與標準的 Class 文件在結構設計上有着本質的區別。
2)當 Java 程序被編譯成 class 文件以後,還須要使用 dx 工具將全部的 class 文件整合到一個 dex 文件中,這樣 dex 文件就將原來每一個 class 文件中都有的共有信息合成了一體,這樣作的目的是 保證其中的每一個類都可以共享數據,這在必定程度上 下降了信息冗餘,同時也使得 文件結構更加緊湊。
與傳統 jar 文件相比,Dex 文件的大小可以縮減 50% 左右。關於 Class 文件與 Dex 文件的結果對比圖以下所示:
若是想深刻地瞭解 Dex 文件格式,能夠參見Google 官方教程 - Dex格式。
Dex 通常在應用包體積中佔據了很多比重,而且,Dex 數量越多,App 的安裝時間也會越長。因此,優化它們能夠說是 重中之重。下面,咱們就來看看有哪些方式能夠優化 Dex 這部分的體積。
二、ProGuard
混淆這裏就不贅述了,你們應該比較熟悉,原文有比較詳細的介紹。
三、D8 與 R8 優化
D8 優化
D8 的 優化效果 總的來講能夠歸結爲以下 四點:
Dex的編譯時間更短。
dex文件更小。
D8 編譯的 .dex 文件擁有更好的運行時性能。
包含 Java 8 語言支持的處理。
在 Android Studio 3.0須要主動在 gradle.properties 文件中新增:
android.enableD8 = true
Android Studio 3.1 或以後的版本 D8 將會被做爲默認的 Dex 編譯器。
R8 優化
R8 官方文檔(目前已經開源)
https://r8.googlesource.com/r8
R8 是 Proguard 壓縮與優化部分的替代品,而且它仍然使用與 Proguard 同樣的 keep 規則。若是咱們僅僅想在 Android Studio 中使用 R8,當咱們在 build.gradle 中打開混淆的時候,R8 就已經默認集成進 Android Gradle plugin 中了。
若是咱們當前使用的是 Android Studio 3.4 或 Android Gradle 插件 3.4.0 及其更高版本,R8 會做爲默認編譯器。不然,咱們 必需要在 gradle.properties 中配置以下代碼讓 App 的混淆去支持 R8,以下所示:
android.enableR8=true android.enableR8.libraries=true
那麼,R8 與混淆相比優點在哪裏呢?
ProGuard 和 R8 都應用了基本名稱混淆:它們 都使用簡短,無心義的名稱重命名類,字段和方法。他們還能夠 刪除調試屬性。
可是,R8 在 inline 內聯容器類中更有效,而且在刪除未使用的類,字段和方法上則更具侵略性。例如,R8 自己集成在 ProGuard V6.1.1 版本中,在壓縮 apk 的大小方面,與 ProGuard 的 8.5% 相比,使用 R8 apk 尺寸減少了約 10%。而且,隨着 Kotlin 如今成爲 Android 的第一語言,R8 進行了 ProGuard 還沒有提供的一些 Kotlin 的特定的優化。
1)、ProGuard 在將枚舉類型簡化爲原始整數方面會更增強大。它還傳遞常量方法參數,這一般對於使用應用程序的特定設置調用的通用庫頗有用。
ProGuard 的屢次優化遍歷一般能夠產生一系列優化。例如,第一遍能夠傳遞一個常量方法參數,以便下一遍能夠刪除該參數並進一步傳遞該值。刪除日誌代碼時,屢次傳遞的效果尤爲明顯。ProGuard 在刪除全部跟蹤(包括組成日誌消息的字符串操做)方面更有效。
2)、ProGuard 中應用的模式匹配算法能夠識別和替換短指令序列,從而提升代碼效率併爲更多優化打開了機會。在優化遍歷的順序中,尤爲是數學運算和字符串運算可從中受益。
三、最後,ProGuard 具備獨特的能力來優化使用 GSON 庫將對象序列化或反序列化爲 JSON 的代碼。該庫嚴重依賴反射,這很方便,但效率低下。而 ProGuard 的優化功能能夠 經過更高效,直接的訪問方式 來代替它。
R8 優化實戰
接下來,咱們就來看看 Awesome-WanAndroid 使用 R8 後,APK 體積的變化,以下圖所示:
能夠看到,相較於僅使用混淆後的 APK 而言,大小減小了 0.1MB,Dex 部分的優化效果大概爲 5%,APK 總體的壓縮效果也有 1.5% 左右。
雖然從減小的 APK 大小來看,0.1MB 不多,可是比例並不小,若是你負責的是一個像微信、淘寶等規模的 App,它們的體積通常都將近 100MB,使用 R8 後也能減少 1.5MB 的大小。
四、去除 debug 信息與行號信息
在講解什麼是 deubg 信息與行號信息以前,咱們須要先了解 Dex 的一些知識。
咱們都知道,JVM 運行時加載的是 .class 文件,而 Android 爲了使包大小更加緊湊、運行時更加高效就發明了 Dalvik 和 ART 虛擬機,兩種虛擬機運行的都是 .dex 文件,固然 ART 虛擬機還能夠同時運行 oat 文件。
因此 Dex 文件裏的信息內容和 Class 文件包含的信息是同樣的,不一樣的是 Dex 文件對 Class 中的信息作了去重,一個 Dex 包含了不少的 Class 文件,而且在結構上有比較大的差別,Class 是流式的結構,Dex 是分區結構,Dex 內部的各個區塊間經過 offset 來進行索引。
爲了在應用出現問題時,咱們能在調試的時候去顯示相應的調試信息或者上報 crash 或者主動獲取調用堆棧的時候能經過 debugItem 來獲取對應的行號,咱們都會在混淆配置中加上下面的規則:
-keepattributes SourceFile, LineNumberTable
這樣就會保留 Dex 中的 debug 與行號信息,此時的 Dex 結構圖 以下所示:
大牛耗時一年:深刻探索 Android 包體積優化,共三萬字建議收藏上
從圖中能夠看到,Dex 文件的結構主要分爲 四大塊:header 區,索引區,data 區,map 區。而咱們的 debug 與行號信息就保存在 data 區中的 debugItems 區域。
而 debug_items 裏面主要包含了 兩種信息,以下所示:
調試的信息:包含函數的參數和全部的局部變量。
排查問題的信息:包含全部的指令集行號與源文件行號的對應關係。
根據 Google 官方的數據,debugItem 通常佔 Dex 的比例有 5% 左右,若是咱們能去除 debug 與行號信息,就能更進一步對 Dex 進行瘦身,可是會失去調試信息的功能,那麼,有什麼方式能夠去掉 debugItem,同時又能讓 crash 上報的時候能拿到正確的行號呢?
咱們能夠嘗試直接修改 Dex 文件,保留一小塊 debugItem,讓系統查找行號的時候指令集行號和源文件行號保持一致,這樣任何監控上報的行號都直接變成了指令集行號。
每個方法都會有一個 debugInfoItem,每個 debuginfoItem 裏面都有一個指令集行號和源文件行號的映射關係,這了咱們直接把多餘的 debugInfoItem 所有刪掉,只保留了一個 debugInfoItem,這樣全部的方法都會指向同一個 debugInfoItem,而且這個 debugInfoItem 中的指令集行號和源文件行號保持一致,這樣無論用什麼方式來查找行號,拿到的都是指令集行號。
須要注意的是,採用這種方案 須要兼容全部虛擬機的查找方式,所以 僅僅保留一個 debugInfoItem 是不夠的,須要對 debugInfoItem 進行分區,而且 debugInfoItem 表不能太大。
關於如何去除 Dex 中的 Debug 信息是經過 ReDex 的 StripDebugInfoPass 來完成的,其配置以下所示:
{ "redex" : { "passes" : [ "StripDebugInfoPass", "RegAllocPass" ] }, "StripDebugInfoPass" : { "drop_all_dbg_info" : false, "drop_local_variables" : true, "drop_line_numbers" : false, "drop_src_files" : false, "use_whitelist" : false, "cls_whitelist" : [], "method_whitelist" : [], "drop_prologue_end" : true, "drop_epilogue_begin" : true, "drop_all_dbg_info_if_empty" : true }, "RegAllocPass" : { "live_range_splitting": false } }
關於 debuginfo 的實戰咱們下面立刻會開始,在此以前,咱們先講講 Dex 分包中的另外一個優化點。
五、Dex 分包優化
Dex 分包優化原理
當咱們的 APK 過大時,Dex 的方法數就會超過65536個,所以,必須採用 mutildex 進行分包,可是此時每個 Dex 可能會調用到其它 Dex 中的方法,這種 跨 Dex 調用的方式會形成許多冗餘信息,具體有以下兩點:
多餘的 method id:跨 Dex 調用會致使當前dex保留被調用dex中的方法id,這種冗餘會致使每個dex中能夠存放的class變少,最終又會致使編譯出來的dex數量增多,而dex數據的增長又會進一步加劇這個問題。
其它跨dex調用形成的信息冗餘:除了須要多記錄被調用的method id以外,還需多記錄其所屬類和當前方法的定義信息,這會形成 string_ids、type_ids、proto_ids 這幾部分信息的冗餘。
爲了減小跨 Dex 調用的狀況,咱們必須 儘可能將有調用關係的類和方法分配到同一個 Dex 中。可是各個類相互之間的調用關係是很是複雜的,因此很難作到最優的狀況。
所幸的是,ReDex 的 CrossDexDefMinimizer 類分析了類之間的調用關係,並 使用了貪心算法去計算局部的最優解(編譯效果和dex優化效果之間的某一個平衡點)。
https://github.com/facebook/redex/blob/master/opt/interdex/CrossDexRefMinimizer.cpp
使用 "InterDexPass" 配置項能夠把互相引用的類儘可能放在同個 Dex,增長類的 pre-verify,以此提高應用的冷啓動速度。
在 ReDex 中使用 Dex 分包優化跨 dex 調用形成的信息冗餘的配置代碼以下所示:
{ "redex" : { "passes" : [ "InterDexPass", "RegAllocPass" ] }, "InterDexPass" : { "minimize_cross_dex_refs": true, "minimize_cross_dex_refs_method_ref_weight": 100, "minimize_cross_dex_refs_field_ref_weight": 90, "minimize_cross_dex_refs_type_ref_weight": 100, "minimize_cross_dex_refs_string_ref_weight": 90 }, "RegAllocPass" : { "live_range_splitting": false }, "string_sort_mode" : "class_order", "bytecode_sort_mode" : "class_order" }
爲了衡量優化效果,咱們可使用 Dex 信息有效率 這個指標,公式以下所示:
git clone https://github.com/facebook/redex.git cd redex
若是 Dex 有效率在 80% 以上,就說明基本合格了。
使用 ReDex 進行分包優化、去除 debug 信息及行號信息
下面,咱們就使用 Redex 來對上一步生成的 app-release-proguardwithr8.apk 進行進一步的優化。(macOS 環境下)
https://fbredex.com/docs/installation
一、首先,咱們須要輸入一下命令去去安裝 Xcode 命令行工具
xcode-select --install
二、而後,使用 homebrew 安裝 redex 項目使用到的依賴庫
ANDROID_SDK=/Users/quchao/Library/Android/sdk redex --sign -s wan-android-key.jks -a wanandroid -p wanandroid -c ~/Desktop/interdex_stripdebuginfo.config -P app/proguard-rules.pro -o ~/Desktop/app-release-proguardwithr8-stripdebuginfo-interdex.apk ~/Desktop/app-release-proguardwithr8.apk
須要注意的是嗎,2020年2月10號版本源碼的 redex 須要的 boost 版本爲 V1.71 及以上,當你使用 brew install boost 安裝 boost 時可能獲取到的 boost 版本會低於 V1.71,此時多是 brew 版本須要更新,使用 brew upgrade 去更新 brew 倉庫的版本 或者能夠直接從 boost 官網下載最新的 boost 源碼 至 /usr/local/Cellar/ 目錄下,我當前使用的是 boost V1.7.2源碼下載地址 中的 boost_1_72_0.zip。
https://dl.bintray.com/boostorg/release/1.72.0/source/
從 深刻探索 Android 啓動優化 時就說起到了 Redex 的類重排優化,當時卡在這一步,因此一直無法真正完成類的重排優化。
三、接着,從 Github 上獲取 ReDex 的源碼並切換到 redex 目錄下
git clone https://github.com/facebook/redex.git
cd redex
四、下一步,使用 autoconf 和 make 去構建 ReDex
autoreconf -ivf && ./configure && make -j4
sudo make install
五、而後,配置 Redex 的 config 代碼
在 Redex 在運行的時候,它是根據 redex/config/default.config 這個配置文件中的通道 passes 中添加不一樣的優化項來對 APK 的 Dex 進行處理的,咱們能夠參考 redex/config/default.config 這個默認的配置,裏面的 passes 中不一樣的配置項都有特定的優化。
爲了優化 App 的包體積,咱們再加上 interdex_stripdebuginfo.config 中的配置項去刪除 debugInfo 和減小跨 Dex 調用的狀況,最終的 interdex_stripdebuginfo.config 配置代碼 以下所示:
{ "redex" : { "passes" : [ "StripDebugInfoPass", "InterDexPass", "RegAllocPass" ] }, "StripDebugInfoPass" : { "drop_all_dbg_info" : false, "drop_local_variables" : true, "drop_line_numbers" : false, "drop_src_files" : false, "use_whitelist" : false, "cls_whitelist" : [], "method_whitelist" : [], "drop_prologue_end" : true, "drop_epilogue_begin" : true, "drop_all_dbg_info_if_empty" : true }, "InterDexPass" : { "minimize_cross_dex_refs": true, "minimize_cross_dex_refs_method_ref_weight": 100, "minimize_cross_dex_refs_field_ref_weight": 90, "minimize_cross_dex_refs_type_ref_weight": 100, "minimize_cross_dex_refs_string_ref_weight": 90 }, "RegAllocPass" : { "live_range_splitting": false }, "string_sort_mode" : "class_order", "bytecode_sort_mode" : "class_order" }
六、最後,執行相應的 redex 優化命令
這裏咱們使用 Redex 命令對上一 Dex 優化中獲得的 app_release-proguardwithr8.apk 進行 Dex 分包優化和去除 debugInfo,它使用了貪心這種局部最優解的方式去減小跨 Dex 調用形成的信息冗餘,命令以下所示(注意,在 redex 的前面可能須要加上 Android sdk 的路徑,由於 redex 中使用到了sdk下的zipalign工具):
ANDROID_SDK=/Users/quchao/Library/Android/sdk redex --sign -s wan-android-key.jks -a wanandroid -p wanandroid -c ~/Desktop/interdex_stripdebuginfo.config -P app/proguard-rules.pro -o ~/Desktop/app-release-proguardwithr8-stripdebuginfo-interdex.apk ~/Desktop/app-release-proguardwithr8.apk
上述 redex 命令的 關鍵參數含義 以下所示:
--sign:對生成的apk進行簽名。
-s:配置應用的簽名文件。
-a: 配置應用簽名的 key_alias。
-p:配置應用簽名的 key_password。
-c:指定 redex 進行 Dex 處理時須要依據的 CONFIG 配置文件。
-o:指定生成 APK 的全路徑。
使用上面的 redex 命令咱們就能夠對優化後的 APK 進行 再簽名和混淆,等待一會後(若是你的 APK 的 Dex 數量和體積很大,可能會比較久),就會生成 優化後的 APK:app-release-proguardwithr8-stripdebuginfo-interdex.apk,以下圖所示:
大牛耗時一年:深刻探索 Android 包體積優化,共三萬字建議收藏上
能夠看到,咱們的 APK 大小几乎沒有變化,這是由於當前的 APK 只有一個 Dex,而且 第一個 Dex 默認不會優化。爲了能實際看到 redex 的優化效果,咱們採用一個新項目來進行實驗,項目地址以下所示:
redex 優化 Apk 項目地址
https://github.com/AndroidAdvanceWithGeektime/Chapter22
首先,引入一大堆開源庫,嘗試把 Dex 數量變多一些。而後直接經過 assembleDebug 編譯便可。此外,爲了能夠更加清楚流程,咱們能夠在 命令行輸入 export TRACE=2 以即可以輸出 redex 的日誌。最後,咱們輸入下面的 redex 命令刪除 dex 中的 debugInfo 和減小跨 dex 調用的狀況,以下所示:
redex --sign -s ReDexSample/keystore/debug.keystore -a androiddebugkey -p android -c redex-test/interdex_stripdebuginfo.config -P ReDexSample/proguard-rules.pro -o redex-test/strip_output.apk ReDexSample/build/outputs/apk/debug/ReDexSample-debug.apk
最終,咱們看到先後的 APK 體積對比圖以下所示:
能夠看到,APK 的大小從 14.2MB 減小到了 12.8MB,優化效果大概有10%,效果仍是比較明顯的。此外,若是你的 App 的 Dex 數量越多,那麼優化的效果就會越大。
六、三方庫處理
實際的開發過程當中,咱們會用到各類各樣的三方庫。尤爲當項目變大以後,開發人員衆多,所以引入的三方庫也會很是多,好比說,有人引入了一個 Fresco 圖片庫,而後這個庫你可能不熟悉,你會引入一個 Glide,而且另外一我的它可能又會引入他熟悉的圖片庫 Picasso,因此項目中可能會存在多個相同功能的三方 SDK,這一點,在大型項目當中必定會存在。
所以,咱們在作代碼瘦身的時候,須要將三方庫進行統一,好比說 將圖片加載庫、網絡庫、數據庫以及其餘基礎庫進行統一,去掉冗餘的庫。
同時,在選擇第三方 SDK 的時候,咱們能夠將包大小做爲選擇的指標之一,咱們應該 儘量地選擇那些比較小的庫來實現相同的功能。
例如,對於圖片加載功能來講,Picasso、Glide、Fresco 它們均可以實現,可是你引入 Fresco 以後會致使包大小增長不少,而 Picasso 卻只增長了不到 100kb,因此引入不一樣的三方 SDK 對包大小的影響是不同的。
這裏,咱們可使用 AS 插件 Android Methods Count,安裝以後,它會自動在 build.gradle 文件中顯示你引入的三方庫的方法數。
最後,若是咱們引入三方庫的時候,能夠 只引入部分須要的代碼,而不是將整個包的代碼都引入進來。
若是你引入的三方庫 沒有進行過結構剝離,就須要 修改源碼,只提取出來你須要的功能便可。
七、移除無用代碼
移除無用代碼時咱們常常會碰到下面兩個問題:
業務代碼只增不減。
代碼太多不敢刪除。
這裏,有一個很好的方法能夠 準確地判斷哪些類在線上環境下用戶確定不會用到了。咱們能夠經過 AOP 的方式來作,對於 Activity 來講,其實很是簡單,咱們只須要 在每一個 Activity 的 onCreate 當中加上統計 便可,而後到了線上以後,若是這個 Activity 被統計了,就說明它還在被使用。
而對於那些 不是 Activity 的類,咱們能夠 利用 AOP 來切它們的構造函數,一個類若是它被使用,那它的構造函數確定會被調用到。例如,下面就是 使用 AspectJ 對某個包下的類進行構造函數切面 的代碼:
@After("execution(org.jay.launchstarter.Task.new(..)") public void newObject(JoinPoint point) { LogHelper.i(" new " + point.getTarget().getClass().getSimpleName()); }
其中,new 表示是 切的構造函數,括號中的 .. 表示的是 匹配全部構造參數。此外,咱們也能夠直接使用 coverage 插件 來作 線上無用代碼分析,須要注意的是,在註冊上報數據的時候記得把服務器名改成本身的。
https://github.com/bytedance/ByteX/blob/master/coverage/README-zh.md
最後,咱們也能夠在線下使用 Simian工具 來 掃描出重複的代碼。
https://blog.csdn.net/Love667767/article/details/53558382
八、避免產生 Java access 方法
access 方法是什麼?
爲了能提供內部類和其外部類直接訪問對方的私有成員的能力,又不違反封裝性要求,Java 編譯器在編譯過程當中自動生成 package 可見性的靜態 access$xxx 方法,而且在須要訪問對方私有成員的地方改成調用對應的 access 方法。
主要有 兩種方式 避免產生 access 方法:
在開發過程當中須要注意在可能產生 access 方法的狀況下適當調整,好比去掉 private,改成 package 可見性。
使用 ASM 在編譯時刪除生成的 access 方法。
由於優化效果不是很明顯,這裏就很少介紹了,具體的實現細節可參見 西瓜視頻 apk 瘦身之 Java access 方法刪除,此外,在 ReDex 中也提供了 access-marking 這個功能去除代碼中的 Access 方法,而且,在 ReDex 還有 type-erasure 的功能,它 與 access-marking 的優化效果同樣,不只能減小包大小,也能提高 App 的啓動速度。
九、利用 ByteX Gradle 插件平臺中的代碼優化插件
若是你想在項目的編譯階段去除 access 方法,這裏我更加建議直接使用 ByteX 的 access_inline 插件。除了 access_inlie 以外,在 ByteX 中還有 四個 很實用的代碼優化 Gradle 插件能夠幫助咱們有效減少 Dex 文件的大小,以下所示:
一、編譯期間 內聯常量字段:const_inline。
二、編譯期間 移除多餘賦值代碼:field_assign_opt。
三、編譯期間 移除 Log 代碼:method_call_opt。
四、編譯期間 內聯 Get / Set 方法:getter-setter-inline-plugin。
https://github.com/bytedance/ByteX
資源瘦身方案探索
其中 Proguard Configuration、Compiled Resources 的 做用 以下所示:
Proguard Configuration:這是AAPT工具爲Manifest中聲明的四大組件與佈局文件中使用的各類Views所生成的混淆配置,該文件一般存放在 ${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/proguard-rules/${flavorName}/${buildType}/aapt_rules.txt。
Compiled Resources:它是一個Zip格式的文件,這個文件的路徑一般爲 ${project.buildDir}/${AndroidProject.FDINTERMEDIATES}/res/resources-${flavorName}-${buildType}-stripped.ap。在通過 zip 解壓以後,能夠發現它 包含了res、AndroidManifest.xml和resources.arsc 這三部分。而且,從上面的 APK 構建流程中能夠得知,Compiled Resources 會被 apkbuilder 打包到 APK 包中,它其實就是 APK 的資源包。
所以,咱們能夠 經過 Compiled Resources 文件來修改不一樣後綴文件資源的壓縮方式來達到瘦身效果的。
可是須要注意的是,resources.arsc 文件最好不要壓縮存儲,若是壓縮會影響必定的性能,尤爲是在冷啓動時間方面形成的影響。而且,若是在 Android 6.0 上開啓了 android:extractNativeLibs=」false」 的話,So 文件也不能被壓縮。
文章不易,若是你們喜歡這篇文章,或者對你有幫助但願你們多多點贊轉發關注哦。文章會持續更新的。絕對乾貨!!!