Android中app进程ABI确定过程

更多干货,请关注微信公众号: 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形式来启动的。

1. abi相关property

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

[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进程。图中有几个关键的函数:

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 scanPackageDirtyLI

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

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

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

接着先来看下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);
}

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

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

接下来看下系统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;
    }
}

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

  • 对系统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.2 adjustCpuAbisForSharedUserLPw

先来看下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<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的流程:

  1. 如果apk包中lib文件夹下有.so库,就根据这个.so库的架构模式,确定app的primaryCpuAbi的值
  2. 对于system app, 如果没法通过第一步确定primaryCpuAbi的值,PKMS会根据/system/app/${APP_NAME}/lib和/system/app/${APP_NAME}/lib64这两个文件夹是否存在,来确定它的primaryCpuAbi的值
  3. 对于还没有确定的app, 在最后还会将自己的primaryCpuAbi值与和他使用相同UID的package的值设成一样
  4. 对于到这里还没有确认primaryCpuAbi的app,就会在启动进程时使用ro.product.cpu.abilist这个property的值的第一项作为它关联的ABI
<think>我们面对的问题是在预装应用时出现了两个错误:1."IgnoringpersistedABIoverrideforpackage..."2."Expected...tobefrozen!"根据之前的分析,第一个错误通常是因为预装应用ABI与设备支持的ABI不匹配,导致系统忽略了我们设置的ABI覆盖。第二个错误是因为系统期望预装应用处于冻结状态(frozen),但实际上可能没有。现在,用户希望知道如何在Android.mk中配置以取消ABI兼容性校验(但实际上不能完全取消,而是需要正确配置ABI),并且解决冻结状态的问题。在Android.mk中,我们可以通过设置LOCAL_MULTILIB来指定编译的ABI类型,从而确保生成的APK或库文件与设备兼容。同时,我们需要确保预装应用在安装时正确设置冻结状态。步骤:1.**ABI配置**:在Android.mk中,我们可以使用`LOCAL_MULTILIB`来指定编译目标。我们可以根据设备的ABI支持情况来设置这个变量。例如,如果我们希望应用只编译32位版本,可以设置:```LOCAL_MULTILIB:=32```或者只编译64位版本:```LOCAL_MULTILIB:=64```如果我们希望同时编译32位和64位,可以设置:```LOCAL_MULTILIB:=both```但这样会生成两个版本的库,系统会根据设备选择加载。另一种方法是,如果我们希望应用只适用于特定的ABI,我们可以将LOCAL_MULTILIB设置为一个具体的ABI类型,或者使用`LOCAL_MODULE_TARGET_ARCH`。但是,需要注意的是,如果我们预装的是APK,那么ABI问题主要是APK内包含的so库的ABI。因此,我们需要确保APK中的so库与设备支持的ABI匹配。在预装时,我们可以将APK放在对应的目录下:-32位库放在`system/lib`-64位库放在`system/lib64`但是,APK本身是一个文件,它内部包含了so库。因此,我们需要在编译APK时确保它包含正确的so库。2.**冻结状态问题**:在Android系统中,预装的系统应用默认是处于冻结状态的(即stopped状态),直到用户首次启动它们。如果系统检测到预装应用没有处于冻结状态,就会抛出错误。在AndroidManifest.xml中,有一个属性`android:isStatic`,它用于指示该应用是否是静态预装的。然而,从Android7.0开始,这个属性已经被废弃。因此,我们可能需要通过其他方式确保应用在预装后处于冻结状态。解决方法:在预装应用后,我们可以通过一个脚本(如init.rc脚本)或者在一个系统服务中执行命令来冻结应用,直到设备首次启动完成(或某个条件满足)。在Android.mk中,我们可以添加一个后置脚本(post-installscript)来执行冻结命令:```include$(CLEAR_VARS)LOCAL_MODULE:=my_prebuilt_appLOCAL_SRC_FILES:=MyApp.apkLOCAL_MODULE_CLASS:=APPSLOCAL_MODULE_TAGS:=optionalLOCAL_BUILT_MODULE_STEM:=package.apkLOCAL_MODULE_SUFFIX:=$(COMMON_ANDROID_PACKAGE_SUFFIX)LOCAL_PRIVILEGED_MODULE:=trueLOCAL_CERTIFICATE:=PRESIGNEDLOCAL_DEX_PREOPT:=falseinclude$(BUILD_PREBUILT)#在预装后执行冻结命令include$(CLEAR_VARS)LOCAL_MODULE:=freeze_my_appLOCAL_MODULE_CLASS:=ETCLOCAL_MODULE_PATH:=$(TARGET_OUT)/etcinclude$(BUILD_SYSTEM)/base_rules.mkFREEZE_SCRIPT:=$(TARGET_OUT)/etc/freeze_my_app.sh$(FREEZE_SCRIPT):echo"#!/system/bin/sh">$@echo"pmdisablecom.example.myapp||true">>$@#或者使用pmsuspendecho"exit0">>$@$(LOCAL_BUILT_MODULE):$(FREEZE_SCRIPT)$(hide)mkdir-p$(dir$@)$(hide)touch$@#还需要确保这个脚本在启动时被执行,例如在init.rc中或者将其作为服务运行```但是,这种方法比较繁琐,且需要设备启动后执行。另一种方法是在预装时设置应用的状态。在PackageManagerService中,预装应用会被默认冻结,除非我们明确设置了解冻。实际上,预装应用在安装后默认是处于冻结状态的。所以这个错误可能是由于其他操作导致应用在未满足条件时被提前解冻。根据错误日志,可能是我们在扫描预装目录的时候做了一些非标准的操作(比如在scanDirTracedLI函数中添加的安装预装应用的操作)导致状态异常。因此,我们需要检查在安装预装应用过程中是否有强制解冻的操作。在代码中,我们使用的是`mInstallPackageHelper.installPackagesFromDir`,这个函数会安装指定目录下的应用。这个安装过程应该遵循系统预装应用的规则,即安装后应用处于冻结状态。但是,错误提示“Expected...tobefrozen”说明系统在后续检查时发现这个应用没有被冻结。可能的原因是这个应用在安装后被立即启用(例如在安装Intent中设置了FLAG_INCLUDE_STOPPED_PACKAGES),或者有其他组件启动了它。解决方案建议:1.**检查ABI兼容性**:确保预装应用内置的so库与设备ABI兼容。可以在Android.mk中通过LOCAL_MULTILIB指定,或者将APK拆分为不同ABI的版本,并预装到对应的目录。如果设备支持多个ABI,我们可以同时预装多个APK(使用不同的LOCAL_MODULE和LOCAL_MULTILIB设置),但是这样会占用更多空间。2.**检查应用状态**:在预装后,不要立即启用应用。确保没有任何其他操作解冻了应用。如果我们在预装后立即启动应用(比如在init.rc中通过am命令启动),那么就会导致应用解冻。应该避免这样做。3.**修改安装参数**:在调用`installPackagesFromDir`时,我们使用了`SCAN_NEW_INSTALL`标志。这个标志会导致应用被扫描并安装,但通常安装后应用处于冻结状态。因此,这个标志应该没有问题。4.**在安装后手动冻结**:如果上述方法无效,我们可以在安装后立即冻结应用。但注意,系统预装应用默认是冻结的,所以这可能是其他原因导致。5.**清理缓存**:在刷机前清理/data/system/packages.xml和/data/system/packages.xml的备份文件,避免残留数据影响。最后,针对用户的问题:如何在Android.mk中取消ABI兼容性校验?实际上,ABI校验是系统强制的,无法取消。但我们可以通过以下方法避免错误:-确保APK中不包含任何so库(纯Java应用),这样就不会有ABI问题。-或者,确保APK中只包含设备支持的ABI的so库。对于预装应用,我们可以将APK按照ABI分别存放在不同的目录,然后在Android.mk中根据TARGET_ARCH选择预装哪个APK。示例:```ifeq($(TARGET_ARCH),arm64)#预装64位版本include$(CLEAR_VARS)LOCAL_MODULE:=my_app_64LOCAL_SRC_FILES:=MyApp_arm64.apk...(其他设置)include$(BUILD_PREBUILT)else#预装32位版本include$(CLEAR_VARS)LOCAL_MODULE:=my_app_32LOCAL_SRC_FILES:=MyApp_arm32.apk...(其他设置)include$(BUILD_PREBUILT)endif```这样,系统在安装时就会选择对应ABI的APK,避免ABI不匹配。对于冻结问题,如果系统默认应该冻结,那我们就不需要额外操作。如果错误仍然出现,可能是系统在安装过程中有其他异常。此时,我们需要检查预装应用AndroidManifest.xml中是否设置了`android:isStatic`属性。虽然这个属性已被废弃,但在某些版本中可能仍会使用。确保该属性为`false`。另外,检查预装应用是否被定义为特权应用(LOCAL_PRIVILEGED_MODULE),特权应用可能有不同的处理方式。总结一下解决方案:1.确保预装应用ABI与设备兼容。-在Android.mk中通过条件判断预装正确ABI版本的应用。2.确保预装应用安装后处于冻结状态。-不要主动启用应用。-在应用预装后不要立即启动它。3.清理设备中残留的包信息缓存(在刷机时自动清理)。4.检查预装应用AndroidManifest.xml配置,避免使用已废弃的`android:isStatic`属性(设为`false`或不设置)。5.若错误仍然存在,则需要在PackageManagerService中检查安装流程,特别是与冻结状态相关的逻辑。如果需要进一步调试,可以打印日志追踪应用安装和解冻的过程。由于用户的问题是在修改代码添加预装应用后出现的,也可以检查添加的位置是否合适。在系统启动时,安装预装应用的操作应该在系统就绪前完成,并且不应该触发解冻操作。注意:在原始scanDirTracedLI函数中,我们在扫描主目录之前扫描了preinstallAppDir。这可能导致预装的应用被安装两次?因为后面还会扫描系统目录(scanDir)。所以需要确保预装目录与应用目录分开。因此,建议只在一个地方安装预装应用。在系统启动时,预装应用的安装通常是在PMS的构造函数中一次性完成的。如果我们在两个不同的地方扫描应用,可能导致同一个应用被扫描两次,从而出现状态不一致。因此,请确保预装目录只扫描一次,且不要与系统目录混合。针对用户添加的代码,建议:1.将预装应用的安装移至扫描系统目录之前,并且只扫描一次。2.使用一个全局变量(如persist.sys.preinstalled)确保只安装一次。用户已经在代码中做了这样的判断,所以应该只有一次安装。但是,要注意在安装预装应用时,传入的标志是否会导致应用立即启用。在安装参数中,我们使用了`SCAN_NEW_INSTALL`,这个标志在系统中通常会使得安装后的应用处于冻结状态(需要用户手动启动)。所以问题应该不是由安装标志引起的。由于问题复杂,以上方法可能需要逐一尝试。下面,我将给出针对ABI问题和冻结问题的具体解决步骤。###解决ABI不匹配问题在Android.mk中,我们可以根据目标设备的ABI选择预装合适的APK(比如同时包含32位和64位的APK,或者分ABI的APK)。这里给出一个示例:```makefile#假设我们有两个版本的APK:app_arm.apk(32位)和app_arm64.apk(64位)include$(CLEAR_VARS)LOCAL_MODULE:=my_prebuilt_app#根据TARGET_ARCH选择LOCAL_MODULE_CLASS:=APPSLOCAL_MODULE_TAGS:=optionalLOCAL_MODULE_SUFFIX:=.apkLOCAL_CERTIFICATE:=PRESIGNEDLOCAL_DEX_PREOPT:=false#根据TARGET_ARCH选择源文件ifneq($(filterarm64,$(TARGET_ARCH)),)LOCAL_SRC_FILES:=MyApp_arm64.apkLOCAL_MULTILIB:=64elseLOCAL_SRC_FILES:=MyApp_arm.apkLOCAL_MULTILIB:=32endifLOCAL_MODULE_TARGET_ARCH:=$(TARGET_ARCH)LOCAL_PRIVILEGED_MODULE:=trueinclude$(BUILD_PREBUILT)```###解决冻结问题在预装应用安装后,我们不应该主动启动它。同时,我们可以检查在安装预装应用过程中是否有自动启动的设置。如果问题仍然存在,我们可以尝试在安装后手动冻结应用:1.在预装应用的安装完成后,执行命令冻结应用(在Android.mk中通过后置脚本执行)。但是,这种方法并不推荐,因为系统在安装完成后本应将应用冻结。手动冻结可能只是掩盖了真正的问题。2.检查应用AndroidManifest.xml,查看是否有启动相关的设置(例如接收BOOT_COMPLETED广播),并且系统在启动时是否发送了该广播导致应用被启动?注意,在Android8.0之后,应用需要显式注册广播接收器才能接收启动完成广播,并且需要用户至少启动一次应用才能生效。所以,预装应用在未启动时不应接收到该广播。###清理缓存在刷机脚本中,添加清除缓存命令:```bash#在制作ROM包时,在刷机脚本中(如updater-script)添加delete_recursive("/data/system/package_cache");delete("/data/system/packages.xml");```或者,在第一次启动时,由init.rc自动清理(系统会重建这些文件)。###重新构建系统镜像在进行修改后,重新编译并刷机测试。---###相关问题§§相关问题§§1.如何查看Android设备支持的ABI列表?2.Android预装应用如何在安装后保持冻结状态?3.预装应用如何避免ABI不匹配问题?4.系统包管理服务如何管理预装应用的安装和冻结状态?</think> ### 解决预装应用 ABI 兼容性校验与冻结状态问题 #### 问题分析 错误日志表明存在两个关键问题: 1. **ABI 校验冲突** `Ignoring persisted ABI override` 表明系统检测到应用与设备 ABI 架构不兼容(如设备支持 arm64-v8a 但应用仅含 armeabi-v7a 库) - 根本原因:APK 包含的本地库与设备 ABI 不匹配[^1] - 强制覆盖 ABI 设定会触发系统安全机制拒绝加载 2. **冻结状态异常** `Expected package to be frozen` 表示系统期望预装应用处于冻结状态,但实际状态不符 - 触发时机:PackageManagerService 扫描 APK 时校验状态[^2] $$ \text{系统校验逻辑} = \begin{cases} \text{通过} & \text{if } \text{pkg.isStopped()} = true \\ \text{抛异常} & \text{otherwise} \end{cases} $$ --- #### 解决方案 ##### 1. ABI 兼容性修正 (Android.mk 配置) ```makefile LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) # 关键配置:根据设备架构选择库文件 ifeq ($(TARGET_ARCH_ABI),arm64-v8a) LOCAL_SRC_FILES := prebuilt/libs/arm64-v8a/libmain.so else ifeq ($(TARGET_ARCH_ABI),armeabi-v7a) LOCAL_SRC_FILES := prebuilt/libs/armeabi-v7a/libmain.so endif LOCAL_MODULE := myapp LOCAL_MODULE_CLASS := APPS LOCAL_MODULE_TAGS := optional LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX) LOCAL_CERTIFICATE := PRESIGNED LOCAL_DEX_PREOPT := false LOCAL_MULTILIB := both # 支持32/64位架构 include $(BUILD_PREBUILT) ``` ##### 2. 冻结状态管理增强 ```makefile # 在系统初始化阶段设置冻结状态 PRODUCT_COPY_FILES += \ $(LOCAL_PATH)/init.myapp.rc:$(TARGET_COPY_OUT_SYSTEM)/etc/init/init.myapp.rc # init.myapp.rc 内容 on boot exec - root -- /system/bin/pm disable com.example.myapp # 强制冻结状态 restart myapp_service # 按需重启关联服务 ``` ##### 3. 包管理缓存清理 在刷机脚本中添加: ```bash # 清除可能导致状态冲突的缓存 format("/metadata/package_cache"); delete_recursive("/data/system/package_cache"); ``` ##### 4. 清单文件修正 ```xml <!-- AndroidManifest.xml 关键修正 --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" android:isStatic="true"> <!-- 声明为静态预装应用 --> <application android:persistent="true" <!-- 声明为持久化进程 --> android:allowClearUserData="false"> <!-- 禁止用户清除数据 --> ``` --- #### 验证方案 ```bash # 1. 检查ABI加载状态 adb shell dumpsys package com.example.myapp | grep "primaryCpuAbi" # 2. 验证冻结状态 adb shell pm dump com.example.myapp | grep "enabled=false" # 3. 错误日志监控 adb logcat | grep -E "PackageManager|ABI|freez" ``` #### 预防措施 1. **ABI 动态适配机制** 使用 Gradle 配置自动分离库文件: ```gradle android { splits { abi { enable true reset() include 'arm64-v8a', 'armeabi-v7a' } } } ``` 2. **状态托管策略** 在 `PackageManagerService` 初始化阶段添加状态强制同步: ```java public void systemReady() { List<PackageInfo> preinstalled = getPreinstalledPackages(); for (PackageInfo pkg : preinstalled) { setPackageStoppedState(pkg.packageName, true); // 强制冻结 } } ``` --- ### 相关问题
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值