ABI确定过程

ABI确定过程

之所以了解这块内容,是因为在项目中出现了一个奇怪的问题:64位的代码编译刷机后,多个app无法运行,点击后直接闪退,log提示是找不到dex文件。问题非必现,但是第一次启动后如果存在该问题,之后就会一直存在该问题。但是恢复出厂设置和重新刷机后问题可能会消失。

经人提示是32和62位相关的东西引起的,看log发现出问题的apk都是64位的,但是查找的路径是32位的,所以找不到。

问题的log如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
01-01 12:00:00.869  1344  1344 E AndroidRuntime: FATAL EXCEPTION: main
01-01 12:00:00.869 1344 1344 E AndroidRuntime: Process: com.android.deskclock, PID: 1344
01-01 12:00:00.869 1344 1344 E AndroidRuntime: java.lang.RuntimeException: Unable to instantiate application com.android.deskclock.DeskClockApplication: java.lang.ClassNotFoundException: Didn't find class "com.android.deskclock.DeskClockApplication" on path: DexPathList[[zip file "/system/app/ApeDeskClock80/ApeDeskClock80.apk"],nativeLibraryDirectories=[/system/app/ApeDeskClock80/lib/arm64, /system/app/ApeDeskClock80/ApeDeskClock80.apk!/lib/armeabi, /system/lib, /vendor/lib, /system/lib, /vendor/lib]]
01-01 12:00:00.869 1344 1344 E AndroidRuntime: at android.app.LoadedApk.makeApplication(LoadedApk.java:999)
01-01 12:00:00.869 1344 1344 E AndroidRuntime: at android.app.ActivityThread.handleBindApplication(ActivityThread.java:5742)
01-01 12:00:00.869 1344 1344 E AndroidRuntime: at android.app.ActivityThread.-wrap1(Unknown Source:0)
01-01 12:00:00.869 1344 1344 E AndroidRuntime: at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1680)
01-01 12:00:00.869 1344 1344 E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:106)
01-01 12:00:00.869 1344 1344 E AndroidRuntime: at android.os.Looper.loop(Looper.java:164)
01-01 12:00:00.869 1344 1344 E AndroidRuntime: at android.app.ActivityThread.main(ActivityThread.java:6523)
01-01 12:00:00.869 1344 1344 E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method)
01-01 12:00:00.869 1344 1344 E AndroidRuntime: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
01-01 12:00:00.869 1344 1344 E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:857)
01-01 12:00:00.869 1344 1344 E AndroidRuntime: Caused by: java.lang.ClassNotFoundException: Didn't find class "com.android.deskclock.DeskClockApplication" on path: DexPathList[[zip file "/system/app/ApeDeskClock80/ApeDeskClock80.apk"],nativeLibraryDirectories=[/system/app/ApeDeskClock80/lib/arm64, /system/app/ApeDeskClock80/ApeDeskClock80.apk!/lib/armeabi, /system/lib, /vendor/lib, /system/lib, /vendor/lib]]
01-01 12:00:00.869 1344 1344 E AndroidRuntime: at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:125)
01-01 12:00:00.869 1344 1344 E AndroidRuntime: at java.lang.ClassLoader.loadClass(ClassLoader.java:379)
01-01 12:00:00.869 1344 1344 E AndroidRuntime: at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
01-01 12:00:00.869 1344 1344 E AndroidRuntime: at android.app.Instrumentation.newApplication(Instrumentation.java:1087)
01-01 12:00:00.869 1344 1344 E AndroidRuntime: at android.app.LoadedApk.makeApplication(LoadedApk.java:993)
01-01 12:00:00.869 1344 1344 E AndroidRuntime: ... 9 more
01-01 12:00:00.869 1344 1344 E AndroidRuntime: Suppressed: java.io.IOException: No original dex files found for dex location /system/app/ApeDeskClock80/ApeDeskClock80.apk
01-01 12:00:00.869 1344 1344 E AndroidRuntime: at dalvik.system.DexFile.openDexFileNative(Native Method)
01-01 12:00:00.869 1344 1344 E AndroidRuntime: at dalvik.system.DexFile.openDexFile(DexFile.java:353)
01-01 12:00:00.869 1344 1344 E AndroidRuntime: at dalvik.system.DexFile.<init>(DexFile.java:100)
01-01 12:00:00.869 1344 1344 E AndroidRuntime: at dalvik.system.DexFile.<init>(DexFile.java:74)
01-01 12:00:00.869 1344 1344 E AndroidRuntime: at dalvik.system.DexPathList.loadDexFile(DexPathList.java:374)
01-01 12:00:00.869 1344 1344 E AndroidRuntime: at dalvik.system.DexPathList.makeDexElements(DexPathList.java:337)
01-01 12:00:00.869 1344 1344 E AndroidRuntime: at dalvik.system.DexPathList.<init>(DexPathList.java:157)
01-01 12:00:00.869 1344 1344 E AndroidRuntime: at dalvik.system.BaseDexClassLoader.<init>(BaseDexClassLoader.java:65)
01-01 12:00:00.869 1344 1344 E AndroidRuntime: at dalvik.system.PathClassLoader.<init>(PathClassLoader.java:64)
01-01 12:00:00.869 1344 1344 E AndroidRuntime: at com.android.internal.os.ClassLoaderFactory.createClassLoader(ClassLoaderFactory.java:73)
01-01 12:00:00.869 1344 1344 E AndroidRuntime: at com.android.internal.os.ClassLoaderFactory.createClassLoader(ClassLoaderFactory.java:88)
01-01 12:00:00.869 1344 1344 E AndroidRuntime: at android.app.ApplicationLoaders.getClassLoader(ApplicationLoaders.java:69)
01-01 12:00:00.869 1344 1344 E AndroidRuntime: at android.app.ApplicationLoaders.getClassLoader(ApplicationLoaders.java:35)
01-01 12:00:00.869 1344 1344 E AndroidRuntime: at android.app.LoadedApk.createOrUpdateClassLoaderLocked(LoadedApk.java:703)
01-01 12:00:00.869 1344 1344 E AndroidRuntime: at android.app.LoadedApk.getClassLoader(LoadedApk.java:737)
01-01 12:00:00.869 1344 1344 E AndroidRuntime: at android.app.LoadedApk.getResources(LoadedApk.java:964)
01-01 12:00:00.869 1344 1344 E AndroidRuntime: at android.app.ContextImpl.createAppContext(ContextImpl.java:2271)
01-01 12:00:00.869 1344 1344 E AndroidRuntime: at android.app.ActivityThread.handleBindApplication(ActivityThread.java:5666)
01-01 12:00:00.869 1344 1344 E AndroidRuntime: ... 8 more

……
01-01 12:16:15.024 800 800 W PackageManager: Instruction set mismatch, PackageSetting{2aef961 com.ape.factory/1000} requires arm whereas PackageSetting{324db9c factory.tinnosnew.tinno.ui.test/1000} requires arm64
01-01 12:16:15.024 800 800 W PackageManager: Instruction set mismatch, PackageSetting{2aef961 com.ape.factory/1000} requires arm whereas PackageSetting{8fef249 com.android.settings/1000} requires arm64

刚开始无从下手,先百度。然后找到两篇博客和这个问题一模一样。

https://blog.csdn.net/buding_code/article/details/54633048

https://blog.csdn.net/qq_37610155/article/details/78607276

问题原因都是同一个uid的app有的是32位的有的是64位的,uid都是1000 ,也就是是android.uid.system。从问题的现象可以推断该问题和第一次启动时的某些操作有关,另外和同uid的32位应用有关。根据这两篇博客在log中查找关键词Instruction set mismatch,找到32应用为com.ape.factory。问题点找到了,根据“Instruction set mismatch”的log可以追到PKMS中的adjustCpuAbisForSharedUserLPw方法,从方法名看是用来调整相同uid应用的cpuAbi的,那就来了解下ABI是什么,以及为什么会出现这个问题。

以下关于ABI确定过程的内容来自:https://www.2cto.com/kf/201709/687437.html。


所谓的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形式来启动的。

1. abi相关property

我们先来看几个和abi相关的系统property(以我自己的系统为例):

1
2
3
[ro.product.cpu.abilist] : [arm64-v8a, armeabi-v7a, armeabi]
[ro.product.cpu.abilist32] : [armeabi-v7a, armeabi]
[ro.product.cpu.abilist64] : [arm64-v8a]

ro.product.cpu.abilist的值表明当前系统所支持所有的ABI类型 ro.product.cpu.abilist32和ro.product.cpu.abilist64分别表示系统所支持的32位和64位的ABI类型。 需要注意的是,这些property的排序代表着ABI的优先级,比如ro.product.cpu.abilist的值里arm64-v8a排在第一个,就表明如果没有指定,arm64-v8a就会成为app进程默认启动的关联ABI。

2. app进程启动流程

下面这张图是Android系统启动一个新进程的流程图:

可以看到,Android系统中启动新的app进程都是通过socket机制通知zygote进程,然后由zogote进程启动新的app进程。图中有几个关键的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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过程如下:

如果abiOverride非空的话,就使用abiOverride的值,否则使用app.info.primaryCpuAbi的值 如果app.info.primaryCpuAbi也为空,则使用ro.product.cpu.abilist这个property的值的第一项 关于abiOverride的值,其实我也并不知道它是哪里来的,但是一般情况下,这个值都是空的.

3. primaryCpuAbi值的确定

上面提到过app.info.primaryCpuAbi的值会对app进程最终的运行架构产生影响,那app.info.primaryCpuAbi的值又是在哪里确定的呢,答案就在PKMS(PackageManagerService)里。

在PKMS里有两处会对app.info.primaryCpuAbi的值产生影响,分别在scanPackageDirtyLI和adjustCpuAbisForSharedUserLPw两个方法里。

3.1 非第一次开机,直接读取

如果手机不是第一次开机或者升级,则应用的ABI直接从packages.xml中读取。

1
2
3
4
5
6
7
if ((scanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) == 0) {
PackageSetting foundPs = mSettings.getPackageLPr(pkg.packageName);
if (foundPs != null) {
primaryCpuAbiFromSettings = foundPs.primaryCpuAbiString;
secondaryCpuAbiFromSettings = foundPs.secondaryCpuAbiString;
}
}

3.2 scanPackageDirtyLI

先看看scanPackageDirtyLI方法里和primaryCpuAbi相关的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
scanPackageDirtyLI() {
... ...

// 这个方法里会通过apk包里包含的so库的架构来决定app的primaryCpuAbi的值
derivePackageAbi(pkg, scanFile, cpuAbiOverride, extractNativeLibs,
mAppLib32InstallDir);

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];
}
}

从上面的这段代码可以看到:

对所有的app,会先通过derivePackageAbi()方法尝试确定app的primaryCpuAbi的值 如果是system app, 并且通过derivePackageAbi()方法没有确定primaryCpuAbi的值,会再尝试通过setBundledAppAbisAndRoots()方法来确定 需要注意的是,无论是第三方app还是系统app, 运行完这段代码之后,仍然存在primaryCpuAbi值为空的情况,这是正常现象.

接着先来看下derivePackageAbi()方法是如何确定primaryCpuAbi的值的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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);
}

通过这段代码会可以看出:

一些apk包里lib目录下有.so文件的,可以通过.so文件的ABI来确定app的primaryCpuAbi的值 对于那些lib下没有.so文件的apk, 比如不使用so库的或者是系统app,运行完这个方法之后,primaryCpuAbi的值仍然是空

接下来看下系统app是如何通过setBundledAppAbisAndRoots()方法来确定primaryCpuAbi的值的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
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;
}
}

根据上面的代码,可以知道:

对系统app而言,根据/system/app/${APP_NAME}/lib和/system/app/${APP_NAME}/lib64这两个文件夹是否存在,来确定它的primaryCpuAbi的值 当然,如果系统app不存在上述两个文件夹,那它的primaryCpuAbi的值仍然为空

所以在经过scanPackageDirtyLI()方法之后,会存在以下四种情况:

无论是系统app还是第三方app, 如果apk包里lib目录存在.so文件,会根据.so文件来确定primaryCpuAbi的值 如果是系统app, apk包里又不存在.so文件,就会进一步根据/system/app/${APP_NAME}/lib和/system/app/${APP_NAME}/lib64这两个文件夹是否存在,来确定它的primaryCpuAbi的值 对于framework-res.apk为个特殊的apk文件,它的primaryCpuAbi的值由虚拟机是什么架构来决定,在我这里,它是arm64-v8a 对于其余的apk, 它们的primaryCpuAbi的值仍然为空.

3.3 adjustCpuAbisForSharedUserLPw

先来看下adjustCpuAbisForSharedUserLPw的调用位置,在PKMS的构造函数里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
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<packagesetting> 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值的最后一次机会,如果在这里还无法确定,那就在启动进程时,使用系统默认值。

4. 总结

最后来总结一下Android系统确定app进程关联哪种ABI的流程:

  • 手机不是第一次开机或者升级,直接从packages.xml中读取;

  • 如果apk包中lib文件夹下有.so库,就根据这个.so库的架构模式确定app的primaryCpuAbi的值 ;

  • 对于system app, 如果没法通过第一步确定primaryCpuAbi的值,PKMS会根据/system/app/${APP_NAME}/lib和/system/app/${APP_NAME}/lib64这两个文件夹是否存在,来确定它的primaryCpuAbi的值;

  • 对于还没有确定的app, 在最后还会将自己的primaryCpuAbi值与和他使用相同UID的package的值设成一样;

  • 对于到这里还没有确认primaryCpuAbi的app,就会在启动进程时使用ro.product.cpu.abilist这个property的值的第一项作为它关联的ABI;


了解了ABI的确定过程,继续来看前面的问题。上面说的很清楚,第一次开机时app的ABI确定过程分四个过程,首先是找lib文件夹下有没有so,有的话根据so来确定ABI;然后看app目录下有没有lib或lib64文件夹,有的话根据文件夹确定ABI;通过adjustCpuAbisForSharedUserLPw方法将app的api调整为与之uid相同的某个app的ABI;还无法确认的话(同uid的应用都不存在lib和lib64文件夹),去系统默认值。

根据以上过程可以推断,出问题的应用应该是本身无法确定ABI,在通过adjustCpuAbisForSharedUserLPw确定ABI时是根据 com.ape.factory来确定的ABI,也就是32位。因为问题不是必现的,所以adjustCpuAbisForSharedUserLPw确定ABI的顺序应该不是确定的。来看下该方法的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
/**
* Adjusts ABIs for a set of packages belonging to a shared user so that they all match.
* i.e, so that all packages can be run inside a single process if required.
*
* Optionally, callers can pass in a parsed package via {@code newPackage} in which case
* this function will either try and make the ABI for all packages in {@code packagesForUser}
* match {@code scannedPackage} or will update the ABI of {@code scannedPackage} to match
* the ABI selected for {@code packagesForUser}. This variant is used when installing or
* updating a package that belongs to a shared user.
*
* NOTE: We currently only match for the primary CPU abi string. Matching the secondary
* adds unnecessary complexity.
*/
private void adjustCpuAbisForSharedUserLPw(Set<PackageSetting> packagesForUser,
PackageParser.Package scannedPackage) {

String requiredInstructionSet = null;
//根据log可以判断scannedPackage为空,不考虑这个分支
if (scannedPackage != null && scannedPackage.applicationInfo.primaryCpuAbi != null) {
requiredInstructionSet = VMRuntime.getInstructionSet(
scannedPackage.applicationInfo.primaryCpuAbi);
Slog.i(TAG, "In adjustCpuAbisForSharedUserLPw, scannedPackage:" + scannedPackage.toString() + ", primaryCpuAbi:" + scannedPackage.applicationInfo.primaryCpuAbi);

}

PackageSetting requirer = null;//首个能自身确定ABI的应用
 //遍历同uid的应用,packagesForUser是个set,每次遍历的顺序不确定
for (PackageSetting ps : packagesForUser) {
// If packagesForUser contains scannedPackage, we skip it. This will happen
// when scannedPackage is an update of an existing package. Without this check,
// we will never be able to change the ABI of any package belonging to a shared
// user, even if it's compatible with other packages.
if (scannedPackage == null || !scannedPackage.packageName.equals(ps.name)) {
 //找到首个能自身确定ABI的应用,用于确定requirer和requiredInstructionSet
if (ps.primaryCpuAbiString == null) {
continue;
}

final String instructionSet = VMRuntime.getInstructionSet(ps.primaryCpuAbiString);
 //找到首个能自身确定ABI的应用之前requiredInstructionSet一直为空
if (requiredInstructionSet != null && !instructionSet.equals(requiredInstructionSet)) {
// We have a mismatch between instruction sets (say arm vs arm64) warn about
// this but there's not much we can do.
//同uid的应用是能运行在同一个进程的,所以指令集应该是一致的,但是如果不一致,
//系统也无能为力
String errorMessage = "Instruction set mismatch, "
+ ((requirer == null) ? "[caller]" : requirer)
+ " requires " + requiredInstructionSet + " whereas " + ps
+ " requires " + instructionSet;
Slog.w(TAG, errorMessage);
}
  //找到首个能自身确定ABI的应用后赋值,之后循环不会再走到这里
if (requiredInstructionSet == null) {
requiredInstructionSet = instructionSet;
requirer = ps;
}
}
}

if (requiredInstructionSet != null) {
String adjustedAbi;
if (requirer != null) {//存在能确定自身ABI的应用
// requirer != null implies that either scannedPackage was null or that scannedPackage
// did not require an ABI, in which case we have to adjust scannedPackage to match
// the ABI of the set (which is the same as requirer's ABI)
 adjustedAbi = requirer.primaryCpuAbiString;
if (scannedPackage != null) {
scannedPackage.applicationInfo.primaryCpuAbi = adjustedAbi;
}
} else {
// requirer == null implies that we're updating all ABIs in the set to
// match scannedPackage.
adjustedAbi = scannedPackage.applicationInfo.primaryCpuAbi;
}

for (PackageSetting ps : packagesForUser) {
if (scannedPackage == null || !scannedPackage.packageName.equals(ps.name)) {
if (ps.primaryCpuAbiString != null) {//能自己确定ABI的就不会再走这里,保留已经确定的值
continue;
}
  
ps.primaryCpuAbiString = adjustedAbi;
if (ps.pkg != null && ps.pkg.applicationInfo != null &&
!TextUtils.equals(adjustedAbi, ps.pkg.applicationInfo.primaryCpuAbi)) {
 //根据首个得到的ABI确定其余应用的ABI,该值会写入/data/system/packages.xml中,再次启动手机时直接从该文件读取
ps.pkg.applicationInfo.primaryCpuAbi = adjustedAbi;
if (DEBUG_ABI_SELECTION) {
Slog.i(TAG, "Adjusting ABI for " + ps.name + " to " + adjustedAbi
+ " (requirer="
+ (requirer != null ? requirer.pkg : "null")
+ ", scannedPackage="
+ (scannedPackage != null ? scannedPackage : "null")
+ ")");
}
try {
mInstaller.rmdex(ps.codePathString,
getDexCodeInstructionSet(getPreferredInstructionSet()));
} catch (InstallerException ignored) {
}
}
}
}
}
}

关于ps.pkg.applicationInfo.primaryCpuAbi = adjustedAbi;primaryCpuAbi的说明:

1
2
3
4
5
6
7
8
9
10
/**
* The primary ABI that this application requires, This is inferred from the ABIs
* of the native JNI libraries the application bundles. Will be {@code null}
* if this application does not require any particular ABI.
*
* If non-null, the application will always be launched with this ABI.
*
* {@hide}
*/
public String primaryCpuAbi;

上面的代码证明上面的推测是正确的,保险起见,又加了些log,分别抓取了正常情况下的log和出问题时的log,从log中可以看出,在出问题时,com.ape.factory都是遍历过程中第一个可以确认自身ABI的应用,而在正常的情况下,第一个可以确认自身ABI的应用的ABI都是64位的,满足这个条件的应用有两个:com.android.settings和factory.tinnosnew.tinno.ui.test(/system/app/Mygsensortest)。所以是否会出问题取决于在遍历过程中先取到factory还是另外两个应用,只要不先取到factory就不会出现问题。将代码修改未只能根据factory确定其余应用ABI,则该问题变为必现,进一步证明该问题就是factory引起的。

最后,要确定是不是存在相同uid中同时有32位和64位的apk,只需要在log中搜索‘“PackageManager: Instruction set mismatch”即可。比如:

1
Instruction set mismatch, PackageSetting{b4910c6 factory.tinnosnew.tinno.ui.test/1000} requires arm64 whereas PackageSetting{da776f5 com.ape.factory/1000} requires arm

表示先取得的是64位的factory.tinnosnew.tinno.ui.test,这时一切正常;

1
2
01-01 12:40:19.782   823   823 W PackageManager: Instruction set mismatch, PackageSetting{58e729 com.ape.factory/1000} requires arm whereas PackageSetting{79d22ba com.android.settings/1000} requires arm64
01-01 12:40:19.783 823 823 W PackageManager: Instruction set mismatch, PackageSetting{58e729 com.ape.factory/1000} requires arm whereas PackageSetting{bd64d3a factory.tinnosnew.tinno.ui.test/1000} requires arm64

表示先取到的是com.ape.factory,这时就会出现文中的问题。

尾声

一个疑惑:

1
01-01 12:00:00.869  1344  1344 E AndroidRuntime: java.lang.RuntimeException: Unable to instantiate application com.android.deskclock.DeskClockApplication: java.lang.ClassNotFoundException: Didn't find class "com.android.deskclock.DeskClockApplication" on path: DexPathList[[zip file "/system/app/ApeDeskClock80/ApeDeskClock80.apk"],nativeLibraryDirectories=[/system/app/ApeDeskClock80/lib/arm64, /system/app/ApeDeskClock80/ApeDeskClock80.apk!/lib/armeabi, /system/lib, /vendor/lib, /system/lib, /vendor/lib]]

一直以为这段log说的是在“zip file “/system/app/ApeDeskClock80/ApeDeskClock80.apk”],nativeLibraryDirectories=[/system/app/ApeDeskClock80/lib/arm64, /system/app/ApeDeskClock80/ApeDeskClock80.apk!/lib/armeabi, /system/lib, /vendor/lib, /system/lib, /vendor/lib”这些路径下找不到DeskClockApplication这个类,但实际上dex文件是在appname/oat/arm(64)下面的,即使不存下32和64位的问题,也应该找不到dex的,为什么能找到?

后来问了同时才知道DexPathList[[zip file “/system/app/ApeDeskClock80/ApeDeskClock80.apk”],nativeLibraryDirectories=[/system/app/ApeDeskClock80/lib/arm64, /system/app/ApeDeskClock80/ApeDeskClock80.apk!/lib/armeabi, /system/lib, /vendor/lib, /system/lib, /vendor/lib]]描述的是一系列的dex文件,[zip file “/system/app/ApeDeskClock80/ApeDeskClock80.apk”],nativeLibraryDirectories=[/system/app/ApeDeskClock80/lib/arm64, /system/app/ApeDeskClock80/ApeDeskClock80.apk!/lib/armeabi, /system/lib, /vendor/lib, /system/lib, /vendor/lib]是其中一个dex文件的描述信息。这部分还有待了解。

以上的内容时关于首次开机时应用ABI的确定的,关于应用其他安装方式中ABI的确定在了解应用安装过程时再做了解。

Powered by Hexo and Hexo-theme-hiker

Copyright © 2018 - 2022 得一 All Rights Reserved.

访客数 : | 访问量 :