信息架构

Android 8.0 为设置应用引入了一种新的信息架构,以简化设置的组织方式,并使用户能够更轻松地快速找到用于自定义 Android 设备的设置。Android 9 引入了一些改进,以提供更多的设置功能和更简单的实现。

示例和源代码

设置中的大多数页面目前都使用新的框架实现。一个很好的例子是 DisplaySettings:packages/apps/Settings/src/com/android/settings/DisplaySettings.java

以下列出了重要组件的文件路径

  • CategoryKeypackages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
  • DashboardFragmentRegistrypackages/apps/Settings/src/com/android/settings/dashboard/DashboardFragmentRegistry.java
  • DashboardFragmentpackages/apps/Settings/src/com/android/settings/dashboard/DashboardFragment.java
  • AbstractPreferenceControllerframeworks/base/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java
  • BasePreferenceController(在 Android 9 中引入): packages/apps/Settings/src/com/android/settings/core/BasePreferenceController.java

实现

鼓励设备制造商采用现有的设置信息架构,并根据需要插入额外的设置页面,以适应合作伙伴特定的功能。将首选项从旧版页面(实现为 SettingsPreferencePage)移动到新页面(使用 DashboardFragment 实现)可能会很复杂。旧版页面中的首选项可能未使用 PreferenceController 实现。

因此,当将首选项从旧版页面移动到新页面时,您需要创建一个 PreferenceController,并将代码移动到控制器中,然后再在新 DashboardFragment 中实例化它。PreferenceController 所需的 API 在其名称中描述,并在 Javadoc 中记录。

强烈建议为每个 PreferenceController 添加单元测试。如果更改已提交到 AOSP,则需要进行单元测试。要获得有关如何编写基于 Robolectric 的测试的更多信息,请参阅自述文件 packages/apps/Settings/tests/robotests/README.md

插件式信息架构

每个设置项都实现为一个 Preference。Preference 可以轻松地从一个页面移动到另一个页面。

为了更容易地移动多个设置,Android 8.0 引入了一个插件式宿主片段,其中包含设置项。设置项被建模为插件式控制器。因此,设置页面由单个宿主片段和多个设置控制器构成。

DashboardFragment

DashboardFragment 是插件式首选项控制器的宿主。该片段继承自 PreferenceFragment,并具有扩展和更新静态首选项列表和动态首选项列表的钩子。

静态首选项

静态首选项列表在 XML 中使用 <Preference> 标签定义。DashboardFragment 实现使用 getPreferenceScreenResId() 方法来定义哪个 XML 文件包含要显示的静态首选项列表。

动态首选项

动态项表示一个带有 intent 的磁贴,它指向外部或内部 Activity。通常,intent 指向不同的设置页面。例如,设置主页上的“Google”设置项是一个动态项。动态项在 AndroidManifest 中定义(如下所述),并通过 FeatureProvider 加载(定义为 DashboardFeatureProvider)。

动态设置比静态配置的设置更重,因此通常开发人员应将设置实现为静态设置。但是,当满足以下任一条件时,动态设置可能很有用

  • 该设置不是直接在设置应用中实现的(例如,注入 OEM/运营商应用实现的设置)。
  • 该设置应显示在设置主页上。
  • 您已经拥有该设置的 Activity,并且不想实现额外的静态配置。

要将 Activity 配置为动态设置,请执行以下操作

  • 通过向 Activity 添加 intent-filter,将 Activity 标记为动态设置。
  • 告诉设置应用它属于哪个类别。该类别是一个常量,在 CategoryKey 中定义。
  • 可选:在显示设置时添加摘要文本。

以下是从设置应用中提取的 DisplaySettings 示例。

<activity android:name="Settings$DisplaySettingsActivity"
                   android:label="@string/display_settings"
                   android:icon="@drawable/ic_settings_display">
             <!-- Mark the activity as a dynamic setting -->
              <intent-filter>
                     <action android:name="com.android.settings.action.IA_SETTINGS" />
              </intent-filter>
             <!-- Tell Settings app which category it belongs to -->
              <meta-data android:name="com.android.settings.category"
                     android:value="com.android.settings.category.ia.homepage" />
             <!-- Add a summary text when the setting is displayed -->
              <meta-data android:name="com.android.settings.summary"
                     android:resource="@string/display_dashboard_summary"/>
             </activity>

在渲染时,片段将从静态 XML 和 AndroidManifest 中定义的动态设置请求首选项列表。无论 PreferenceController 是在 Java 代码还是 XML 中定义的,DashboardFragment 都通过 PreferenceController(如下所述)管理每个设置的处理逻辑。然后,它们在 UI 中显示为混合列表。

PreferenceController

本节介绍在 Android 9 和 Android 8.x 中实现 PreferenceController 之间的差异。

Android 9 版本中的 PreferenceController

PreferenceController 包含与偏好设置交互的所有逻辑,包括显示、更新、搜索索引等。

PreferenceController 的接口定义为 BasePreferenceController。例如,请参阅 packages/apps/Settings/src/com/android/settings/core/ BasePreferenceController.java 中的代码

有几个 BasePreferenceController 的子类,每个子类都映射到设置应用默认支持的特定 UI 样式。例如,TogglePreferenceController 具有一个 API,该 API 直接映射到用户应如何与基于切换的偏好设置 UI 进行交互。

BasePreferenceController 具有诸如 getAvailabilityStatus()displayPreference()handlePreferenceTreeClicked() 等 API。每个 API 的详细文档都在接口类中。

对实现 BasePreferenceController(及其子类,如 TogglePreferenceController)的限制是,构造函数签名必须与以下任一签名匹配

  • public MyController(Context context, String key) {}
  • public MyController(Context context) {}

在将偏好设置安装到 Fragment 时,信息中心提供了一种在显示时间之前附加 PreferenceController 的方法。在安装时,控制器会连接到 Fragment,以便所有未来相关事件都将发送到控制器。

DashboardFragment 保留屏幕中 PreferenceController 的列表。在 Fragment 的 onCreate() 中,将为所有控制器调用 getAvailabilityStatus() 方法,如果该方法返回 true,则将调用 displayPreference() 来处理显示逻辑。getAvailabilityStatus() 对于告知设置框架哪些项在搜索期间可用也很重要。

Android 8.x 版本中的 PreferenceController

PreferenceController 包含与偏好设置交互的所有逻辑,包括显示、更新、搜索索引等。

与偏好设置交互相对应, PreferenceController 的接口具有 API isAvailable() displayPreference()handlePreferenceTreeClicked() 等。有关每个 API 的详细文档,请参阅接口类。

在将偏好设置安装到 Fragment 时,信息中心提供了一种在显示时间之前附加 PreferenceController 的方法。在安装时,控制器会连接到 Fragment,以便所有未来相关事件都将发送到控制器。

DashboardFragment 保留屏幕中 PreferenceControllers 的列表。在 Fragment 的 onCreate() 中,将为所有控制器调用 isAvailable() 方法,如果该方法返回 true,则将调用 displayPreference() 来处理显示逻辑。

使用 DashboardFragment

将偏好设置从页面 A 移动到页面 B

如果偏好设置静态列在原始页面的偏好设置 XML 文件中,请按照下面适用于您的 Android 版本的静态移动程序进行操作。否则,请按照下面适用于您的 Android 版本的动态移动程序进行操作。

Android 9 中的静态移动

  1. 找到原始页面和目标页面的偏好设置 XML 文件。您可以从页面的 getPreferenceScreenResId() 方法中找到此信息。
  2. 从原始页面的 XML 中删除偏好设置。
  3. 将偏好设置添加到目标页面的 XML。
  4. 从原始页面的 Java 实现中删除此偏好设置的 PreferenceController。通常它在 createPreferenceControllers() 中。控制器可以直接在 XML 中声明。

    注意:偏好设置可能没有 PreferenceController

  5. 在目标页面的 createPreferenceControllers() 中实例化 PreferenceController。如果 PreferenceController 在旧页面的 XML 中定义,则也在新页面的 XML 中定义它。

Android 9 中的动态移动

  1. 查找原始页面和目标页面托管的类别。您可以在 DashboardFragmentRegistry 中找到此信息。
  2. 打开包含您需要移动的设置的 AndroidManifest.xml 文件,并找到表示此设置的 Activity 条目。
  3. 将 activity 的元数据值 com.android.settings.category 设置为新页面的类别键。

Android 8.x 版本中的静态移动

  1. 找到原始页面和目标页面的偏好设置 XML 文件。
  2. 您可以从页面的 getPreferenceScreenResId() 方法中找到此信息。
  3. 删除原始页面 XML 中的偏好设置。
  4. 将偏好设置添加到目标页面的 XML。
  5. 从原始页面的 Java 实现中删除此偏好设置的 PreferenceController。通常它在 getPreferenceControllers() 中。
  6. 注意:偏好设置可能没有 PreferenceController

  7. 在目标页面的 getPreferenceControllers() 中实例化 PreferenceController

Android 8.x 版本中的动态移动

  1. 查找原始页面和目标页面托管的类别。您可以在 DashboardFragmentRegistry 中找到此信息。
  2. 打开包含您需要移动的设置的 AndroidManifest.xml 文件,并找到表示此设置的 Activity 条目。
  3. 更改 activity 的元数据值 com.android.settings.category,将该值设置为指向新页面的类别键。

在一个页面中创建新的偏好设置

如果偏好设置静态列在原始页面的偏好设置 XML 文件中,请按照下面的静态程序进行操作。否则,请按照动态程序进行操作。

创建静态偏好设置

  1. 找到页面的偏好设置 XML 文件。您可以从页面的 getPreferenceScreenResId() 方法中找到此信息。
  2. 在 XML 中添加新的 Preference 项。确保它具有唯一的 android:key
  3. 在该页面的 getPreferenceControllers() 方法中为此偏好设置定义 PreferenceController
    • 在 Android 8.x 中以及可选的 Android 9 中,在该页面的 createPreferenceControllers() 方法中实例化此偏好设置的 PreferenceController

      如果此偏好设置已存在于其他位置,则可能已存在一个用于它的 PreferenceController。您可以重用 PreferenceController,而无需构建新的。

    • 从 Android 9 开始,您可以选择在 XML 中与偏好设置一起声明 PreferenceController。例如
      <Preference
              android:key="reset_dashboard"
              android:title="@string/reset_dashboard_title"
              settings:controller="com.android.settings.system.ResetPreferenceController"/>

创建动态偏好设置

  1. 查找原始页面和目标页面托管的类别。您可以在 DashboardFragmentRegistry 中找到此信息。
  2. AndroidManifest 中创建一个新的 Activity
  3. 向新的 Activity 添加必要的元数据以定义设置。将元数据值 com.android.settings.category 设置为步骤 1 中定义的相同值。

创建一个新页面

  1. 创建一个新的 Fragment,继承自 DashboardFragment
  2. DashboardFragmentRegistry 中定义其类别。

    注意:此步骤是可选的。如果在此页面中不需要任何动态偏好设置,则无需提供类别键。

  3. 按照步骤为此页面添加所需的设置。有关更多信息,请参阅实现部分。

验证

  • 在设置中运行 robolectric 测试。所有现有和新测试都应通过。
  • 构建并安装设置,然后手动打开正在修改的页面。页面应立即更新。