信息架构

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 的测试,请参阅 readme 文件 packages/apps/Settings/tests/robotests/README.md

插件式信息架构

每个设置项都作为偏好设置进行实现。可以轻松地将偏好设置从一个页面移到另一个页面。

为了更轻松地移动多个设置,Android 8.0 引入了包含设置项的插件式托管方片段。设置项被建模为插件式控制器。因此,设置页面由单个托管方片段和多个设置控制器构成。

DashboardFragment

DashboardFragment 是插件式偏好设置控制器的托管方。该片段继承自 PreferenceFragment,并具有用于扩大和更新静态偏好设置列表与动态偏好设置列表的钩子。

静态偏好设置

静态偏好设置列表在 XML 中使用 <Preference> 标记定义。DashboardFragment 实现使用 getPreferenceScreenResId() 方法定义哪个 XML 文件包含要显示的静态偏好设置列表。

动态偏好设置

动态项表示具有 intent 的图块,会引向外部或内部 Activity。通常,intent 会引向不同的设置页面。例如,“设置”首页中的“Google”设置项就是一个动态项。动态项在 AndroidManifest 中定义(请参见下文),并通过 FeatureProvider(定义为 DashboardFeatureProvider)加载。

与静态配置的设置相比,动态设置更耗费资源,因此开发者通常应将设置实现为静态设置。不过在以下任一情况下,动态设置可能非常有用:

  • 设置未在“设置”应用中直接实现(例如,注入由 OEM/运营商应用实现的设置)。
  • 设置应显示在“设置”首页上。
  • 您已有相应设置的 Activity,不想实现额外的静态配置。

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

  • 向 Activity 添加 Intent 过滤器,将其标记为动态设置。
  • 将其所属的类别告诉“设置”应用。类别是一个常量,在 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>

在呈现时,片段会请求提供在 AndroidManifest 中定义的静态 XML 和动态设置中的偏好设置列表。无论 PreferenceController 是在 Java 代码中还是在 XML 中定义的,DashboardFragment 都通过 PreferenceController 管理每个设置的处理逻辑(请参见下文)。然后,它们将以混合列表的形式显示在界面中。

PreferenceController

如本部分所述,在 Android 9 中和在 Android 8.x 中实现 PreferenceController 有一些不同。

Android 9 版本中的 PreferenceController

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

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

BasePreferenceController 有多个子类,每个子类都分别映射到“设置”应用默认支持的一个特定界面样式。例如,TogglePreferenceController 包含的某个 API 直接映射到用户应如何与基于切换开关的偏好设置界面进行交互。

BasePreferenceController 具有 getAvailabilityStatus()displayPreference()handlePreferenceTreeClicked(), 等 API。有关每个 API 的详细文档位于相应的接口类中。

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

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

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

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

Android 8.x 版本中的 PreferenceController

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

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

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

DashboardFragment 会在屏幕中保留 PreferenceControllers 的列表。在片段的 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 中添加一个新的偏好设置项。确保它具有独一无二的 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. 创建一个继承自 DashboardFragment 的新片段。
  2. DashboardFragmentRegistry 中定义其类别。

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

  3. 按照为此页面添加所需设置的步骤进行操作。要了解详情,请参阅实现部分。

验证

  • 在“设置”部分运行 robolectric 测试。应通过所有现有测试和新测试。
  • 编译并安装“设置”,然后手动打开正在修改的页面。该页面应立即更新。