移除系统用户的软件包

本页面介绍了如何通过识别和移除系统用户不需要的软件包来提高性能。

停用不必要的软件包

在汽车系统中,系统用户是无头用户,这意味着系统用户不打算供人使用或直接访问。因此,许多应用和服务不需要在系统用户中运行,可以停用以提高性能。因此,我们提供了一个选项来移除系统用户(用户 0)不需要的应用。

本页面讨论了两种类型的用户:

  • 系统:始终为用户 0
  • 完整:旨在供人使用的用户(非系统用户),用户 10+

Android 11

在 Android 11 中,更改 config_userTypePackageWhitelistMode 配置。标记可以组合。在本例中,5 等于 14(标记 14 的组合)。

标记 说明
0 停用允许列表。安装所有系统软件包;不记录日志。
1 强制执行。仅当系统软件包在允许列表中时才安装。
2 记录未在允许列表中的软件包。
4 任何未在允许列表文件中提及的软件包都隐式地在所有用户中被允许。
8 4 相同,适用于系统用户。
16 忽略 OTA。在 OTA 期间不安装系统软件包。

考虑以下常见场景:

  • 要为完整允许列表启用某项功能,请使用 1完全强制执行
  • 要为不完整允许列表启用某项功能,请使用 5
  • 要为 SYSTEM 用户启用某项功能以简化本地开发,请使用 9隐式允许列表
  • 要停用某项功能,就像从未启用过一样,请使用 16
  • 要停用某项功能并撤消所有之前的效果,请使用 0

将 XML 文件安装到设备的 sysconfig 目录中(此目录与包含用于构建设备系统映像的 makefile (.mk) 的目录相同)。为 XML 文件命名时,请包含软件包在构建中定义的位置,例如,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。当此功能开启后,默认情况下,所有软件包都应为系统用户和完整用户安装。
  2. 创建一个 config.xml 文件,列出应为系统用户停用的软件包,例如:
    <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

前提

要确定是否应在系统用户中安装软件包,请检查软件包的 AndroidManifest.xml 文件(位于项目源的根目录),包括应用的属性和应用的组件,其中包括所有 Activity、服务、广播接收器和内容提供程序。要了解详情,请参阅 应用清单概览

Disable packages workflow

图 1. 停用软件包工作流。

级别 1:应用级别

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

如果应用是单例,则系统仅在系统用户中实例化该应用。该应用很可能旨在成为多用户感知应用。要详细了解多用户感知应用,请参阅构建多用户感知应用

  1. 检查 Android 清单中是否有 android:singleUser="true"
  2. 如果为 true,则添加到允许列表。系统用户需要。
  3. 如果为 false,则继续。在移除之前检查其他条件。

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

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

  1. 检查 Android 清单中是否有 android:defaultToDeviceProtectedStorage="true",这是许多系统启动服务所必需的。
  2. 如果为 true,则添加到允许列表。
  3. 如果为 false,则继续。

级别 2:应用组件

Activity

要详细了解 Activity,请参阅Activity 简介

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

Activity 面向用户界面。由于系统用户在汽车系统中是无头的,因此不应有人与系统用户交互。因此,如果应用仅包含 Activity,则该应用很可能与系统用户无关。

检查优先级和特殊权限

  1. 如果为,则系统用户可能需要。
  2. 如果为,则不要添加到系统用户的允许列表。

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

服务

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

b. 检查服务是否声明为私有且无法从其他应用访问

如果服务声明为私有,则其他软件包将不会使用该服务。查找 android:exported="false"。如果服务声明为私有或无法从其他应用访问,则其他应用无法绑定该服务。因此,以下步骤 c 和步骤 d 无关紧要。因此,此组件不会提供更多关于是否系统用户需要该服务的提示。

  • 如果为,则检查下一个组件。
  • 如果为,则继续检查此组件。

c. 检查系统用户中安装的应用是否可能绑定到此服务

检查级别 1 中的允许列表软件包,并确定它们绑定到的服务。从此服务中的 intent 过滤器和其他软件包中的 startService 进行跟踪。

如果此服务绑定到系统用户中安装的应用(例如,com.android.car.companiondevicesupport 已添加到允许列表以在系统用户中运行),则将该服务添加到允许列表

  • 如果为,则添加到允许列表。
  • 如果为,则继续检查此组件。

d. 检查服务是否从其他应用绑定,并且声明为在前台运行

查找 startForeground。这意味着人们将在前台与应用交互。最有可能的是,系统用户不需要此服务,因此无需添加到允许列表

  • 如果为,则不要添加到允许列表。
  • 如果为,则继续检查下一个组件。

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

在 AndroidManifest 文件中,查找 android:process="system"。如果服务有意定义为在系统进程中运行,则它与系统服务在同一进程中运行,并且应添加到允许列表以在系统用户中运行。作为 Android 内存分配设计的一部分,系统服务是一些最后被终止的进程,这意味着使用此类属性定义的服务至关重要。要详细了解 Android 的内存分配设计,请参阅低内存 killer

  • 如果为,则不要添加到允许列表。
  • 如果为,则继续检查其他组件。

例如,软件包 com.android.networkstack.inprocess 必须添加到允许列表,因为它包含 RegularMaintenanceJobService,该服务具有 android:process="system" 标记。

内容提供程序

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

f. 检查系统用户中安装的应用是否依赖于此提供程序

检查级别 1 中的允许列表软件包,并检查它们依赖哪些提供程序。如果系统用户中运行的应用(例如,com.android.car.companiondevicesupport 已添加到允许列表以在系统用户中运行)依赖于此内容提供程序,则确保此内容提供程序也添加到允许列表。

  1. 如果为,则添加到允许列表。
  2. 如果为,则不要添加到允许列表。

例如,如果 com.android.car.EXAMPLE 包含单例提供程序(SystemActionsContentProviderManagedProvisioningActionsContentProvider),则应将其添加到系统用户的允许列表。然后,如果 com.android.car.EXAMPLE 依赖于 android.webkitWebViewFactoryProvider,则 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>