android的壓縮包能刪嗎
此讀書筆記完全基於公開的Google官方文件
壓縮程式碼
如果設定minifyEnabled true, 那麼會啟用R8程式碼壓縮,搖樹最佳化。
android {
。。。
buildTypes {
release {
minifyEnabled true
}
}
}
我覺得類似於JVM的垃圾回收,使用引用樹檢查來確定哪些物件需要清理。
R8構建一張圖來確定執行不到的程式碼
未被引用的就會被移除,以節省資源,提高程式效率。
看到這裡,我立刻就想到,透過引用檢查來移除無用類/方法/成員變數。那麼只通過反射呼叫的類、方法、變數豈不是會出問題?
帶著問題我接著往下看:
自定義要保留的程式碼
在某些情況下,R8 很難做出正確分析,因此可能會移除您的應用實際上需要的程式碼。下面列舉了幾個例子,說明了它在什麼情況下可能會錯誤地移除程式碼:
當您的應用透過 Java 原生介面 (JNI) 呼叫方法時
當您的應用在執行時查詢程式碼時(如使用反射)
果然,設計者肯定知道這個問題,所以留下了解決辦法:
要修復錯誤並強制 R8 保留某些程式碼,請在 ProGuard 規則檔案中新增
-keep
程式碼行。例如:
-keep public class MyClass
壓縮資源
android {
。。。
buildTypes {
release {
shrinkResources true
minifyEnabled true
proguardFiles getDefaultProguardFile(‘proguard-android。txt’),
‘proguard-rules。pro’
}
}
}
如上所示,使用shrinkResources true 來啟用資源壓縮。
而與壓縮程式碼同理,我們會有可能使用Resources。getIdentifier()來訪問資源,那麼這些執行時動態使用的資源該怎麼辦?
R8會預設採取比較安全的防禦策略,將所有具有匹配名稱格式的資源標記為可能已使用,不移除。
例如:
String name = String。format(“img_%1d”, angle + 1);
res = getResources()。getIdentifier(name, “drawable”, getPackageName());
以上程式碼會使系統將所有帶img_字首的資源標記為已使用並保留下來。
PS:資源壓縮器還會瀏覽程式碼以及各種
res/raw/
資源中的所有字串常量,查詢格式類似於
file:///android_res/drawable//ic_plus_anim_016。png
的資源網址。如果它找到這樣的字串,或發現一些其他字串看似可用來構建這樣的網址,就不會將它們移除。
那對於APK體積極其敏感的人表示,想讓資源壓縮和程式碼壓縮一樣,預設刪除無靜態引用的資源,手動保留會動態引用的資源該怎麼辦呢?
答案是啟用嚴格引用檢查,具體做法是在keep。xml檔案中將shrinkMode設為strict,如下所示:
<?xml version=“1。0” encoding=“utf-8”?>
tools:shrinkMode=“strict” /> 同時,需要使用tools:keep屬性來手動保留想保留的資源: <?xml version=“1。0” encoding=“utf-8”?> tools:keep=“@layout/l_used*_c,@layout/l_used_a,@layout/l_used_b*” tools:discard=“@layout/unused2” /> 其中 tools:keep 屬性中指定要保留的每個資源,在 tools:discard 屬性中指定要捨棄的每個資源。 解碼混淆過的堆疊軌跡 在使用騰訊bugly的時候,我沒弄懂它怎麼解碼被混淆過的程式碼、識別出Exception的正確行數和正確方法名,又為啥要這麼做。 首先我們要知道,程式碼混淆就是類似於短網址邏輯,使用較短的類名、方法名、變數名來全域性替換所有的。這樣的一個好處是可以顯著縮小程式碼體積,另一個好處是即使class檔案被反編譯,混淆過的無意義的程式碼也難以看懂,可以保護程式碼。 我們注意到,在此過程中,方法名類名都會變,所以反射呼叫肯定會完蛋。另一個是行號也會變化了,縮短程式碼後,方法、表示式所在行可能會有變動(參考程式碼最佳化),所以Exception的日誌會完蛋,不僅類名方法名一臉懵逼,連行號也找不到了。 所以生產環境的bug,產生的日誌要怎麼看?文章中有了解答: R8 每次執行時都會建立一個 mapping。txt 檔案,其中列出了混淆過的類、方法和欄位名稱與原始名稱的對映關係。此對映檔案還包含用於將行號映射回原始原始檔行號的資訊。R8 將此檔案儲存在 /build/outputs/mapping/ 目錄中。 原來如此,這個mapping。txt就相當於密碼本了,bugly讓每個版本都上傳一份對映檔案,原來如此。 顯然隨著每個版本程式碼迭代,mapping檔案肯定會變化,所以一個apk對應一個mapping檔案是自然的。 文章也提示了我們: 注意 :您每次編譯專案時都會覆蓋 R8 生成的 mapping。txt 檔案,因此您每次釋出新版本時都必須小心地儲存一個副本。透過為每個釋出版本保留一個 mapping。txt 檔案副本,如果使用者提交了來自舊版應用的混淆過的堆疊軌跡,您將能夠除錯相關問題。 仍然存在的疑問 梯子掛了,所以訪問很多原文不便。 我繼續讀文章的過程中注意到,上面說的關於 “混淆過的程式碼透過反射呼叫肯定會完蛋”是武斷的。文章下面提到: 預設情況下,R8 假設您打算在執行時檢查和操縱該類的物件(即使您的程式碼實際上並不這樣做),因此它會自動保留該類及其靜態初始化程式。 通俗地說,就是如果開發者透過反射呼叫了class A,即使A沒有被其他任何地方使用到,那麼R8會保留A和A的靜態初始化方法,保證你還能正常反射呼叫。 而我們可以透過在專案的 gradle。properties 檔案中新增 android。enableR8。fullMode=true 來啟用更積極的最佳化,也就是預設不保留反射呼叫的類A的。 我的疑惑就是,R8如何分析得知我反射呼叫了class A呢,大機率還是類似於上面的防禦性地保護資原始檔,對編譯時確定或者半確定的class進行積極的保留。對於執行時地動態獲取需要反射呼叫的類名時,束手無策。