Android 8.0 为设置应用引入了一种新的信息架构,以简化设置的组织方式,并使用户能够更轻松地快速找到用于自定义 Android 设备的设置。Android 9 引入了一些改进,以提供更多的设置功能和更简单的实现。
示例和源代码
设置中的大多数页面目前都使用新的框架实现。一个很好的例子是 DisplaySettings:packages/apps/Settings/src/com/android/settings/DisplaySettings.java
以下列出了重要组件的文件路径
- CategoryKey:
packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
- DashboardFragmentRegistry:
packages/apps/Settings/src/com/android/settings/dashboard/DashboardFragmentRegistry.java
- DashboardFragment:
packages/apps/Settings/src/com/android/settings/dashboard/DashboardFragment.java
- AbstractPreferenceController:
frameworks/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 中的静态移动
- 找到原始页面和目标页面的偏好设置 XML 文件。您可以从页面的
getPreferenceScreenResId()
方法中找到此信息。 - 从原始页面的 XML 中删除偏好设置。
- 将偏好设置添加到目标页面的 XML。
- 从原始页面的 Java 实现中删除此偏好设置的
PreferenceController
。通常它在createPreferenceControllers()
中。控制器可以直接在 XML 中声明。注意:偏好设置可能没有
PreferenceController
。 - 在目标页面的
createPreferenceControllers()
中实例化PreferenceController
。如果PreferenceController
在旧页面的 XML 中定义,则也在新页面的 XML 中定义它。
Android 9 中的动态移动
- 查找原始页面和目标页面托管的类别。您可以在
DashboardFragmentRegistry
中找到此信息。 - 打开包含您需要移动的设置的
AndroidManifest.xml
文件,并找到表示此设置的 Activity 条目。 - 将 activity 的元数据值
com.android.settings.category
设置为新页面的类别键。
Android 8.x 版本中的静态移动
- 找到原始页面和目标页面的偏好设置 XML 文件。 您可以从页面的
- 删除原始页面 XML 中的偏好设置。
- 将偏好设置添加到目标页面的 XML。
- 从原始页面的 Java 实现中删除此偏好设置的
PreferenceController
。通常它在getPreferenceControllers()
中。 - 在目标页面的
getPreferenceControllers()
中实例化PreferenceController
。
getPreferenceScreenResId()
方法中找到此信息。注意:偏好设置可能没有 PreferenceController
。
Android 8.x 版本中的动态移动
- 查找原始页面和目标页面托管的类别。您可以在
DashboardFragmentRegistry
中找到此信息。 - 打开包含您需要移动的设置的
AndroidManifest.xml
文件,并找到表示此设置的 Activity 条目。 - 更改 activity 的元数据值
com.android.settings.category
,将该值设置为指向新页面的类别键。
在一个页面中创建新的偏好设置
如果偏好设置静态列在原始页面的偏好设置 XML 文件中,请按照下面的静态程序进行操作。否则,请按照动态程序进行操作。
创建静态偏好设置
- 找到页面的偏好设置 XML 文件。您可以从页面的 getPreferenceScreenResId() 方法中找到此信息。
- 在 XML 中添加新的 Preference 项。确保它具有唯一的
android:key
。 - 在该页面的
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"/>
- 在 Android 8.x 中以及可选的 Android 9 中,在该页面的
创建动态偏好设置
- 查找原始页面和目标页面托管的类别。您可以在
DashboardFragmentRegistry
中找到此信息。 - 在
AndroidManifest
中创建一个新的 Activity - 向新的 Activity 添加必要的元数据以定义设置。将元数据值
com.android.settings.category
设置为步骤 1 中定义的相同值。
创建一个新页面
- 创建一个新的 Fragment,继承自
DashboardFragment
。 - 在
DashboardFragmentRegistry
中定义其类别。注意:此步骤是可选的。如果在此页面中不需要任何动态偏好设置,则无需提供类别键。
- 按照步骤为此页面添加所需的设置。有关更多信息,请参阅实现部分。
验证
- 在设置中运行 robolectric 测试。所有现有和新测试都应通过。
- 构建并安装设置,然后手动打开正在修改的页面。页面应立即更新。