文章首發於公眾號「 技術最TOP 」
,每天都有乾貨文章持續更新,可以微信搜尋「 技術最TOP 」
第一時間閱讀!
0. 前言
前幾天啊,在公眾號發了一篇文章《優化ApK大小之ABI Filters 和 APK split》,評論區收到了一些留言說,文章講得不夠深入,關於系統是如何選擇不同abi下的so庫的?當前APP該如何適配?該去掉哪些該保留哪些?都存在一些疑問。
因此,決定親自更文一篇,系統地講一下關於Android CPU架構方面的一些東西,以及結合大廠APP如微信、支付寶、淘寶等APP的適配情況,分析我們開發APP中該如何適配。本文涉及以下幾個問題:
- 什麼是ABI?
- ABI有何作用?
- 目前大廠APP是如何適配不同的CPU架構的?
- ABI 是如何工作的?
- 我們自己的APP中該如何適配?
- ABI split-效能+相容全都要
本篇文章中,就一一為你解答這些疑問。
1. 什麼是ABI?
ABI是英文Application Binary Interface的縮寫,及應用二進位制介面。
不同Android裝置,使用的CPU架構可能不同,因此支援不同的指令集。 CPU 與指令集的每種組合都有其自己的應用二進位制介面(或 ABI),ABI非常精確地定義了應用程式的機器程式碼應如何在執行時與系統互動。您必須為要與您的應用程式一起使用的每種CPU架構指定一個ABI(Application Binary Interface)。
ABI 包含以下資訊:
- 可使用的 CPU 指令集(和擴充套件指令集)。
- 執行時記憶體儲存和載入的位元組順序。Android 始終是 little-endian。
- 在應用和系統之間傳遞資料的規範(包括對齊限制),以及系統呼叫函式時如何使用堆疊和暫存器。
- 可執行二進位制檔案(例如程式和共享庫)的格式,以及它們支援的內容型別。Android 始終使用 ELF。
如何重整 C++ 名稱。
Android目前支援以下7種ABIs:
mips, mips64, X86, X86–64, arm64-v8a, armeabi, armeabi-v7a
2. ABI有何作用?
當我們想要在專案中使用 native(C/C++) 類庫,我們必須對要支援的處理器架構提供對應編譯包。每個處理器架構需要我們提供一個或多個包含native程式碼的.so檔案。
預設情況下,為了使APP有更好的相容性,我們使用Android Studio 或者命令打包時,會預設支援所有的架構,但相應的APK size 會瘋狂的增大。對於使用者來說,目標裝置只需要其中一個版本,但當使用者下載APK時,會全部下載(對使用者來說相當的不友好)。
怎麼辦呢?abifilters
為我們提供瞭解決方案,abifilters
為我們提供了選擇適配指定CPU架構的能力,只需要在app下的build.gradle
新增如下配置:
android {
defaultConfig {
ndk {
abiFilters 'arm64-v8a', 'x86_64'
}
}
}
你可能看了上面的這些文字,還不能理解abi的作用,那麼我們就用一個簡單的例子來說明一下。
2.1 舉例說明ABI的作用
首先,我們建立一個最簡單的Hello world
應用,只有一個Activity和一個啟動圖示。我們看以下打出來的apk:
沒有任何的原生庫使用,大小為2.1MB
,現在我們為它新增多ABI原生庫支援,我們在專案中整合Realm
,然後打包。
看到沒,apk大小從2.1MB
猛增加到11.2MB
,多了一個原生so庫的資料夾,大小為8.8MB
,我們來看一下它的詳細資訊:
如上圖所示,Realm
為5種CPU架構生成了.so
庫,分別是mips
、x86
、x86_64
、arm64-v8a
、armeabi-v7a
。增加了8.8MB
包的大小。但是這不是我們想要的,我們只想要適配我們指定的的CPU架構,因此,我們需要在gralde.build中新增abifilters
配置來完成我們想要的效果:
android {
compileSdkVersion 28 // 編譯sdk版本
defaultConfig {
applicationId "com.example.zhouwei.helloworld"
minSdkVersion 15
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
// 適配指定CPU架構
ndk {
abiFilters 'arm64-v8a', 'x86_64'
}
}
}
效果如下:
可以看到,只生成了我們指定CPU架構的so檔案,包的大小也減少了5.3MB
。
這時候,你可能會有一個疑問,Android 共支援7種CPU架構,那麼,我們在實際專案中該適配哪些CPU架構能保證最好的相容,同時又最大限度的減少APK的大小?
在回答這個問題之前,我們不妨看一下這些頂級巨頭公司,他們是是如何適配的。
3. 目前大廠APP是如何適配不同的CPU架構的?
首先,我們下載一些大廠的APK,看一下他們的適配情況,這裡我分析了微信、手機QQ、支付寶和淘寶這4個APP的適配情況:
可以看到,微信適配的是arm64-v8a
(微信應該是最近才適配到arm64-v8a
,以前是armeabi
),支付寶和手Q適配的是armwabi
,淘寶適配的是armwabi-v7a
。各個APP適配的平臺不太一樣,但是他們有一個共同點,那就是它們只指定了一個平臺。
等等,上面這些APP只適配了一中CPU架構,比如只適配了armwabi-v7a
,那如果APP裝在其他架構的手機上,如arm64-v8a
上,會蹦嗎?
要弄清楚這個問題,我們得先搞清楚,ABI是如何工作的。
ABI是如何工作的呢?
官方文件解釋如下:
Android 系統在執行時知道它支援哪些 ABI,因為版本特定的系統屬性會指示:
- 裝置的主要 ABI,與系統映像本身使用的機器程式碼對應。
- (可選)與系統映像也支援的其他 ABI 對應的輔助 ABI。
此機制確保系統在安裝時從軟體包提取最佳機器程式碼。
為實現最佳效能,應直接針對主要 ABI 進行編譯。例如,基於 ARMv5TE 的典型裝置只會定義主 ABI:armeabi。相反,基於 ARMv7 的典型裝置將主 ABI 定義為 armeabi-v7a,並將輔助 ABI 定義為 armeabi,因為它可以執行為每個 ABI 生成的應用原生二進位制檔案。
64 位裝置也支援其 32 位變體。以 arm64-v8a 裝置為例,該裝置也可以執行 armeabi 和 armeabi-v7a 程式碼。但請注意,如果應用以 arm64-v8a 為目標,而非依賴於執行 armeabi-v7a 版應用的裝置,則應用在 64 位裝置上的效能要好得多。
許多基於 x86 的裝置也可執行 armeabi-v7a 和 armeabi NDK 二進位制檔案。對於這些裝置,主 ABI 將是 x86,輔助 ABI 是 armeabi-v7a。
上面這一段是不是有點看蒙了,這裡我來簡單解釋以下。總的來說,就是一個Android裝置可以支援多種ABI,裝置主ABI和輔助ABI,以arm64-v8a
為主ABI的裝置,輔助ABI為armeabi-v7a
和armeabi
,以armeabi-v7a
為主ABI的裝置,輔助ABI為armeabi
。
另外,x86 架構的手機都會包含由 Intel 提供的稱為 Houdini 的指令集動態轉碼工具,實現對 arm .so 的相容,也就是說有適配armeabi平臺的APP是可以跑在x86手機上的。
3.1 主輔助ABI具體適配流程
前面說了ABI的工作原理,一個Android裝置支援主輔ABI,那麼他們具體是如何工作的呢?我們以arm64-v8a
架構的手機為例:
對於一個cpu是arm64-v8a架構的手機,它執行app時,進入jnilibs去讀取庫檔案時,先看有沒有arm64-v8a資料夾,如果沒有該資料夾,去找armeabi-v7a資料夾,如果沒有,再去找armeabi資料夾,如果連這個資料夾也沒有,就丟擲異常;
如果有arm64-v8a資料夾,那麼就去找特定名稱的.so檔案,注意:如果沒有找到想要的.so
檔案,不會再往下(armeabi-v7a資料夾)找了,而是直接丟擲異常。
Exception:Java.lang.UnsatisfiedLinkError: dlopen failed: library “/***.so” not found
特別需要注意的情況是在命中了資料夾,而未命中so檔案這種情況:
- 比如命中了
arm64-v8a
資料夾,沒有找到需要的so檔案,就不會再往下(armeabi-v7a資料夾)找了,而是直接丟擲異常。 - 如果你的專案用到了第三方依賴,如果只保留一個ABI的時候,建議在Build中加入ndk.abiFilters
- 例如:第三方aar檔案,如果這個sdk對abi的支援比較全,可能會包含armeabi、armeabi-v7a、x86、arm64-v8a、x86_64五種abi,而你應用的其它so只支援armeabi、armeabi-v7a、x86三種,直接引用sdk的aar,會自動編譯出支援5種abi的包。但是應用的其它so缺少對其它兩種abi的支援,那麼如果應用執行於arm64-v8a、x86_64為首選abi的裝置上時,就會==crash==了哦。
因此,我們需要在我們的app中配置 abiFilter 配置,來避免一些未知的錯誤。
defaultConfig {
ndk {
abiFilters "armeabi"// 指定ndk需要相容的ABI(這樣其他依賴包裡x86,armeabi,arm-v8之類的so會被過濾掉)
}
}
3.2 Android 7種CPU架構在當前市場的佔有率
arm64-v8a
: 目前主流版本armeabi-v7a
: 一些老舊的手機x86 / x86_64
: x86 架構的手機都會包含由 Intel 提供的稱為 Houdini 的指令集動態轉碼工具,實現對 arm .so 的相容,再考慮 x86 1% 以下的市場佔有率,x86 相關的兩個 .so 也是可以忽略的armeabi/mips / mips64
: NDK 以前支援 ARMv5 (armeabi) 以及 32 位和 64 位 MIPS,但 NDK r17 已不再支援,極少用於手機可以忽。
目前手機市場上,x86 / x86_64/armeabi/mips / mips6
的架構,基本可以不不考慮了,它們的佔有量應很少很少了,arm64-v8a
作為最新一代架構,應該是目前的主流,armeabi-v7a
只存在少部分老舊手機。
我試著在Google上查詢,具體的市場佔有資料,但沒找到,但是從國民級應用微信只適配arm64-v8a
就可以看出,arm64-v8a
是目前的主流,並且還有一點,Google Play 從2019年8月開始,就強制APP適配arm64-v8a
,以慢慢淘汰32位的armeabi-v7a
。
4. 我們專案中該如何適配呢?
這裡就可以回答前面的兩個問題了。
Q1
: 只適配了armwabi-v7a
,那如果APP裝在其他架構的手機上,如arm64-v8a
上,會蹦嗎?
A:
不會,但是反過來會。
因為armwabi-v7a
和arm64-v8a
會向下相容:
- 只適配
armeabi
的APP可以跑在armeabi
,x86
,x86_64
,armwabi-v7a
,arm64-v8
上 - 只適配
armwabi-v7a
可以執行在armwabi-v7a
和arm64-v8a
- 只適配
arm64-v8a
可以執行在arm64-v8a
上
那我們該如何適配呢?給出如下幾個方案:
方案一
:只適配armeabi
優點:
基本上適配了全部CPU架構(除了淘汰的mips和mips_64)缺點:
效能低,相當於在絕大多數手機上都是需要輔助ABI或動態轉碼來相容
方案二
:只適配 armwabi-v7a
同理方案一,只是又篩掉了一部分老舊裝置,在效能和相容二者中比較平衡
方案三:
只適配 arm64-v8
優點:
效能最佳缺點:
只能執行在arm64-v8
上,要放棄部分老舊裝置使用者
這三種方案都是可以的,現在的大廠APP適配中,這三種都有,大部分是前2種方案。具體選哪一種就看自己的考量了,以效能換相容就arm64-v8
,以相容換效能armeabi
,二者稍微平衡一點的就armwabi-v7a
。
目前來說,大多數的大廠APP用的都是armeabi
或armwabi-v7a
,只有像微信這種牛逼的APP,為了追求效能和使用者體驗,放棄了少部分裝置,這也說得通吧,畢竟微信也不在乎蒼蠅那點肉。
5.番外篇-效能+相容能否兼得?
其實到上一小節,本文就該結束了,但總感覺優點意猶未盡,除了適配所有全部CPU架構外,就特麼不能效能和相容同時兼得嗎?其實Google早有考慮。也是可以實現的那就是 abi split,分包,實現也很簡單,在gradle 中新增如下配置:
android {
...
splits {
// Configures multiple APKs based on ABI.
abi {
// Enables building multiple APKs per ABI.
enable true
// By default all ABIs are included, so use reset() and include to specify that we only
// want APKs for x86 and x86_64.
// Resets the list of ABIs that Gradle should create APKs for to none.
reset()
// Specifies a list of ABIs that Gradle should create APKs for.
include "x86", "x86_64", "arm64-v8a", "armeabi", "armeabi-v7a"
// Specifies that we do not want to also generate a universal APK that includes all ABIs.
universalApk false
}
}
}
然後,就能為每個CPU架構單獨打一個APK,該apk 中就只包含一個平臺,如下:
這樣,又能保證效能,又能不額外增加APK的大小,同時又又很完美的相容,因為可以為所有架構都單獨打一個包,一舉多得。
同時,Google Play 支援上傳多個APK:
這樣,就能根據不同的CPU架構,下載不同的包啦!
但是,但是,但是,很遺憾,國內的應用商店目前還不支援!
參考文章
以上就是本文的全部內容,如有錯誤,歡迎評論區指出。原創不易,如果你喜歡本文,歡迎點贊、轉發、收藏三連一下。