更多干货,请关注微信公众号: tmac_lover
所谓的ABI全称是application binary interface,是一个机器语言级别的接口,描述的是二进制代码之间的兼容性,也就是说一起工作的二进制文件必须是ABI兼容的。
我们都知道Android现在支持的CPU架构大概有:ARMv5,ARMv7 (从2010年起),x86 (从2011年起),MIPS (从2012年起),ARMv8,MIPS64和x86_64这么多种,在Android系统中,上面的每一种CPU架构都关联着一个相应的ABI。如果某个app使用了.so文件,
那Android系统就必须要保证这个app进程所关联的ABI要和.so文件所依赖的ABI对应,否则这个app就可能会因为找不到需要的so文件而无法正常运行。今天这篇文章就来介绍一下Android系统是如何决定每个app进程以哪种ABI形式来启动的。
我们先来看几个和abi相关的系统property(以我自己的系统为例):
[ro.product.cpu.abilist] : [arm64-v8a, armeabi-v7a, armeabi]
[ro.product.cpu.abilist32] : [armeabi-v7a, armeabi]
[ro.product.cpu.abilist64] : [arm64-v8a]
下面这张图是Android系统启动一个新进程的流程图:
可以看到,Android系统中启动新的app进程都是通过socket机制通知zygote进程,然后由zogote进程启动新的app进程。图中有几个关键的函数:
startProcessLocked(ProcessRecord app, String hostingType,
String hostingNameStr, String abiOverride, String entryPoint, String[] entryPointArgs) {
... ...
String requiredAbi = (abiOverride != null) ? abiOverride : app.info.primaryCpuAbi;
if (requiredAbi == null) {
// Build.SUPPORTED_ABIS[0]的值就是ro.product.cpu.abilist这个property的值的第一项
requiredAbi = Build.SUPPORTED_ABIS[0];
}
app.requiredAbi = requiredAbi;
Process.ProcessStartResult startResult = Process.start(entryPoint,
app.processName, uid, uid, gids, debugFlags, mountExternal,
app.info.targetSdkVersion, app.info.seinfo, requiredAbi, instructionSet,
app.info.dataDir, entryPointArgs);
}
startProcessLocked方法里确定app进程的关联abi过程如下:
上面提到过app.info.primaryCpuAbi的值会对app进程最终的运行架构产生影响,那app.info.primaryCpuAbi的值又是在哪里确定的呢,答案就在PKMS(PackageManagerService)里。
在PKMS里有两处会对app.info.primaryCpuAbi的值产生影响,分别在scanPackageDirtyLI和adjustCpuAbisForSharedUserLPw两个方法里。
先看看scanPackageDirtyLI方法里和primaryCpuAbi相关的代码:
scanPackageDirtyLI() {
... ...
// 这个方法里会通过apk包里包含的so库的架构来决定app的primaryCpuAbi的值
derivePackageAbi(pkg, scanFile, cpuAbiOverride, true /* extract libs */);
if (isSystemApp(pkg) && !pkg.isUpdatedSystemApp() &&
pkg.applicationInfo.primaryCpuAbi == null) {
// 如果是system app,并且这个app没有通过上面的函数找到primaryCpuAbi的值
setBundledAppAbisAndRoots(pkg, pkgSetting);
// setNativeLibraryPaths方法会根据CpuAbi的值确定apk使用的so库的安装路径
setNativeLibraryPaths(pkg);
}
... ...
// 当前解析的apk是framework-res.apk, 对这个特殊的apk, 让它的ABI值的系统相同
// 在我这里,它就是arm64-v8a
if (mPlatformPackage == pkg) {
pkg.applicationInfo.primaryCpuAbi = VMRuntime.getRuntime().is64Bit() ?
Build.SUPPORTED_64_BIT_ABIS[0] : Build.SUPPORTED_32_BIT_ABIS[0];
}
}
从上面的这段代码可以看到:
接着先来看下derivePackageAbi()方法是如何确定primaryCpuAbi的值的:
public void derivePackageAbi(PackageParser.Package pkg, File scanFile,
String cpuAbiOverride, boolean extractLibs) {
// 这里会先设置一个默认的so库安装路径
setNativeLibraryPaths(pkg);
if (isMultiArch(pkg.applicationInfo)) {
// 这里处理的是支持两种abi的apk, 这种apk的AndroidManifest.xml里会设置android:multiarch为true
... ...
} else {
String[] abiList = (cpuAbiOverride != null) ?
new String[] { cpuAbiOverride } : Build.SUPPORTED_ABIS;
final int copyRet;
// 这是一个JNI函数,作用就是根据apk包里的lib/目录下的.so的ABI确定返回值
copyRet = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
nativeLibraryRoot, abiList, useIsaSpecificSubdirs);
// 根据copyRet的值,确定当前app的primaryCpuAbi值
if (copyRet >= 0) {
pkg.applicationInfo.primaryCpuAbi = abiList[copyRet];
} else if (copyRet == PackageManager.NO_NATIVE_LIBRARIES && cpuAbiOverride != null) {
pkg.applicationInfo.primaryCpuAbi = cpuAbiOverride;
} else if (needsRenderScriptOverride) {
pkg.applicationInfo.primaryCpuAbi = abiList[0];
}
}
// 到这里有一些app已经确定了primaryCpuAbi的值,所以再调一次这个函数,更新它使用的.so库的安装位置
setNativeLibraryPaths(pkg);
}
通过这段代码会可以看出:
接下来看下系统app是如何通过setBundledAppAbisAndRoots()方法来确定primaryCpuAbi的值的:
private void setBundledAppAbisAndRoots(PackageParser.Package pkg,
PackageSetting pkgSetting) {
final String apkName = deriveCodePathName(pkg.applicationInfo.getCodePath());
final String apkRoot = calculateBundledApkRoot(pkg.applicationInfo.sourceDir);
// 使用setBundledAppAbi()方法确定primaryCpuAbi值
setBundledAppAbi(pkg, apkRoot, apkName);
if (pkgSetting != null) {
pkgSetting.primaryCpuAbiString = pkg.applicationInfo.primaryCpuAbi;
pkgSetting.secondaryCpuAbiString = pkg.applicationInfo.secondaryCpuAbi;
}
}
private static void setBundledAppAbi(PackageParser.Package pkg, String apkRoot, String apkName) {
final File codeFile = new File(pkg.codePath);
final boolean has64BitLibs;
final boolean has32BitLibs;
if (isApkFile(codeFile)) {
// 只有framework-res.apk这个包会进这个if分支,has64BitLibs和has32BitLibs的值都是false
// 在前面scanPackageDirtyLI里有说过,这个app的primaryCpuAbi的值是arm64-v8a
has64BitLibs = (new File(apkRoot, new File(LIB64_DIR_NAME, apkName).getPath())).exists();
has32BitLibs = (new File(apkRoot, new File(LIB_DIR_NAME, apkName).getPath())).exists();
} else {
// 对于其它的app, codeFile是apk所在的路径
final File rootDir = new File(codeFile, LIB_DIR_NAME);
final String isa = VMRuntime.getInstructionSet(Build.SUPPORTED_64_BIT_ABIS[0]);
// 通过判断/system/app/${APP_NAME}/lib64这个文件夹是否存在决定has64BitLibs的值
has64BitLibs = (new File(rootDir, isa)).exists();
final String isa = VMRuntime.getInstructionSet(Build.SUPPORTED_32_BIT_ABIS[0]);
// 通过判断/system/app/${APP_NAME}/lib这个文件夹是否存在决定has32BitLibs的值
has32BitLibs = (new File(rootDir, isa)).exists();
}
// 下面这一段会根据has64BitLibs和has32BitLibs的值来确定app的primaryCpuAbi的值
if (has64BitLibs && !has32BitLibs) {
pkg.applicationInfo.primaryCpuAbi = Build.SUPPORTED_64_BIT_ABIS[0];
pkg.applicationInfo.secondaryCpuAbi = null;
} else if (has32BitLibs && !has64BitLibs) {
pkg.applicationInfo.primaryCpuAbi = Build.SUPPORTED_32_BIT_ABIS[0];
pkg.applicationInfo.secondaryCpuAbi = null;
} else if (has32BitLibs && has64BitLibs) {
if (VMRuntime.is64BitInstructionSet(getPreferredInstructionSet())) {
pkg.applicationInfo.primaryCpuAbi = Build.SUPPORTED_64_BIT_ABIS[0];
pkg.applicationInfo.secondaryCpuAbi = Build.SUPPORTED_32_BIT_ABIS[0];
} else {
pkg.applicationInfo.primaryCpuAbi = Build.SUPPORTED_32_BIT_ABIS[0];
pkg.applicationInfo.secondaryCpuAbi = Build.SUPPORTED_64_BIT_ABIS[0];
}
} else {
pkg.applicationInfo.primaryCpuAbi = null;
pkg.applicationInfo.secondaryCpuAbi = null;
}
}
根据上面的代码,可以知道:
所以在经过scanPackageDirtyLI()方法之后,会存在以下四种情况:
先来看下adjustCpuAbisForSharedUserLPw的调用位置,在PKMS的构造函数里:
public PackageManagerService(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
... ...
// 逐个解析系统里的所有apk文件,上一节中的内容,都在这里完成
scanDirLI();
... ...
// 当所有的apk文件解析完之后,对使用了相同UID的apk, 调用adjustCpuAbisForSharedUserLPw
for (SharedUserSetting setting : mSettings.getAllSharedUsersLPw()) {
// setting.packages是所有使用相同UID的apk的集合
adjustCpuAbisForSharedUserLPw(setting.packages, null /* scanned package */,
false /* force dexopt */, false /* defer dexopt */);
}
... ...
}
private void adjustCpuAbisForSharedUserLPw(Set packagesForUser,
PackageParser.Package scannedPackage, boolean forceDexOpt, boolean deferDexOpt) {
String requiredInstructionSet = null;
... ...
PackageSetting requirer = null;
for (PackageSetting ps : packagesForUser) {
if (scannedPackage == null || !scannedPackage.packageName.equals(ps.name)) {
if (ps.primaryCpuAbiString == null) {
continue;
}
// 这个for循环的作用就是遍历所有使用相同UID的package,把遍历过程中遇到的第一个确定primaryCpuAbi
// 的那个package取出来,保存到requirer中
final String instructionSet = VMRuntime.getInstructionSet(ps.primaryCpuAbiString);
if (requiredInstructionSet == null) {
// 只取第一个被遍历到的
requiredInstructionSet = instructionSet;
requirer = ps;
}
}
}
if (requiredInstructionSet != null) {
String adjustedAbi;
if (requirer != null) {
// 证明在这个集合中找到了已经确定primaryCpuAbi的那个package
adjustedAbi = requirer.primaryCpuAbiString;
} else {
// scannedPackage == null时,这种情况不存在,所以不考虑这里
}
for (PackageSetting ps : packagesForUser) {
if (scannedPackage == null || !scannedPackage.packageName.equals(ps.name)) {
if (ps.primaryCpuAbiString != null) {
continue;
}
// 将adjustedAbi的值给那些使用同一个UID并且primaryCpuAbi是空的package
ps.primaryCpuAbiString = adjustedAbi;
if (ps.pkg != null && ps.pkg.applicationInfo != null) {
ps.pkg.applicationInfo.primaryCpuAbi = adjustedAbi;
... ...
}
}
}
}
}
这段代码的作用就是调整使用相同UID的package的primaryCpuAbi的值,将那些还没有确定primaryCpuAbi的package用已经确定了的Abi的值代替。这里将是那些没有确定primaryCpuAbi的apk
再次确定abi值的最后一次机会,如果在这里还无法确定,那就在启动进程时,使用系统默认值。
最后来总结一下Android系统确定app进程关联哪种ABI的流程: