什么是Native Library?
Native Library,一般我們譯為本地庫或原生庫,是由C/C++編寫的動態(tài)庫(.so),并通過JNI(Java Native Interface)機制為java層提供接口。應用一般會出于性能、安全等角度考慮將相關(guān)邏輯用C/C++實現(xiàn)并編譯為庫的形式提供接口,供上層或其他模塊調(diào)用。
為什么需要本地庫(Native Library)的選擇?
我們知道本地庫(Native Library)是由C/C++源碼編譯后的動態(tài)庫文件,其編譯是C/C++源碼根據(jù)其要運行的目標平臺的指令集翻譯成對應的機器碼的過程,不同的處理器架構(gòu),需要編譯出相應平臺的動態(tài)庫,才能被正確的執(zhí)行。因此為了讓app能在不同的處理器平臺上都能正確的運行,一般app都會將其C/C++源文件為常見平臺(armeabi,armeabi-v7a等)都編譯出相應的本地庫文件,在打包成apk文件時,將這些不同平臺的庫都打包進apk的lib子目錄中,每一種abi存放一個目錄,目錄名為abi的類型名。因此,一個apk文件的lib子目錄中就可能同時包含多種abi庫文件情況,當一個用戶在自己的設備上安裝該app時,系統(tǒng)就需要根據(jù)自己的所支持的abi類型,為app選擇適當abi類型的庫文件進行安裝,以確保app在系統(tǒng)上正常運行。
Android6.0,本地庫(Native Library)的選擇流程
上文提到本地庫(Native Library)的選擇流程是在app被安裝的時候進行的,下面就以此為切入點,來看看在android6.0安裝app時,是怎么為其選擇合適的本地庫(Native Library)的。
首先,我們來看一下PMS(PackageManagerService)的scanPackageDirtyLI函數(shù),它是負責app安裝時簽名驗證、本地庫(Native Library)的選擇安裝等系列工作的,這里我們重點看一下本地庫(Native Library)的選擇流程:
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg, int parseFlags, int scanFlags, long currentTime, UserHandle user) throw PackageManagerException{
....
/*計算cpuOverride的值,pkg.cpuAbiOverride來自安裝參數(shù)指定,
pkg.cpuAbiOverrided的優(yōu)先級高于pkgSetting.cpuAbiOverrideString,
只要pkg.cpuAbiOverrided不等于NativeLibraryHelper.CLEAR_ABI_OVERRIDE
*/
final String cpuAbiOverride = deriveAbiOverride(pkg.cpuAbiOverride, pkgSetting);
if ((scanFlags & SCAN_NEW_INSTALL) == 0) {
/* 這里執(zhí)行abi的選擇和本地庫的安裝操作,最后一個參數(shù)表示是否要將本地庫抽取出來,這里為true,表示需要*/
derivePackageAbi(pkg, scanFile, cpuAbiOverride, true /* extract libs */);
}else{
....
}
....
}
可以看到在scanPackageDirtyLI中主要是調(diào)用了derivePackageAbi執(zhí)行abi的選擇和安裝操作,接下來我們看一下 derivePackageAbi的實現(xiàn):
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
public void derivePackageAbi(PackageParser.Package pkg, File scanFile,
String cpuAbiOverride, boolean extractLibs)
throws PackageManagerException {
setNativeLibraryPaths(pkg);//主要是初始化和設置與本地相關(guān)的幾個變量,如本地庫的目錄等
....
NativeLibraryHelper.Handle handle = null;
try {
handle = NativeLibraryHelper.Handle.create(scanFile); //打開apk文件
final File nativeLibraryRoot = new File(nativeLibraryRootStr);
// 將primaryCpuAbi和secondaryCpui都先置為null,以便于后面重新計算abi
pkg.applicationInfo.primaryCpuAbi = null;
pkg.applicationInfo.secondaryCpuAbi = null;
if (isMultiArch(pkg.applicationInfo)) {
// pkg.applicationInfo.pkgFlags設置了FLAG_MULTIARCH標志位,
//即表示該app的代碼會被其他app進程加載,因此就可能需要同時安裝32位和64位的本地庫
// Warn if we've set an abiOverride for multi-lib packages..
// By definition, we need to copy both 32 and 64 bit libraries for
// such packages.
...
int abi32 = PackageManager.NO_NATIVE_LIBRARIES;
int abi64 = PackageManager.NO_NATIVE_LIBRARIES;
if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
//系統(tǒng)支持32位的abi
if (extractLibs) {
//表示需要為該app選擇、**安裝**合適的abi
abi32 = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
nativeLibraryRoot, Build.SUPPORTED_32_BIT_ABIS,
useIsaSpecificSubdirs);//調(diào)用NativeLibraryHelp的copyNativeBinariesForSupportedAbi進行abi的選擇和安裝
} else {
//只需選擇(計算)出合適的abi即可,**不用執(zhí)行安裝**操作
abi32 = NativeLibraryHelper.findSupportedAbi(handle, Build.SUPPORTED_32_BIT_ABIS);//調(diào)用NativeLibraryHelp的findSupportedAbi進行abi的選擇 --- 這是本文關(guān)注的重點
}
}
maybeThrowExceptionForMultiArchCopy(
"Error unpackaging 32 bit native libs for multiarch app.", abi32);
//系統(tǒng)支持64位的abi。流程和32位abi選擇流程相同,只是將調(diào)用參數(shù)中的候選abi列表改為了64位的,
//即Build.SUPPORTED_32_BIT_ABIS改為Build.SUPPORTED_64_BIT_ABIS
if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
if (extractLibs) {
abi64 = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
nativeLibraryRoot, Build.SUPPORTED_64_BIT_ABIS,
useIsaSpecificSubdirs);
} else {
abi64 = NativeLibraryHelper.findSupportedAbi(handle, Build.SUPPORTED_64_BIT_ABIS);
}
}
maybeThrowExceptionForMultiArchCopy(
"Error unpackaging 64 bit native libs for multiarch app.", abi64);
//根據(jù)計算結(jié)果,對primaryCpuAbi和secondaryCpuAbi進行賦值,注意這里查找結(jié)果abi32,abi64分別是在Build.SUPPORTED_32_BIT_ABIS和Build.SUPPORTED_64_BIT_ABIS列表中的下標
/*賦值的規(guī)則如下:
1. 如果同時找到64位和32位的abi,則primaryCpuAbi設為64位的,secondaryCpuAbi設為32位的
2. 只找到64位但是沒有找到32位的abi,則primaryCpuAbi設為64位的,secondaryCpuAbi保持為null
3. 如果沒有找到64位但是找到了32位合適的abi,primaryCpuAbi設為32為的abi,secondaryCpuabi保持為null
4. 64位和32位的abi都沒找到,則primaryCpuAbi和secondaryCpuAbi都保持為null
*/
if (abi64 >= 0) {
pkg.applicationInfo.primaryCpuAbi = Build.SUPPORTED_64_BIT_ABIS[abi64];
}
if (abi32 >= 0) {
final String abi = Build.SUPPORTED_32_BIT_ABIS[abi32];
if (abi64 >= 0) {
pkg.applicationInfo.secondaryCpuAbi = abi;
} else {
pkg.applicationInfo.primaryCpuAbi = abi;
}
}
} else {
//app的代碼不會被其他app進程載入
/*
初始化候選abi列表:abiList。cpuAbiOverride優(yōu)先級高于Build.SUPPORTED_ABIS,
前者是根據(jù)安裝參數(shù)和PackageSetting計算出來的,后者是系統(tǒng)編譯時生成的
*/
String[] abiList = (cpuAbiOverride != null) ?
new String[] { cpuAbiOverride } : Build.SUPPORTED_ABIS;
...
//以下邏輯與if語句中的邏輯相同,copyRet存儲合適的abi在候選abi列表(abiList)中的下標
final int copyRet;
if (extractLibs) {
copyRet = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
nativeLibraryRoot, abiList, useIsaSpecificSubdirs);
} else {
copyRet = NativeLibraryHelper.findSupportedAbi(handle, abiList);
}
if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES) {
throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
"Error unpackaging native libs for app, errorCode=" + copyRet);
}
//對primaryCpuAbi進行賦值
if (copyRet >= 0) {
pkg.applicationInfo.primaryCpuAbi = abiList[copyRet];
} else if (copyRet == PackageManager.NO_NATIVE_LIBRARIES && cpuAbiOverride != null) {
//NativeLibraryHelper沒找到合適的abi,但是cpuAbiOverride不為null,則primaryCpuAbi設為cpuAbiOverride
pkg.applicationInfo.primaryCpuAbi = cpuAbiOverride;
} else if (needsRenderScriptOverride) {
pkg.applicationInfo.primaryCpuAbi = abiList[0];
}
}
} catch (IOException ioe) {
Slog.e(TAG, "Unable to get canonical file " + ioe.toString());
} finally {
//關(guān)閉apk文件
IoUtils.closeQuietly(handle);
}
// 計算出本地庫,再一次更新本地庫目錄及其相關(guān)信息
setNativeLibraryPaths(pkg);
}
接下來我們就到NativeLibraryHelper的findSupportedAbi中看看,具體是怎么進行abi選擇的:
frameworks/base/core/java/com/android/internal/content/NativeLibraryHelper.java
public static int findSupportedAbi(Handle handle, String[] supportedAbis) {
int finalRes = NO_NATIVE_LIBRARIES;
/*handle為上文中PMS中derivePackageAbi調(diào)用NativeLibraryHelper.Handle.create
* 打開的。因為存在Split APK機制,一個app有可能被拆分成幾個apk情況,
* create函數(shù)會打開一個app所有的apk文件,并存放在apkHandles數(shù)組中
*/
//依次處理一個app的每一個apk文件
for (long apkHandle : handle.apkHandles) {
//直接調(diào)用nativeFindSupportedAbi對每一個apk進行本地庫的選擇
final int res = nativeFindSupportedAbi(apkHandle, supportedAbis);
if (res == NO_NATIVE_LIBRARIES) {
// 這個apk不存在本地庫,什么也不做
} else if (res == INSTALL_FAILED_NO_MATCHING_ABIS) {
//apk存在本地庫,但是不在系統(tǒng)支持的列表中,
//將返回值暫時先置為INSTALL_FAILED_NO_MATCHING_ABIS
if (finalRes < 0) {
finalRes = INSTALL_FAILED_NO_MATCHING_ABIS;
}
} else if (res >= 0) {
// apk存在本地庫,且在系統(tǒng)支持的列表中,
// 如果其在系統(tǒng)支持列表的索引比之前apk找到的低,則更新返回值
if (finalRes < 0 || res < finalRes) {
finalRes = res;
}
} else {
// 未知錯誤,直接結(jié)束,返回相應的錯誤值
return res;
}
}
return finalRes;
}
總的來說findSupportedAbi還是比較簡單,依次對一個app的所有apk調(diào)用nativeFindSupportedAbi來選擇合適的本地庫。 如果多個apk文件都找到了本地庫,則根據(jù)其在supportedAbis索引值,選擇索引較小的。因此可以看出系統(tǒng)支持的本地庫優(yōu)先級,在supportedAbis中是按高到低依次排列的。接下來我們就來看一下,對每一個apk進行本地庫選擇的nativeFindSupportedAbi。
在NativeLibraryHelper中,nativeFindSupportedAbi的定義如下
private native static int nativeFindSupportedAbi(long handle, String[] supportedAbis);
是一個native函數(shù),其定義在:
frameworks/base/core/java/jni/com_android_internal_content_NativeLibraryHelper.cpp
其中JNINativeMethod定義如下:
static JNINativeMethod gMethods[] = {
{
...
{"nativeFindSupportedAbi",
"(J[Ljava/lang/String;)I",
(void *)com_android_internal_content_NativeLibraryHelper_findSupportedAbi},
...
};
可見NativeLibraryHelper.java中調(diào)用的nativeFindSupportedAbi在com_android_internal_content_NativeLibraryHelper.cpp中定義的函數(shù)名為com_android_internal_content_NativeLibraryHelper_findSupportedAbi,其定義如下:
static jint
com_android_internal_content_NativeLibraryHelper_findSupportedAbi(JNIEnv *env, jclass clazz, jlong apkHandle, jobjectArray javaCpuAbisToSearch)
{
return (jint) findSupportedAbi(env, apkHandle, javaCpuAbisToSearch);
}
該函數(shù)只是簡單的調(diào)用findSupportedAbi,本身沒有做工作。我們接著看findSupportedAbi的實現(xiàn):
static int findSupportedAbi(JNIEnv *env, jlong apkHandle, jobjectArray supportedAbisArray) {
/*
* 先將java層的傳入的系統(tǒng)支持abi類型列表(String[])轉(zhuǎn)化為C++層的表示(VectorM<SCopedUtfChars),這樣做的目的應該是為了便于后續(xù)字符串比較之類的操作更加方便。
*/
const int numAbis = env->GetArrayLength(supportedAbisArray);
Vector<ScopedUtfChars*> supportedAbis;
for (int i = 0; i < numAbis; ++i) {
supportedAbis.add(new ScopedUtfChars(env,
(jstring) env->GetObjectArrayElement(supportedAbisArray, i)));
}
//將java層NativeLibraryHelp.java中findSupportedAbi傳入的apkHandle(long)轉(zhuǎn)化為ZipFileRO*
ZipFileRO* zipFile = reinterpret_cast<ZipFileRO*>(apkHandle);
if (zipFile == NULL) {
return INSTALL_FAILED_INVALID_APK;
}
/* 創(chuàng)建NativeLibrariesIterator迭代器,該迭代器主要做了以下封裝:
* 1. 在打開的時候會自動打開apk文件的lib目錄
* 2. next操作會自動迭代apk文件lib目錄中的本地庫文件,自動跳過非本地庫文件,并更新相關(guān)變量。
*/
UniquePtr<NativeLibrariesIterator> it(NativeLibrariesIterator::create(zipFile));
if (it.get() == NULL) {
return INSTALL_FAILED_INVALID_APK;
}
ZipEntryRO entry = NULL;
int status = NO_NATIVE_LIBRARIES;
//通過迭代器的next函數(shù),依次迭代lib目錄下的本地庫文件
while ((entry = it->next()) != NULL) {
//找到了本地庫,且初始化后未被修改過,則將返回值暫且設為INSTALL_FAILED_NO_MATCHING_ABIS
if (status == NO_NATIVE_LIBRARIES) {
status = INSTALL_FAILED_NO_MATCHING_ABIS;
}
const char* fileName = it->currentEntry();//文件名,包括相對路徑的目錄名,如lib/armeabi/libxx.so
const char* lastSlash = it->lastSlash();//路徑中,最后一個`/`的位置
// 分別獲取子目錄名的起始地址和目錄名長度-->這個目錄名就是這個本地庫文件的abi類型
const char* abiOffset = fileName + APK_LIB_LEN;
const size_t abiSize = lastSlash - abiOffset;
//檢查候選abi列表是否包含該abi類型
for (int i = 0; i < numAbis; i++) {
const ScopedUtfChars* abi = supportedAbis[i];
if (abi->size() == abiSize && !strncmp(abiOffset, abi->c_str(), abiSize)) {
//該本地庫文件的abi類型在候選abi列表中
if (((i < status) && (status >= 0)) || (status < 0) ) {
//該abi類型在候選abi列表的索引小于已經(jīng)找到的值或者之前還未找到合適的abi類型,則更新返回值
status = i;
}
}
}
}
//回收內(nèi)存
for (int i = 0; i < numAbis; ++i) {
delete supportedAbis[i];
}
return status;
}
可以看到,真正的本地庫的abi類型選擇是在native層完成的,核心邏輯其實也挺簡單:即查看apk文件中l(wèi)ib子目錄,看其包含了哪些子目錄,每個目錄名即一種abi類型;再查看這些abi類型是否在系統(tǒng)支持的abi列表改中,如果在則該abi就是app在系統(tǒng)中將要使用的abi類型,如果同時存在多種類型滿足的情況,則選取索引在候選abi列表中較小的那個。
總結(jié)一下要點
- 如果app設置了
FLAG_MULTIARCH標志位(即,app要被載入其他app進程), 需要同時選擇64位和32位的abi,并根據(jù)結(jié)果,分別對primaryCpuAbi和secondaryCpuAbi賦值;否則,判斷一下安裝時是否指定了abi類型,如果指定了,就以該值作為abi候選列表,沒有指定就用Build.SUPPORTED_ABIS(它是一個String[]類型,每個元素是一種abi類型的名稱,如armeabi,armeabi-V7A,arm64-V8A,X86等),在系統(tǒng)編譯時確定的。 - apk文件是zip文件類型,本地庫文件就放在它的
lib/子目錄下,每一種abi類型的本地庫文件存放一個目錄,目錄名就是該abi類型名。 - 一個app可能拆分成幾個apk文件組成。在為app進行
abi類型選擇時,依次查看它每一個apk文件,選出最合適abi類型,如果一個app不同apk文件選出的abi類型不同,則為app選取在候選abi列表中索引最小的那個。apk文件的最合適abi類型的標準是:該abi類型在abi候選列表中,且索引最小。