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
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 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屬性,如下:
矢量圖顔色修改
我們是可以單獨修改矢量圖某一部分的顔色的,如圖:
Tint着色器
雖然我們前面說了,可以直接在 xml 檔案中修改矢量圖的顔色,但是并不建議直接修改矢量圖的顔色,而是使用Tint 着色器來實作。比如我們一般讓矢量圖的顔色為黑色,然後用 Tint 着色器去修改矢量圖的顔色為其他的各種顔色(比如紅色、綠色、藍色等),這樣隻需要一張SVG矢量圖就可以實作各種顔色,而不需要很多張不同顔色的圖檔。
Tint着色器修改顔色:
那如果我們想實作一個點選效果呢?
Tint着色器–點選變色
建立兩個選擇器,然後正常使用即可。
建立 drawable 選擇器 battery_selector.xml
建立 color 選擇器 battery_tint_selector.xml
使用
移除無用資源
Remove unused resources
AS 給我們提供了一鍵移除所有無用的資源,如圖。
但是這種方式不建議使用,因為如果某資源僅存在使用動态擷取資源id的方式,那麼這個資源會被認為沒有使用過,進而會直接被删除。
動态擷取資源id的方式:
getResources().getIdentifier(“name”,“defType”,getPackageName());
另外,如果某些資源檔案可能以後會用到,是以不想删除,這種方式無法做到,因為會删除所有檢測到的沒有使用過的資源。
Lint移除無用資源
可以點選這個按鈕過濾不想被移除的資源:
國際化資源配置
預設會适配很多國家的語言:
配置為隻适配英語後:
android{
defaultConfig{
// 隻适配英語
resConfigs 'en'
}
}
這樣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 即可
但是我們會發現,運作時會報錯,因為 minifyEnabled 既壓縮了代碼,也混淆了代碼,是以我們需要處理下混淆,需要在proguard-rules.pro檔案中keep不能被混淆的類。
代碼混淆壓縮為什麼能減小apk大小?代碼中複雜的變量名和方法名全變為了簡短的字母和數字,自然減小了體積。
資源壓縮
資源壓縮隻與代碼壓縮協同工作,需要同時開啟minifyEnabled:
開啟時,會檢測未使用的資源,對于使用的資源,包括動态方式使用的資源:
//動态擷取資源id的方式:
getResources().getIdentifier("activity_main1", "layout", getPackageName());
不進行處理,activity_main1.xml保持原内容。
對于未使用的資源,打包進apk時,會進行資源壓縮,比如:
activity_main2.xml是未使用的,apk中activity_main2.xml裡面的内容幾乎全被删除,壓縮的隻剩下很少的内容:
注意:删除的是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,如下
将該檔案儲存在項目資源中,例如,儲存在 res/raw/keep.xml。建構時不會将該檔案打包到 APK 之中。
如果你有想要保留或舍棄的特定資源,則可以建立如下的 xml 檔案,然後在 tools:keep 屬性中指定每個要保留的資源,在 tools:discard 屬性中指定每個要舍棄的資源。
資源混淆
資源混淆主要這兩個部分:
資源目錄名和資源名都會被混淆。
未混淆的:
混淆後:
res變為r
資源目錄名和資源名全是簡短的字母和數字:
資源混淆後,代碼中是如何通路到資源的?
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 資源在其所屬的資源類型中所出現的次序。
混淆步驟
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檔案格式
檢視二進制格式的ARSC檔案可以使用010editor工具。