天天看點

Android性能優化—apk瘦身APK的結構使用SVG矢量圖移除無用資源動态庫打包配置代碼混淆壓縮資源壓縮資源混淆

1.代碼:代碼壓縮混淆

2.資源方面:小圖檔使用SVG矢量圖、移除無用資源、資源壓縮、資源混淆

3.動态庫:一般隻需要配置armeabi和armeabi-v7a即可,相比配置各種ABI減少了大量體積(x86和x86_64架構的手機CPU已經幾乎沒有了,市面上幾乎都是arm架構的手機,是以打包時可以忽略x86和x86_64架構的so庫)。

APK的結構

包含以下目錄:

  • assets/: 包含了應用的資源,這些資源能夠通過AssetManager對象獲得。
  • lib/: 包含了針對處理器層面的被編譯的代碼。這個目錄針對每個平台類型都有一個子目錄,比如armeabi, armeabi-v7a, arm64-v8a, x86, x86_64和mips。
  • res/: 包含了沒被編譯到resources.arsc的資源。
  • META-INF/: 包含CERT.SF和CERT.RSA簽名檔案,也包含了MANIFEST.MF檔案。(譯注:校驗這個APK是否被人改動過)

包含以下檔案:

  • classes.dex: 包含了能被Dalvik/Art虛拟機了解的 dex 檔案格式的類。
  • resources.arsc: 包含了被編譯的資源。該檔案包含了res/values目錄的所有配置的 xml 内容。打包工具将 xml 内容編譯成二進制形式并壓縮。這些内容包含了語言字元串和styles,還包含了那些内容雖然不直接存儲在resources.arsc檔案中,但是給定了該内容的路徑,比如布局檔案和圖檔。是以又叫 資源映射表
  • AndroidManifest.xml: 包含了主要的Android配置檔案。這個檔案列出了應用名稱、版本、通路權限、引用的庫檔案。該檔案使用二進制 xml 格式存儲。(譯注:該檔案還能看到應用的minSdkVersion, targetSdkVersion等資訊)

使用SVG矢量圖

SVG導入

SVG(Scalable Vector Graphics),可縮放矢量圖。SVG不會像位圖一樣因為縮放而讓圖檔品質下降。優點在于可以減小APK的體積(比如如果使用png圖檔,則需要在drawable和drawable-xhdpi等目錄下放置多套分辨率不同的圖檔,否則png圖檔會因為縮放而變得模糊)。常用于簡單小圖示,官方建議一般圖檔小于200*200時用SVG,當圖檔較大時SVG圖檔繪制時間會較長。對于app的啟動頁的背景圖這種大圖檔,就不能使用SVG,可以使用webp圖檔來減小圖檔大小。

svg是由xml定義的,标準svg根節點為

<svg>

Android中隻支援

<vector>

,我們可以通過 vector 将svg的根節點

<svg>

轉換為

<vector>

在Android Studio中打開工程,在res目錄中點選右鍵 -> new -> Vector Asset

Android性能優化—apk瘦身APK的結構使用SVG矢量圖移除無用資源動态庫打包配置代碼混淆壓縮資源壓縮資源混淆
Android性能優化—apk瘦身APK的結構使用SVG矢量圖移除無用資源動态庫打包配置代碼混淆壓縮資源壓縮資源混淆

SVG批量轉換

如果有多個svg需要轉換為android的vector,則可以通過第三方工具 svg2vector 進行批量轉換。

執行轉換指令:

java -jar svg2vector-cli-1.0.0.jar -d . -o a -h 20 -w 20

-d 指定svg檔案所在目錄

-o 輸出android vector圖像目錄

-h 設定轉換後svg的高

-w 設定轉換後svg的寬

不支援的SVG

如果 SVG 檔案包含不受支援的功能,将在 Vector Asset Studio 的底部顯示一個錯誤提示,如圖:

Android性能優化—apk瘦身APK的結構使用SVG矢量圖移除無用資源動态庫打包配置代碼混淆壓縮資源壓縮資源混淆

不支援的功能舉例:

濾鏡效果:不支援投影,模糊和顔色矩陣等效果。

文本:建議使用其他工具将文本轉換為形狀。

矢量圖向後相容

Android 5.0(API 21)之前的版本不支援矢量圖,使用 Vector Asset Studio 有兩種方式适配:生成PNG和使用支援庫。

方式一:生成 png 格式的圖檔

Vector Asset Studio 可在建構時 針對每種螢幕密度将矢量圖轉換為不同大小的位圖,在 build.gradle 中配置如下,适用于 Gradle 插件1.5 及以上版本:

android{

defaultConfig{

// 5.0(API 21)版本以下,将svg圖檔生成指定次元的png圖檔

generatedDensities = [‘xhdpi’,‘xxhdpi’]

}

}

方式二:支援庫

在 build.gradle 中配置如下,适用于 Gradle 插件2.0及以上版本:

android{

// Gradle Plugin 2.0+

defaultConfig{

// 利用支援庫中的 VectorDrawableCompat 類,可實作 2.1 版本及更高版本中支援 VectorDrawable

vectorDrawables.useSupportLibrary = true

}

}

dependencies {

// 支援庫版本需要是 23.2 或更高版本

compile ‘com.android.support:appcompat-v7:23.2.0’

}

這時,使用矢量圖必須使用 app:srcCompat 屬性,而不是 android:src屬性,如下:

Android性能優化—apk瘦身APK的結構使用SVG矢量圖移除無用資源動态庫打包配置代碼混淆壓縮資源壓縮資源混淆

矢量圖顔色修改

我們是可以單獨修改矢量圖某一部分的顔色的,如圖:

Android性能優化—apk瘦身APK的結構使用SVG矢量圖移除無用資源動态庫打包配置代碼混淆壓縮資源壓縮資源混淆
Android性能優化—apk瘦身APK的結構使用SVG矢量圖移除無用資源動态庫打包配置代碼混淆壓縮資源壓縮資源混淆
Android性能優化—apk瘦身APK的結構使用SVG矢量圖移除無用資源動态庫打包配置代碼混淆壓縮資源壓縮資源混淆

Tint着色器

雖然我們前面說了,可以直接在 xml 檔案中修改矢量圖的顔色,但是并不建議直接修改矢量圖的顔色,而是使用Tint 着色器來實作。比如我們一般讓矢量圖的顔色為黑色,然後用 Tint 着色器去修改矢量圖的顔色為其他的各種顔色(比如紅色、綠色、藍色等),這樣隻需要一張SVG矢量圖就可以實作各種顔色,而不需要很多張不同顔色的圖檔。

Tint着色器修改顔色:

Android性能優化—apk瘦身APK的結構使用SVG矢量圖移除無用資源動态庫打包配置代碼混淆壓縮資源壓縮資源混淆

那如果我們想實作一個點選效果呢?

Tint着色器–點選變色

建立兩個選擇器,然後正常使用即可。

建立 drawable 選擇器 battery_selector.xml

Android性能優化—apk瘦身APK的結構使用SVG矢量圖移除無用資源動态庫打包配置代碼混淆壓縮資源壓縮資源混淆

建立 color 選擇器 battery_tint_selector.xml

Android性能優化—apk瘦身APK的結構使用SVG矢量圖移除無用資源動态庫打包配置代碼混淆壓縮資源壓縮資源混淆

使用

Android性能優化—apk瘦身APK的結構使用SVG矢量圖移除無用資源動态庫打包配置代碼混淆壓縮資源壓縮資源混淆

移除無用資源

Remove unused resources

AS 給我們提供了一鍵移除所有無用的資源,如圖。

Android性能優化—apk瘦身APK的結構使用SVG矢量圖移除無用資源動态庫打包配置代碼混淆壓縮資源壓縮資源混淆

但是這種方式不建議使用,因為如果某資源僅存在使用動态擷取資源id的方式,那麼這個資源會被認為沒有使用過,進而會直接被删除。

動态擷取資源id的方式:

getResources().getIdentifier(“name”,“defType”,getPackageName());

另外,如果某些資源檔案可能以後會用到,是以不想删除,這種方式無法做到,因為會删除所有檢測到的沒有使用過的資源。

Lint移除無用資源

Android性能優化—apk瘦身APK的結構使用SVG矢量圖移除無用資源動态庫打包配置代碼混淆壓縮資源壓縮資源混淆
Android性能優化—apk瘦身APK的結構使用SVG矢量圖移除無用資源動态庫打包配置代碼混淆壓縮資源壓縮資源混淆
Android性能優化—apk瘦身APK的結構使用SVG矢量圖移除無用資源動态庫打包配置代碼混淆壓縮資源壓縮資源混淆
Android性能優化—apk瘦身APK的結構使用SVG矢量圖移除無用資源動态庫打包配置代碼混淆壓縮資源壓縮資源混淆

可以點選這個按鈕過濾不想被移除的資源:

Android性能優化—apk瘦身APK的結構使用SVG矢量圖移除無用資源動态庫打包配置代碼混淆壓縮資源壓縮資源混淆

國際化資源配置

預設會适配很多國家的語言:

Android性能優化—apk瘦身APK的結構使用SVG矢量圖移除無用資源動态庫打包配置代碼混淆壓縮資源壓縮資源混淆

配置為隻适配英語後:

android{
    defaultConfig{
        // 隻适配英語
        resConfigs 'en'
    }
}
           
Android性能優化—apk瘦身APK的結構使用SVG矢量圖移除無用資源動态庫打包配置代碼混淆壓縮資源壓縮資源混淆

這樣string中隻有指定語言對應的字元串,減少了大量其他語言的字元串。

動态庫打包配置

首先我們需要知道 so檔案是由ndk編譯出來的動态庫,是 c/c++ 寫的,是以不是跨平台的。即每一個平台需要使用對應的so庫。

ABI 是應用程式二進制接口簡稱(Application Binary Interface),定義了二進制檔案(尤其是.so檔案)如何運作在相應的系統平台上,從使用的指令集,記憶體對齊到可用的系統函數庫。

在Android 系統上,每一個CPU架構對應一個ABI:armeabi,armeabi-v7a,arm64- v8a,x86,x86_64,mips,mips64。

現在我們一般隻需要配置armeabi-v7a即可,相比配置各種ABI減少了大量體積。

android{
    defaultConfig{
        ndk{
            abiFilters "armeabi-v7a"
        }
    }
}
           

反編譯微信的apk就可以發現lib目錄下隻有armeabi-v7a目錄。

ABI–應用二進制接口

早期的Android系統幾乎隻支援ARM v5的CPU架構,而現在你知道它支援多少種了嗎?7種!

Android系統目前支援以下七種不同的CPU架構:ARMv5,ARMv7 (從2010年起),x86 (從2011年起),MIPS (從2012年起),ARMv8,MIPS64和x86_64 (從2014年起),每一種都關聯着一個相應的ABI。

ABI 是應用程式二進制接口簡稱(Application Binary Interface),定義了二進制檔案(尤其是.so檔案)如何運作在相應的系統平台上,從使用的指令集,記憶體對齊到可用的系統函數庫。

在Android 系統上,每一個CPU架構對應一個ABI:armeabi,armeabi-v7a,arm64-v8a,x86,x86_64,mips,mips64。

ABI Supported Instruction Set(s) Notes
armeabi ARMV5TE and later,Thumb-1 No hard float
armeabi-v7a armeabi,Thumb-2,VFPv3-D16,Other,optional Incompatible with ARMv5,v6 devices
arm64-v8a AArch-64
x86 x86(IA-32),MMX,SSE/2/3,SSSE3 No support for MOVBE or SSE4
x86_64 x86-64,MMX,SSE/2/3,SSE3,SSE4.1,SSE4.2,POPCNT
mips MIPS32r1 and later
mips64 MIPS64r6

各版本分析如下:

  • mips / mips64:極少用于手機可以忽略
  • x86 / x86_64:x86 架構的手機都會包含由 Intel 提供的稱為 Houdini 的指令集動态轉碼工具,實作 對 arm .so 的相容,再考慮 x86 1% 以下的市場占有率,x86 相關的兩個 .so 也是可以忽略的
  • armeabi:ARM v5 這是相當老舊的一個版本,缺少對浮點數計算的硬體支援,在需要大量計算時有性能瓶頸
  • armeabi-v7a:ARM v7 目前主流版本
  • arm64-v8a:64位支援

ps:ARM v8的相關資訊:

ARM公司自己本身并沒有64位晶片設計技術,在2011年11月,他是通過了收購MIPS64處理器架構的部分技術使用權,再結合ARM的一些特性設計出來的。也就是說:MIPS、ARM、X86三大架構中,唯一沒有64位技術的ARM,通過收購MIPS的形式得到了64位。

所謂的ARMv8架構,就是在MIPS64架構上增加了ARMv7架構中已經擁有的的TrustZone技術、虛拟化技術及NEON advanced SIMD技術等特性,研發成的。

64位ARMv8架構中包含兩個執行狀态:AArch32(也就是我們常說的ARMv7)和AArch64(ARMv8)。AArch64執行狀态針對64位處理技術,引入了一個全新指令集A64(也就是基于收購的MIPS64架構),而AArch32執行狀态将支援現有的ARM指令集。是以64位的ARM處理器中同時包含着32位的ARMv7和64位的ARMv8兩種架構。是以:看到這裡,你一定明白了,ARM64位處理器和電腦的64位處理器是兩個截然不容的概念,他并不是64位就能原生向下相容32位程式,而是通過64位處理器中內建的32位架構來運作32位程式。說得通俗點,它不是以64位形态來運作32位程式,而是以32位的形态運作32位程式的。

由于目前新出的64位處理器包含兩個架構,而且制程技術沒有提升(28nm),同時在手機與平闆上,晶片面積有着嚴格的限定,不能過分增加,這導緻64位ARM處理器平均配置設定到每個架構的半導體數量銳減,也就是說從64位處理器中的32位架構方面,對于同規格的32位處理器而言,不但沒有提高,性能反而是一定規模下降的。但處理器廠家又必須給消費者一個交代,以更好的推廣64位,是以廠家就必須在其他方面提升性能,以彌補CPU的半導體數量減少帶來的損失。比如:更換性能更強的GPU、提升記憶體帶寬、多核心虛拟單顆核心提升單核性能、聯合跑分軟體商修改跑分權重(提升GPU分數,降低CPU分數的權重)等等。這樣,揚長避短,最終到達消費者手裡,用跑分軟體一跑,确實有提升,使用者開心,廠家腰包也鼓了。

綜上所述,ARM64位處理器從嚴格意義來說,叫它ARM32+64更加貼切,他相對于ARM32位處理器,有倒退的地方,也有進步的餘地,但正因為倒退激起了ARM進取的決心,讓它大刀闊斧的向前變革,不得不說也算一種進步。但ARM64在的手機上真的有用嗎?我隻能說,目前确實沒啥用,但今後或許有。

谷歌官方曾說,安卓很早前就支援64位了,這話不假,從Android4.0到Android4.4,安卓系統都支援64位的硬體,但是這僅僅表示底層驅動支援64位,能運作在64位的硬體之上,僅此而已。然而,上層運作軟體的,無論是Dalvik的虛拟機,還是ART虛拟機都是32位的。也就是說,隻要你的手機系統是Android4.0—4.4,即便你的處理器是64位,也隻能在32位虛拟機下運作32位程式,就算真的64位程式擺在你眼前,也無法安裝。

Android L開始才真正支援32位和64位的ART虛拟機,配合上64位處理器,名正言順的運作64位軟體。但是問題又來了,沒有軟體商 願意開發64位程式。

ARMv8是一套不錯的指令集,它既支援未來的64位程式,也向下相容現有32位程式。有了ARMv8的支撐,以後的64位手機作業系統,如Android L 64bit都可以簡單、高效地支援現有的32位App,你不用擔心相容性問題。

大小端

大端模式,是指資料的高位元組儲存在記憶體的低位址中,而資料的低位元組儲存在記憶體的高位址中,這樣的存儲模式有點兒類似于把資料當作字元串順序處理:位址由小向大增加,而資料從高位往低位放;這和我們的閱讀習慣一緻。

小端模式,是指資料的高位元組儲存在記憶體的高位址中,而資料的低位元組儲存在記憶體的低位址中,這種存儲模式将位址的高低和資料位權有效地結合起來,高位址部分權值高,低位址部分權值低。

為什麼會有大小端模式之分呢?這是因為在計算機系統中,我們是以位元組為機關的,每個位址單元都對應着一個位元組,一個位元組為 8bit。

例如這樣一個資料:0x11223344 轉為二進制為 00010001 00100010 00110011 01000100

大端存儲後 我們看到的就是 00010001 00100010 00110011 01000100 即 16進制的 11 22 33 44

小端存儲後 我們看到的就是 01000100 00110011 00100010 00010001 即 16進制的 44 33 22 11

現階段狀況

目前Intel的80x86系列晶片是唯一還在堅持使用小端的晶片,ARM晶片預設采用小端,但可以切換為大端;而MIPS等晶片要麼采用全部大端的方式儲存,要麼提供選項支援大端——可以在大小端之間切換。另外,對于大小端的處理也和編譯器的實作有關,在C語言中,預設是小端(但在一些對于單片機的實作中卻是基于大端,比如Keil 51C),Java是平台無關的,預設是大端。在網絡上傳輸資料普遍采用的都是大端。

代碼混淆壓縮

将 minifyEnabled 設定為 true 即可

Android性能優化—apk瘦身APK的結構使用SVG矢量圖移除無用資源動态庫打包配置代碼混淆壓縮資源壓縮資源混淆

但是我們會發現,運作時會報錯,因為 minifyEnabled 既壓縮了代碼,也混淆了代碼,是以我們需要處理下混淆,需要在proguard-rules.pro檔案中keep不能被混淆的類。

代碼混淆壓縮為什麼能減小apk大小?代碼中複雜的變量名和方法名全變為了簡短的字母和數字,自然減小了體積。

資源壓縮

資源壓縮隻與代碼壓縮協同工作,需要同時開啟minifyEnabled:

Android性能優化—apk瘦身APK的結構使用SVG矢量圖移除無用資源動态庫打包配置代碼混淆壓縮資源壓縮資源混淆

開啟時,會檢測未使用的資源,對于使用的資源,包括動态方式使用的資源:

//動态擷取資源id的方式:
	getResources().getIdentifier("activity_main1", "layout", getPackageName());
           

不進行處理,activity_main1.xml保持原内容。

對于未使用的資源,打包進apk時,會進行資源壓縮,比如:

activity_main2.xml是未使用的,apk中activity_main2.xml裡面的内容幾乎全被删除,壓縮的隻剩下很少的内容:

Android性能優化—apk瘦身APK的結構使用SVG矢量圖移除無用資源動态庫打包配置代碼混淆壓縮資源壓縮資源混淆

注意:删除的是apk中activity_main2.xml裡面的内容,項目中activity_main2.xml裡面的内容不會被删除,資源壓縮是指壓縮資源裡的内容。

預設情況是未啟用嚴格模式,即尋找使用的資源名稱是采用startwith模式,如果activity_main1.xml被使用,則會認為activity_main12.xml,activity_main123.xml等資源檔案也被使用,是以這些資源檔案的内容也被保留。如果啟用嚴格模式,則尋找使用的資源名稱是采用equal模式,是以activity_main12.xml,activity_main123.xml等資源檔案的内容會被删除,因為這些資源檔案沒有被使用。

如需啟動則需設定 shrinkMode,需要建立keep.xml,如下

Android性能優化—apk瘦身APK的結構使用SVG矢量圖移除無用資源動态庫打包配置代碼混淆壓縮資源壓縮資源混淆

将該檔案儲存在項目資源中,例如,儲存在 res/raw/keep.xml。建構時不會将該檔案打包到 APK 之中。

如果你有想要保留或舍棄的特定資源,則可以建立如下的 xml 檔案,然後在 tools:keep 屬性中指定每個要保留的資源,在 tools:discard 屬性中指定每個要舍棄的資源。

Android性能優化—apk瘦身APK的結構使用SVG矢量圖移除無用資源動态庫打包配置代碼混淆壓縮資源壓縮資源混淆

資源混淆

資源混淆主要這兩個部分:

Android性能優化—apk瘦身APK的結構使用SVG矢量圖移除無用資源動态庫打包配置代碼混淆壓縮資源壓縮資源混淆

資源目錄名和資源名都會被混淆。

未混淆的:

Android性能優化—apk瘦身APK的結構使用SVG矢量圖移除無用資源動态庫打包配置代碼混淆壓縮資源壓縮資源混淆

混淆後:

res變為r

Android性能優化—apk瘦身APK的結構使用SVG矢量圖移除無用資源動态庫打包配置代碼混淆壓縮資源壓縮資源混淆

資源目錄名和資源名全是簡短的字母和數字:

Android性能優化—apk瘦身APK的結構使用SVG矢量圖移除無用資源動态庫打包配置代碼混淆壓縮資源壓縮資源混淆
Android性能優化—apk瘦身APK的結構使用SVG矢量圖移除無用資源動态庫打包配置代碼混淆壓縮資源壓縮資源混淆

資源混淆後,代碼中是如何通路到資源的?

Android性能優化—apk瘦身APK的結構使用SVG矢量圖移除無用資源動态庫打包配置代碼混淆壓縮資源壓縮資源混淆

resource.arsc – > Android Resource

resources.arsc是Android打包後,資源映射檔案(資源映射表),所有res目錄下的資源都會到此檔案裡面。代碼中是通過R檔案中的資源id通路資源的,通過這個資源id可以在resources.arsc中找到對應的資源路徑,是以隻要resources.arsc中的資源路徑改為混淆後的資源路徑即可定位到資源。

資源id的格式:

R檔案中資源的整型數格式為:0xpptteeee(16進制,p代表的是package,t代表的是type,e代表的是entry)。

  • Package ID 包ID,系統為0x01,應用程式資源為0x7f。
  • Type ID 資源的類型ID,資源的類型有animator、anim、color等等,每一種都會被賦予一個ID。
  • Entry ID 資源在其所屬的資源類型中所出現的次序。
Android性能優化—apk瘦身APK的結構使用SVG矢量圖移除無用資源動态庫打包配置代碼混淆壓縮資源壓縮資源混淆

混淆步驟

1.解壓待混淆APK,記錄APK内檔案存儲方式(結合強制壓縮檔案清單,/config/compressData.txt)

解析 arsc 檔案(ZIP中存儲檔案兩種方式:DEFLATED(壓縮)/STORED(僅存儲),對于APK檔案來說某些資源不允許壓縮(如:SoudPool加載raw下的mp3),而有些資源可以壓縮但是AS打包APK時卻沒有壓縮(如png/jpg等)。)

2.混淆 arsc 檔案資料中對應的資源名與檔案路徑位元組資料

3.輸出混淆後的 arsc 檔案至 app 目錄

4.将 apk 中其他檔案拷貝到 app 目錄,并根據混淆修改 res/ 目錄下檔案名

5.打包、對齊并簽名

http://androidos.net.cn/android/8.0.0_r4/xref/frameworks/base/libs/androidfw/include/androidfw/ResourceTypes.h

http://androidos.net.cn/android/7.1.1_r28/xref/frameworks/base/include/androidfw/ResourceTypes.h

https://github.com/shwenzhang/AndResGuard

ARSC檔案格式

Android性能優化—apk瘦身APK的結構使用SVG矢量圖移除無用資源動态庫打包配置代碼混淆壓縮資源壓縮資源混淆

檢視二進制格式的ARSC檔案可以使用010editor工具。

字元串池的格式

Android性能優化—apk瘦身APK的結構使用SVG矢量圖移除無用資源動态庫打包配置代碼混淆壓縮資源壓縮資源混淆

混淆ARSC檔案

Android性能優化—apk瘦身APK的結構使用SVG矢量圖移除無用資源動态庫打包配置代碼混淆壓縮資源壓縮資源混淆