什么是Native Library?
Native Library,一般我们译为本地库或原生库,是由C/C++
编写的动态库(.so
),并通过JNI(Java Native Interface)
机制为java
层提供接口。应用一般会出于性能、安全等角度考虑将相关逻辑用C/C++
实现并编译为库的形式提供接口,供上层或其他模块调用。
为什么需要本地库(Native Library)的选择?
我们知道本地库(Native Library)是由C/C++
源码编译后的动态库文件,其编译是C/C++
源码根据其要运行的目标平台的指令集翻译成对应的机器码的过程,不同的处理器架构,需要编译出相应平台的动态库,才能被正确的执行。因此为了让app能在不同的处理器平台上都能正确的运行,一般app都会将其C/C++
源文件为常见平台(armeabi
,armeabi-v7a
等)都编译出相应的本地库文件,在打包成apk文件时,将这些不同平台的库都打包进apk的lib
子目录中,每一种abi
存放一个目录,目录名为abi
的类型名。因此,一个apk文件的lib子目录中就可能同时包含多种abi库文件情况,当一个用户在自己的设备上安装该app时,系统就需要根据自己的所支持的abi类型,为app选择适当abi类型的库文件进行安装,以确保app在系统上正常运行。
Android6.0,本地库(Native Library)的选择流程
上文提到本地库(Native Library)的选择流程是在app被安装的时候进行的,下面就以此为切入点,来看看在android6.0安装app时,是怎么为其选择合适的本地库(Native Library)的。
首先,我们来看一下PMS(PackageManagerService)
的scanPackageDirtyLI
函数,它是负责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来自安装参数指定,
pkg.cpuAbiOverrided的优先级高于pkgSetting.cpuAbiOverrideString,
只要pkg.cpuAbiOverrided不等于NativeLibraryHelper.CLEAR_ABI_OVERRIDE
*/
final String cpuAbiOverride = deriveAbiOverride(pkg.cpuAbiOverride, pkgSetting);
if ((scanFlags & SCAN_NEW_INSTALL) == 0) {
/* 这里执行abi的选择和本地库的安装操作,最后一个参数表示是否要将本地库抽取出来,这里为true,表示需要*/
derivePackageAbi(pkg, scanFile, cpuAbiOverride, true /* extract libs */);
}else{
....
}
....
}
可以看到在scanPackageDirtyLI
中主要是调用了derivePackageAbi
执行abi
的选择和安装操作,接下来我们看一下 derivePackageAbi
的实现:
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);//主要是初始化和设置与本地相关的几个变量,如本地库的目录等
....
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) {
//系统支持32位的abi
if (extractLibs) {
//表示需要为该app选择、**安装**合适的abi
abi32 = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
nativeLibraryRoot, Build.SUPPORTED_32_BIT_ABIS,
useIsaSpecificSubdirs);//调用NativeLibraryHelp的copyNativeBinariesForSupportedAbi进行abi的选择和安装
} else {
//只需选择(计算)出合适的abi即可,**不用执行安装**操作
abi32 = NativeLibraryHelper.findSupportedAbi(handle, Build.SUPPORTED_32_BIT_ABIS);//调用NativeLibraryHelp的findSupportedAbi进行abi的选择 --- 这是本文关注的重点
}
}
maybeThrowExceptionForMultiArchCopy(
"Error unpackaging 32 bit native libs for multiarch app.", abi32);
//系统支持64位的abi。流程和32位abi选择流程相同,只是将调用参数中的候选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);
//根据计算结果,对primaryCpuAbi和secondaryCpuAbi进行赋值,注意这里查找结果abi32,abi64分别是在Build.SUPPORTED_32_BIT_ABIS和Build.SUPPORTED_64_BIT_ABIS列表中的下标
/*赋值的规则如下:
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优先级高于Build.SUPPORTED_ABIS,
前者是根据安装参数和PackageSetting计算出来的,后者是系统编译时生成的
*/
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 {
//关闭apk文件
IoUtils.closeQuietly(handle);
}
// 计算出本地库,再一次更新本地库目录及其相关信息
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调用NativeLibraryHelper.Handle.create
* 打开的。因为存在Split APK机制,一个app有可能被拆分成几个apk情况,
* create函数会打开一个app所有的apk文件,并存放在apkHandles数组中
*/
//依次处理一个app的每一个apk文件
for (long apkHandle : handle.apkHandles) {
//直接调用nativeFindSupportedAbi对每一个apk进行本地库的选择
final int res = nativeFindSupportedAbi(apkHandle, supportedAbis);
if (res == NO_NATIVE_LIBRARIES) {
// 这个apk不存在本地库,什么也不做
} else if (res == INSTALL_FAILED_NO_MATCHING_ABIS) {
//apk存在本地库,但是不在系统支持的列表中,
//将返回值暂时先置为INSTALL_FAILED_NO_MATCHING_ABIS
if (finalRes < 0) {
finalRes = INSTALL_FAILED_NO_MATCHING_ABIS;
}
} else if (res >= 0) {
// apk存在本地库,且在系统支持的列表中,
// 如果其在系统支持列表的索引比之前apk找到的低,则更新返回值
if (finalRes < 0 || res < finalRes) {
finalRes = res;
}
} else {
// 未知错误,直接结束,返回相应的错误值
return res;
}
}
return finalRes;
}
总的来说findSupportedAbi
还是比较简单,依次对一个app的所有apk调用nativeFindSupportedAbi
来选择合适的本地库。 如果多个apk文件都找到了本地库,则根据其在supportedAbis
索引值,选择索引较小的。因此可以看出系统支持的本地库优先级,在supportedAbis
中是按高到低依次排列的。接下来我们就来看一下,对每一个apk进行本地库选择的nativeFindSupportedAbi
。
在NativeLibraryHelper
中,nativeFindSupportedAbi
的定义如下
private native static int nativeFindSupportedAbi(long handle, String[] supportedAbis);
是一个native函数,其定义在:
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
中调用的nativeFindSupportedAbi
在com_android_internal_content_NativeLibraryHelper.cpp
中定义的函数名为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);
}
该函数只是简单的调用findSupportedAbi
,本身没有做工作。我们接着看findSupportedAbi
的实现:
static int findSupportedAbi(JNIEnv *env, jlong apkHandle, jobjectArray supportedAbisArray) {
/*
* 先将java层的传入的系统支持abi类型列表(String[])转化为C++层的表示(VectorM<SCopedUtfChars),这样做的目的应该是为了便于后续字符串比较之类的操作更加方便。
*/
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)转化为ZipFileRO*
ZipFileRO* zipFile = reinterpret_cast<ZipFileRO*>(apkHandle);
if (zipFile == NULL) {
return INSTALL_FAILED_INVALID_APK;
}
/* 创建NativeLibrariesIterator迭代器,该迭代器主要做了以下封装:
* 1. 在打开的时候会自动打开apk文件的lib目录
* 2. next操作会自动迭代apk文件lib目录中的本地库文件,自动跳过非本地库文件,并更新相关变量。
*/
UniquePtr<NativeLibrariesIterator> it(NativeLibrariesIterator::create(zipFile));
if (it.get() == NULL) {
return INSTALL_FAILED_INVALID_APK;
}
ZipEntryRO entry = NULL;
int status = NO_NATIVE_LIBRARIES;
//通过迭代器的next函数,依次迭代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列表的索引小于已经找到的值或者之前还未找到合适的abi类型,则更新返回值
status = i;
}
}
}
}
//回收内存
for (int i = 0; i < numAbis; ++i) {
delete supportedAbis[i];
}
return status;
}
可以看到,真正的本地库的abi类型选择是在native层完成的,核心逻辑其实也挺简单:即查看apk文件中lib子目录,看其包含了哪些子目录,每个目录名即一种abi
类型;再查看这些abi
类型是否在系统支持的abi
列表改中,如果在则该abi
就是app在系统中将要使用的abi类型,如果同时存在多种类型满足的情况,则选取索引在候选abi列表中较小的那个。
总结一下要点
- 如果app设置了
FLAG_MULTIARCH
标志位(即,app要被载入其他app进程), 需要同时选择64位和32位的abi
,并根据结果,分别对primaryCpuAbi
和secondaryCpuAbi
赋值;否则,判断一下安装时是否指定了abi
类型,如果指定了,就以该值作为abi
候选列表,没有指定就用Build.SUPPORTED_ABIS
(它是一个String[]
类型,每个元素是一种abi
类型的名称,如armeabi,armeabi-V7A,arm64-V8A,X86
等),在系统编译时确定的。 - apk文件是zip文件类型,本地库文件就放在它的
lib/
子目录下,每一种abi
类型的本地库文件存放一个目录,目录名就是该abi
类型名。 - 一个app可能拆分成几个apk文件组成。在为app进行
abi
类型选择时,依次查看它每一个apk文件,选出最合适abi
类型,如果一个app不同apk文件选出的abi
类型不同,则为app选取在候选abi
列表中索引最小的那个。apk文件的最合适abi
类型的标准是:该abi
类型在abi
候选列表中,且索引最小。