移除面向系统用户的软件包

本文介绍了如何确定和移除 SYSTEM 用户不需要的软件包,从而提高性能。

停用不必要的软件包

在 Automotive 中,SYSTEM 用户是无头的,这意味人不得使用或直接访问 SYSTEM 用户。因而许多应用和服务无需以 SYSTEM 用户角色运行,并且可以停用这些应用和服务,以提高性能。因此,我们提供了一个选项,可用于移除 SYSTEM 用户(用户 0)不需要的应用。

在本文中,我们将讨论以下两类用户:

  • SYSTEM。始终为用户 0
  • FULL。将由人(非 SYSTEM 用户)使用的用户,如用户 10+

Android 11

在 Android 11 中,您可以更改 config_userTypePackageWhitelistMode 配置。标志可以合并。在这种情况下,5 相当于 1 加上 4(标志 14 的组合)。

  0  - disable whitelist (install all system packages; no logging)
  1  - enforce (only install system packages if they are whitelisted)
  2  - log (log non-whitelisted packages)
  4  - any package not mentioned in the whitelist file is implicitly whitelisted on all users
  8  - same as 4, but just for the SYSTEM user
  16 - ignore OTAs (don't install system packages during OTAs)
  Common scenarios:
  - to enable feature (fully enforced) for a complete allowlist: 1
  - to enable feature for an incomplete allowlist (so use implicit allowlist mode): 5
  - to enable feature but implicitly allowlist for SYSTEM user to ease local development: 9
  - to disable feature completely if it had never been enabled: 16
  - to henceforth disable feature and try to undo its previous effects: 0

请务必将 XML 文件安装在设备的 sysconfig 目录中(此目录与包含用于构建设备系统映像的 makefile (.mk) 的目录相同)。为 XML 文件命名时,请包含在 build 中定义软件包的位置。例如 preinstalled-packages-product-car-CAR_PRODUCT_NAME.xml

<!- this package will be installed for both FULL and SYSTEM user -->
    <install-in-user-type package="com.android.bluetooth"->
        <install-in user-type="FULL" /->
        <install-in user-type="SYSTEM" /->
    </install-in-user-type->

<!- this package will only be installed for both FULL user -->
    <install-in-user-type package="com.android.car.calendar"->
        <install-in user-type="FULL" >
    </install-in-user-type->

Android 9 和 Android 10

如需在 Android 9 和 Android 10 中配置此功能,请按以下步骤操作:

  1. 叠加 frameworks/base/core/res/res/values/config.xml 中的 config_systemUserPackagesBlacklistSupported 配置,并将其设置为 true。默认情况下,启用此功能后,应同时为 SYSTEM 用户和 FULL 用户安装所有软件包。
  2. 创建一个 config.xml 文件,列出应为 SYSTEM 用户停用哪些软件包。例如:
    <config>
        <!-- This package will be uninstalled for the system user -->
        <system-user-blacklisted-app package="com.google.car.calendar" />
    </config>
    
  3. device.mk 添加一行,以将文件复制到设备的目标文件夹 system/etc/sysconfig/。例如:
    PRODUCT_COPY_FILES += <full path to the config file>:system/etc/sysconfig/<new denylist config file>.xml
    

验证结果

如需验证结果,请运行以下命令:

$ adb shell dumpsys user | grep PACKAGE_SUBSTRING
$ adb shell pm list packages --user USER_ID PACKAGE_SUBSTRING
$ adb shell cmd user report-system-user-package-whitelist-problems

前提

如需确定是否应以 SYSTEM 用户角色安装软件包,请检查位于项目源根目录下的软件包的 AndroidManifest.xml 文件,包括应用的属性和组件,其中包括所有 activity、服务、广播接收器和 content provider。如需了解详情,请参阅应用清单概览

停用软件包工作流

图 1. 停用软件包工作流

第 1 级,应用级

1. 检查应用(或应用组件)是否已声明为单例

如果应用是单例,系统只会以 SYSTEM 用户角色实例化应用。该应用可能是一款可感知多用户的应用。如需详细了解可感知多用户的应用,请参阅构建可感知多用户的应用

  1. 检查 android:singleUser="true" 的 Android 清单。
  2. 如果为 true,请列入许可名单。对于 SYSTEM 用户而言,此操作十分必要。
  3. 如果为 false,请继续。请先检查其他条件,然后再移除。

2. 检查应用是否需要受保护的存储空间访问权限

许多系统启动服务通常依赖于设备加密 (DE) 存储空间,而不是凭据加密 (CE) 存储空间。此外,直接启动感知型系统应用也依赖于设备加密存储空间。如需详细了解直接启动感知型应用,请参阅在系统应用中支持直接启动

  1. 检查 Android 清单中的条件 android:defaultToDeviceProtectedStorage="true",很多系统启动服务都要求满足此条件。
  2. 如果为 true,请列入许可名单。
  3. 如果为 false,请继续。

第 2 级,应用组件

activity

如需详细了解 Activity,请参阅 Activity 简介

a. 检查应用是否仅包含 Activity

Activity 以界面为导向。因为在 Automotive 中 SYSTEM 用户是无头的,因而人不会与 SYSTEM 用户进行互动。因此,如果应用仅包含 Activity,该应用很可能与 SYSTEM 用户无关。

检查优先级和特殊权限。

  1. 如果为,表示 SYSTEM 用户可能需要。
  2. 如果为,请勿将其列入 SYSTEM 用户的许可名单。

例如,兼容性测试套件 (CTS) (com.android.cts.priv.ctsshim) 仅包含 Activity,而 Activity 被定义为测试 intent 过滤器。但是,由于该套件具有较高权限,因此需要为 SYSTEM 用户安装该套件,以便进行测试。

服务

如需详细了解服务,请参阅服务概览

b. 检查服务是否声明为专用服务,是否无法通过其他应用访问

如果服务声明为专用服务,其他软件包将不会使用该服务。查找 android:exported="false"。如果服务声明为专用服务,或无法通过其他应用访问,则该服务将无法通过其他应用绑定。在这种情况下,无需考虑下文中的步骤 C 和 D。因此,此组件不会提供有关 SYSTEM 用户是否需要该服务的更多提示。

  1. 如果为,请检查下一个组件。
  2. 如果为,请继续检查此组件。

c. 检查以 SYSTEM 用户角色安装的应用是否可以绑定到该服务

检查第 1 级中列入许可名单的软件包,并确定这些软件包要绑定到的服务。从该服务的 intent 过滤器以及其他软件包的 startService 进行跟踪。

如果该服务绑定到以 SYSTEM 用户角色安装的应用(例如,com.android.car.companiondevicesupport 已列入许可名单,可以 SYSTEM 用户角色运行),则将该服务列入许可名单。

  1. 如果为,请列入许可名单。
  2. 如果为,请继续检查此组件。

d. 检查服务是否是从其他应用绑定的,以及是否声明在前台运行

查找 startForeground。这意味着,人会在前台与该应用互动。最有可能的是,SYSTEM 用户无需使用该服务,且该服务也无需列入许可名单。

  1. 如果为,请勿列入许可名单。
  2. 如果为,请继续检查下一个组件。

e. 检查服务是否定义为在系统进程中运行

在 AndroidManifest 中,查找 android:process="system"
如有意将服务定义为在系统进程中运行,这意味着该服务将明确在与系统服务相同的进程中运行,并且应列入许可名单以便以 SYSTEM 用户角色运行。作为 Android 内存分配设计的一部分,系统服务是最后终止的进程,这表明使用此类属性定义服务十分重要。如需详细了解 Android 的内存分配设计,请参阅低内存终止守护程序

  1. 如果为,请勿列入许可名单。
  2. 如果为,请继续检查其他组件。

例如,必须将 com.android.networkstack.inprocess 软件包列入许可名单,因为它包含 RegularMaintenanceJobService,后者具有 android:process="system" 标记。

内容提供程序

如需详细了解内容提供程序,请参阅内容提供程序

f. 检查以 SYSTEM 用户角色安装的应用是否依赖于此提供程序

检查第 1 级中列入许可名单的软件包,并检查它们所依赖的提供程序。如果某一应用以 SYSTEM 用户角色运行(例如 com.android.car.companiondevicesupport 已列入许可名单,可以 SYSTEM 用户角色运行)并且依赖于此 content provider,请确保允许将此 content provider 列入许可名单。

  1. 如果为,请列入许可名单。
  2. 如果为,请勿列入许可名单。

例如,如果 com.android.car.EXAMPLE 包含单例提供程序(SystemActionsContentProviderManagedProvisioningActionsContentProvider),那么可以将其列入 SYSTEM 用户许可名单。然后,如果 com.android.car.EXAMPLE 依赖于 WebViewFactoryProviderandroid.webkit,必须允许将 com.android.webview 列入 SYSTEM 用户的许可名单,因为 com.android.webview 加载了 android.webkit

软件包演练示例

以下示例展示了如何评估软件包的 AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<!-- 1. Search in the entire manifest for singleUser attribute.
No. Move to step 2 -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.android.providers.calendar"
        android:sharedUserId="android.uid.calendar">
    We can ignore the entire permission section
    <uses-permission android:name="android.permission.READ_CALENDAR" />
    ...
    <uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" />
<!-- 2. Look for defaultToDeviceProtectedStorage in application's attribute.
No. Continue evaluating app components. -->
    <application android:label="@string/calendar_storage"
                 android:allowBackup="false"
                 android:icon="@drawable/app_icon"
                 android:usesCleartextTraffic="false">
<!-- a. Contain only activities?
No. Continue to evaluate components other than activities. -->
        <provider android:name="CalendarProvider2" android:authorities="com.android.calendar"
                <!-- b. Is this component exported?
                Yes. Continue evaluating this component.
                f. App on u0 might depend on this? Search for CalendarProvider2 in dumpsys, shows ContentProviderRecord{b710923 u0 com.android.providers.calendar/.CalendarProvider2}
                Yes. Whitelist for system user. -->
                android:label="@string/provider_label"
                android:multiprocess="false"
                android:exported="true"
                android:readPermission="android.permission.READ_CALENDAR"
                android:writePermission="android.permission.WRITE_CALENDAR" />

<activity android:name="CalendarContentProviderTests" android:label="Calendar Content Provider" android:exported="false"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.UNIT_TEST" /> </intent-filter> </activity> <!-- Not service/content provider. Ignore. --> <receiver android:name="CalendarProviderBroadcastReceiver" android:exported="false"> <intent-filter> <action android:name="com.android.providers.calendar.intent.CalendarProvider2"/> <category android:name="com.android.providers.calendar"/> </intent-filter> <intent-filter> <action android:name="android.intent.action.EVENT_REMINDER"/> <data android:scheme="content" /> </intent-filter> </receiver> <service android:name="CalendarProviderIntentService"/> </application> </manifest>