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

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

停用不必要的软件包

在 Automotive 中,系统用户是无头用户,这意味着系统用户不会由人类用户使用或直接访问。因此,许多应用和服务无需以系统用户身份运行,并且可以停用这些应用和服务,以提高性能。因此,我们提供了一个选项,可用于移除对系统用户(用户 0)来说不必要的应用。

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

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

Android 11

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

举报 说明
0 停用许可名单。安装所有系统软件包;无日志记录。
1 执行。仅在系统软件包已列入许可名单时安装这些软件包。
2 记录未列入许可名单的软件包。
4 许可名单文件中未提及的任何软件包都会隐式列入所有用户的许可名单。
8 4 相同,适用于系统用户。
16 忽略 OTA。在 OTA 期间不安装系统软件包。

请考虑以下常见情况:

  • 如需为完整的许可名单启用某项功能,则设为 1完全强制执行
  • 如需为不完整的许可名单启用某项功能,则设为 5
  • 如需为 SYSTEM 用户启用某项功能以简化本地开发,则设为 9隐式许可名单
  • 如需将某项功能当作从未启用过的模式停用,则设为 16
  • 如需停用某项功能并撤消之前的所有效果,则设为 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。默认情况下,启用此功能后,应同时为系统用户和 FULL 用户安装所有软件包。
  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、服务、广播接收器和 content provider。如需了解详情,请参阅应用清单概览

停用软件包工作流

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

第 1 级,应用级

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

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

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

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

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

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

第 2 级,应用组件

活动

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

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

activity 以界面为导向。由于在 Automotive 中系统用户是无头用户,因而人类用户不应与系统用户进行互动。所以,如果应用仅包含 activity,则很可能与系统用户无关。

检查优先级和特殊权限:

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

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

服务

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

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

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

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

c. 检查以系统用户身份安装的应用是否可以绑定到该服务

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

如果该服务绑定到以系统用户身份安装的应用(例如,com.android.car.companiondevicesupport 已列入许可名单,能够以系统用户身份运行),则将该服务列入许可名单:

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

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

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

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

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

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

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

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

content provider

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

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

检查第 1 级中列入许可名单的软件包,并检查它们所依赖的提供程序。如果某应用以系统用户身份运行(例如,com.android.car.companiondevicesupport 已列入许可名单,能够以系统用户身份运行),并且依赖于此 content provider,请务必将此 content provider 也列入许可名单。

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

例如,如果 com.android.car.EXAMPLE 包含单例 provider(SystemActionsContentProviderManagedProvisioningActionsContentProvider),那么应将其列入系统用户的许可名单。此外,如果 com.android.car.EXAMPLE 依赖于 WebViewFactoryProviderandroid.webkit,那么必须将 com.android.webview 列入系统用户的许可名单,因为 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>