九分喜歡,一分尊嚴,放棄你,也放過本身,願你安好,在多年之後不要記起深愛你的我。javascript
工做不長不短,以前不曾考慮過深處,只是停留寫出來了,即是完美。html
而今的處境,不尷不尬,歲月恰好,背起行囊,繼續前行。java
現在的 5 G 也在萬衆矚目矚目下翩翩起舞,而 Android 近些年也惹得很多爭議,所謂的謠言,不過爾爾。android
每一個人的追求不同,盡本身最大努力吧。git
如何減小 Apk 大小,一直以來都是處於觀望狀態,懶得折騰,其實仍是不會,Low 的一批。github
Today,一塊兒來搞一波~web
歡迎各位指正~算法
現學現賣~api
首先附上一張如今 Apk 大小圖:android-studio
未作任何處理原包大小爲 10 MB,加固以後將近 11 MB。
以此爲例,一塊兒看看通過咱們玲瓏寶塔升級完,最終還剩下多少精華?
來到第一層,咱們先來簡單分析下是什麼形成 Apk 包如此「龐大」?
上圖可看到 lib 下兼容了全面的 CPU 架構,試想一下,假設將來的將來多了短視頻、直播、地圖導航等等(不接受槓精),這塊的大小會不會成倍數的增加。
上圖可看到默認支持了 89 種語言類型,目前的應用暫時未國際化,這塊也可直接設置兼容中文便可,原諒我這個強迫症。
佔比排行榜依次爲:源代碼、資源文件、lib。
咱們先挑個軟柿子玩玩。
關於這塊,我的以爲雖然佔比較小,可是用啥玩啥,用不到的直接幹掉。
在 build.gradle 中設置僅支持中文:
defaultConfig {
...
// 僅支持 中文
resConfigs "zh"
}
複製代碼
這塊主要是根據現有項目需求來定,中心思想只有一個,兼容哪兒個就設置哪兒個國家語言,其餘的直接忽略。
設置完以後打個包,看下有沒有什麼變化。
從上圖中能夠很清晰的看到,通過設置僅支持的國家語言後,包大小減小了 0.2 MB。隨後咱們看下資源映射文件中關於 string 中會有什麼變化。
默認語言中設置爲中文,且應用也只支持了中文,少了好多東西,爽得很~
話說這裏的 lib 爲什麼兼容了這麼多的 CPU 架構類型???
正好走到這裏,關於這塊的小知識再次重溫下,瞅瞅 Google 爲咱們提供的解釋:
不一樣的 Android 手機使用不一樣的 CPU,而不一樣的 CPU 支持不一樣的指令集。CPU 與指令集的每種組合都有專屬的應用二進制接口,即 ABI。ABI 能夠很是精確地定義應用的機器代碼在運行時如何與系統交互。您必須爲應用要使用的每一個 CPU 架構指定 ABI。
貌似 Google 商店如今支持對應的架構模式分發對應的 Apk 包,這點爽的每一個包只須要兼容一種就行了。But,ummm。
目前而言,項目中使用到真正用到 So 庫沒幾個,所有兼容太過於浪費,聽說 arm 屬於通用,那麼這裏同語言設置同樣,僅支持 arm 便可。
defaultConfig {
...
ndk {
// 設置支持的SO庫架構
abiFilters "armeabi"
}
}
複製代碼
打包運行後,繼續查看如今包大小:
這塊一直屬於個心病,以前的項目光是 So 庫就佔了很大一部分空間,很溼蛋疼。
根據 Google 官網解釋,當咱們使用 Android Gradle 3.4.0 或者更高版本時,默認會啓用 R8 編譯器進行壓縮、混淆以及優化,主要項以及做用以下:
這裏須要注意一下:
關於混淆文件,這裏須要正好學習一下。
混淆的意義在於什麼?(引入官方解釋)
混淆效果(摘自官方):
androidx.appcompat.app.ActionBarDrawerToggle$DelegateProvider -> a.a.a.b:
androidx.appcompat.app.AlertController -> androidx.appcompat.app.AlertController:
android.content.Context mContext -> a
int mListItemLayout -> O
int mViewSpacingRight -> l
android.widget.Button mButtonNeutral -> w
int mMultiChoiceItemLayout -> M
boolean mShowTitle -> P
int mViewSpacingLeft -> j
int mButtonPanelSideLayout -> K
複製代碼
混淆需注意:
隨後列舉經常使用混淆規則(語法):
具體規則可文末查看官方手冊。
接下來跟着官網一塊兒實踐一波~
buildTypes {
release {
// 打開資源壓縮
shrinkResources true
// 開啓混淆操做
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.config
}
debug {
// 關閉資源壓縮以及混淆操做
shrinkResources false
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.config
}
}
複製代碼
這裏須要注意,在 Debug 模式下須要關閉資源壓縮以及混淆操做,不然會增長編譯時間,通常在發佈正式包時打開便可。
這裏附上如今項目使用的混淆文件,基於大芬兒提供混淆文件作了部分修改:
#############################################
#
# 混淆基本指令
#
#############################################
# 代碼混淆壓縮比,在0~7之間,默認爲5,通常不作修改
-optimizationpasses 5
# 混合時不使用大小寫混合,混合後的類名爲小寫
-dontusemixedcaseclassnames
# 指定不去忽略非公共庫的類
-dontskipnonpubliclibraryclasses
# 這句話可以使咱們的項目混淆後產生映射文件
# 包含有類名->混淆後類名的映射關係
-verbose
# 指定不去忽略非公共庫的類成員
-dontskipnonpubliclibraryclassmembers
# 不作預校驗,preverify是proguard的四個步驟之一,Android不須要preverify,去掉這一步可以加快混淆速度。
-dontpreverify
# 保留Annotation不混淆
-keepattributes *Annotation*,InnerClasses
# 避免混淆泛型
-keepattributes Signature
# 拋出異常時保留代碼行號
-keepattributes SourceFile,LineNumberTable
# 指定混淆是採用的算法,後面的參數是一個過濾器
# 這個過濾器是谷歌推薦的算法,通常不作更改
-optimizations !code/simplification/cast,!field/*,!class/merging/*
# 忽略警告
-ignorewarnings
#############################################
#
# 須要保留的公共部分
#
#############################################
# 保留咱們使用的四大組件,自定義的 Application 等等這些類不被混淆
# 由於這些子類都有可能被外部調用
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Appliction
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class * extends android.view.View
-keep public class com.android.vending.licensing.ILicensingService
# 保留support下的全部類及其內部類
-keep class android.support.** {*;}
# 保留繼承的
-keep public class * extends android.support.v4.**
-keep public class * extends android.support.v7.**
-keep public class * extends android.support.annotation.**
# 保留R下面的資源
-keep class **.R$* {*;}
# 保留本地native方法不被混淆
-keepclasseswithmembernames class * {
native <methods>;
}
# 保留在Activity中的方法參數是view的方法,保障layout中寫的onClick不會被影響
-keepclassmembers class * extends android.app.Activity{
public void *(android.view.View);
}
# 保留枚舉類不被混淆
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
# 保留咱們自定義控件(繼承自View)不被混淆
-keep public class * extends android.view.View{
*** get*();
void set*(***);
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
}
# 保留Parcelable序列化類不被混淆
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
# 保留Serializable序列化的類不被混淆
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
!static !transient <fields>;
!private <fields>;
!private <methods>;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
# 對於帶有回調函數的onXXEvent、**On*Listener的,不能被混淆
-keepclassmembers class * {
void *(**On*Event);
void *(**On*Listener);
}
# webView處理,項目中沒有使用到webView忽略便可
-keepclassmembers class fqcn.of.javascript.interface.for.webview {
public *;
}
-keepclassmembers class * extends android.webkit.webViewClient {
public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap);
public boolean *(android.webkit.WebView, java.lang.String);
}
-keepclassmembers class * extends android.webkit.webViewClient {
public void *(android.webkit.webView, jav.lang.String);
}
# 移除Log類打印各個等級日誌的代碼,打正式包的時候能夠作爲禁log使用,這裏能夠做爲禁止log打印的功能使用
# 記得proguard-android.txt中必定不要加-dontoptimize才起做用
# 另外的一種實現方案是經過BuildConfig.DEBUG的變量來控制
-assumenosideeffects class android.util.Log {
public static int v(...);
public static int i(...);
public static int w(...);
public static int d(...);
public static int e(...);
}
#############################################
#
# 處理項目中咱們的部分
#
#############################################
#-----------處理實體類---------------
# 在開發的時候咱們能夠將全部的實體類放在一個包內,這樣咱們寫一次混淆就好了
-keep public class 實體類.**{*;}
# Js 交互
-keepclassmembers class JS 交互類地址{
public *;
}
-keepattributes *JavascriptInterface*
#############################################
#
# 處理第三方依賴庫部分
#
#############################################
# 此處按照實際項目中使用去官方查找對應的混淆代碼塊
複製代碼
隨後咱們繼續打包,查看混淆、資源壓縮後 Apk 大小以及部分變化:
dex 從 3 個下降到 2 個。未 Keep 的文件均已混淆,而 Keep 的文件依舊傲嬌挺立,以下圖:
混淆操做,在必定程度增大了破解的難度。固然,也沒有絕對的安全。
R8 每次運行時都會建立一個 mapping.txt 文件,其中列出了混淆過的類、方法和字段名稱與原始名稱的映射關係。此映射文件還包含用於將行號映射回原始源文件行號的信息。R8 將此文件保存在 /build/outputs/mapping// 目錄中。
線上版本確定要進行混淆,那麼針對線上版本報出的異常,咱們又該如何處理呢?畢竟關鍵內容都變成無心義字符,鑑名其意不存在了。
iTerm 2 打開:
點擊 ReTrace:
這塊步驟以下:
這塊我看的很溼懵逼,估計惟有雞大行雲流水了。簡單摘自官方解釋:
zipalign 是一種歸檔對齊工具,可對 Android 應用文件進行重要的優化。其目的是要確保全部未壓縮數據的開頭均相對於文件開頭部分執行特定的對齊。具體來講,它會使 APK 中的全部未壓縮數據(例如圖片或原始文件)在 4 字節邊界上對齊。這樣一來,便可使用 mmap() 直接訪問全部部分,即便其中包含具備對齊限制的二進制數據也不要緊。這樣作的好處是能夠減小運行應用時消耗的 RAM 容量。
如何使用?非常 easy~
buildTypes {
release {
// 開啓Zipalign 優化
zipAlignEnabled true
}
debug {
zipAlignEnabled false
}
}
複製代碼
看一下結果:
貌似沒啥用哦,能加仍是加上吧。
來到第二層,咱們再來開下資源映射文件中關於圖片這塊:
其實對於圖片而言,真的是個很蛋疼的操做,不過還好,一些簡單的小背景、小效果,如今大部分都直接採用 shape、selector 等實現,多少也避免引入了一些圖片。
對於圖片優化,主要分爲如下幾點:
什麼是套圖呢?
比如應用中的某個 Icon,通常來說,UI 都會爲咱們提供 n 套圖,以便於咱們適配不一樣分辨率記性,大概的目錄以下:
例以下面的這些大大小小的 Icon,一個個拷貝、更名也是比較痛苦的:
這個時候,SVG 便派上了用場。
可縮放矢量圖形(英語:Scalable Vector Graphics,SVG)是一種基於可擴展標記語言(XML),用於描述二維矢量圖形的圖形格式。 SVG由W3C制定,是一個開放標準。
SVG 優點:
SVG 劣勢:
ummm,須要註明一點,Android 6.0 + 支持,6.0 如下須要作兼容處理。不過如今應該也不必兼容那麼低的版本了吧?
如何在 Android Studio 中建立一張 SVG 圖片呢?以下所示:
彈出以下界面,在此頁面能夠選擇直接導入 Android 內置 Icon 庫圖,仍是手動加載 SVG or PSD 格式,看需選擇。
放個操做圖省事兒點:
使用也很 easy:
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/iv_toolbar_back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="fitXY"
app:srcCompat="@drawable/ic_arrow_back" />
複製代碼
別忘記在 build 裏面對此設置:
defaultConfig {
...
// 強制 Gradle 在編譯時不自動生成兼容低版本的位圖資源
vectorDrawables.useSupportLibrary = true
// 生成指定類型的圖片資源
vectorDrawables.generatedDensities = ['xhdpi', 'xxhdpi', 'xxxhdpi']
}
複製代碼
建議直接下載 svg,導入 svg,這個真心比較爽。
推薦下良心企業,阿里的 iconfont:
通過一通折騰,阿里下載 SVG,以後 Android Studio 導入 SVG,巴拉巴拉,好歹改完了,哈哈哈
先來舉個天馬行空的例子,例如同一張圖片在不一樣的狀態下顯示不一樣,好比成功綠色,失敗紅色等等。按照以往的習慣,那確定要求至少提供每種狀態對應的圖片,否則我怎麼搞?
可是有個實實在在的問題,那就是,圖片同樣,只是顏色發生了變化,假設五種狀態,咱們就須要引入至少五張圖片,那麼,能否只須要一張圖片,針對不一樣的狀態,咱們渲染不一樣的顏色呢?
固然能夠,這就是今天要說到的 Tint 着色器。有了它,最簡潔明瞭一點,至少能幫我剩下不少可謂是「無用」圖片,大大節省了不少空間,咱們的 Apk 更加「幹練」。
再舉一個咱們項目中常見的例子,首頁 Tab 欄,以下圖:
Tab 切換,字體變色、圖片變色,這個見怪不怪了吧。上來至少給我提供八張圖,四張默認,四張選中,而後經過 selector 文件設置,不給圖無法作。對吧,這就是以前最實際的想法,嗯,還感受本身可 dei 了。
一塊兒先來回顧下之前的 low 寫法:
Step 1:至少提供八張圖後,設置 icon 引用 selector 文件:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/nav_home"
android:icon="@drawable/selector_menu_home"
android:title="@string/nav_home" />
...
</menu>
複製代碼
Step 2:定義 Selector:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true" android:drawable="@drawable/ic_tab_home_sel"/>
<item android:drawable="@drawable/ic_tab_home"/>
</selector>
複製代碼
這樣寫當然木有問題,可是無緣無故多了不少圖片,在今天的我來看,必須不能忍,今天只用四張圖,幹他~
這裏吐槽下,因爲以前底部導航採用 BottomNavigationView 方式,折騰好半天踩折騰出來,中間無數次想放棄了。但回頭一想,我好歹也是跟隨我雞大的,何況抽菸的時候還要和文哥交流呢。艾瑪,不容易,容小弟我抽根菸,ummm,沒煙了😅😅😅。
先來看個效果圖吧,畢竟我費了好大力氣~ (其實想得瑟下~)
首先是佈局:
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/nav_bottom_menu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorWhite"
app:itemBackground="@null"
app:itemIconTint="@color/tint_selector_menu_color" // 重點在這裏
app:itemTextColor="@color/tint_selector_menu_color"
app:labelVisibilityMode="labeled"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:menu="@menu/nav_bottom_menu" />
複製代碼
編寫渲染顏色選擇器:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/comm_app_color" android:state_checked="true" />
<item android:color="@color/color_tab_def" />
</selector>
複製代碼
最後,修改咱們的 menu 文件中 icon 直接採用默認圖便可:
引入官方描述:
WebP 是 Google 開發的一種圖片文件格式,提供有損壓縮(如 JPEG)並支持透明性(如 PNG),不過與 JPEG 或 PNG 相比,這種格式能夠提供更好的壓縮效果。Android 4.0(API 級別 14)及更高版本支持有損 WebP 圖片,Android 4.3(API 級別 18)及更高版本支持無損且透明的 WebP 圖片。
注意:因爲只有 Android 4.3 及更高版本支持無損且透明的 WebP 圖片,所以您的項目聲明的 minSdkVersion 必須爲 18 或更高,才能使用 Android Studio 建立無損或透明的 WebP 圖片。
操做步驟以下:
點擊要轉換的圖片,選擇 Convert to WebP...
採用默認配置:
預覽結果圖:
200 多 kb 降到 20 多 kb,若是項目中此類圖片較多時,使用 WebP 確實很爽啊~
固然,關於轉換可單張、多張,看我的心情。
經歷了最痛苦的時候,咱們一塊兒打包看下通過二層過濾,咱們精簡了多少?
Apk 大小減小 1.5 MB,res 佔比從 28 % 下降到 15.5 %。其實裏面仍是有很大的優化空間,奈何懶癌上身,矯情開始做祟。
通過前倆層的打怪歷練,咱們終於要見大 Boss 了。Come on,篇幅過長,灰常感謝可以看到這裏,比個心心~
在正式玩微信資源壓縮前,咱們先來回顧下以前的混淆,說白了混淆不只僅優化了代碼,並且將關鍵的一些信息經過無心義的標示進行替換,從而進一步加深了反編譯破解的難度,僅僅是加深了。而咱們的佈局、圖片仍是屬於赤裸裸的狀態,以下所示:
針對某些安全要求比較高或者有那麼一丟丟追求的小夥伴,就是不想赤裸裸給你看,怎麼辦呢?
AndResGuard 上場~ (對比美團的方案,微信真香)
Step 1:項目根目錄 build 添加依賴
dependencies {
...
classpath 'com.tencent.mm:AndResGuard-gradle-plugin:1.2.17'
}
複製代碼
Step 2:app 目錄下新建 wechat-ResGuard.gradle 文件:
apply plugin: 'AndResGuard'
andResGuard {
// mappingFile = file("./resource_mapping.txt")
mappingFile = null
use7zip = true
useSign = true
// 打開這個開關,會keep住全部資源的原始路徑,只混淆資源的名字
keepRoot = false
// 設置這個值,會把arsc name列混淆成相同的名字,減小string常量池的大小
fixedResName = "arg"
// 打開這個開關會合並全部哈希值相同的資源,但請不要過分依賴這個功能去除去冗餘資源
mergeDuplicatedRes = true
whiteList = [
// App Logo 這裏對應找本身的 App Logo
"R.mipmap.ic_launcher",
"R.mipmap.ic_launcher_foreground",
"R.mipmap.ic_launcher_round",
// for fabric
"R.string.com.crashlytics.*",
// for google-services
"R.string.google_app_id",
"R.string.gcm_defaultSenderId",
"R.string.default_web_client_id",
"R.string.ga_trackingId",
"R.string.firebase_database_url",
"R.string.google_api_key",
"R.string.google_crash_reporting_api_key"
]
compressFilePattern = [
"*.png",
"*.jpg",
"*.jpeg",
"*.gif",
]
sevenzip {
artifact = 'com.tencent.mm:SevenZip:1.2.17'
//path = "/usr/local/bin/7za"
}
/**
* 可選: 若是不設置則會默認覆蓋assemble輸出的apk
**/
// finalApkBackupPath = "${project.rootDir}/final.apk"
/**
* 可選: 指定v1簽名時生成jar文件的摘要算法
* 默認值爲「SHA-1」
**/
// digestalg = "SHA-256"
}
複製代碼
Step 3:app 下 build 中添加末尾添加依賴
apply from: 'wechat-ResGuard.gradle'
複製代碼
Step 4:打包
最後咱們看下包裏面資源相應的資源是否被混淆了?
Apk 減小 0.4 MB,資源被混淆~
看了下雙十一數據,真有錢。。。
Analyze 選中 Run Inspection by Name...
輸入 unused re,選擇 Unused resources:
直接採用默認便可:
根據提示修改便可:
困了,不改了,改天再說。
物理刪除未使用的資源文件。
記得點擊中間的,否則給你刪沒了
一個個對應,而後開始刪除吧~
選中要移除的,直接 Remove 便可
丟個包瞅瞅。
ummm,我好像啥都沒弄。算求了。呼呼呼。。。
墨跡了好幾天,終於邁出了一小步。
加油呀~