RKAndroid11-系统设置新增开关选项
场景:在Room 开发中
- 我们在日常开发的应用里面有一些信息、开关 需要在系统设置里面显示
- 部分业务逻辑开关需要在系统设置里面可以直接控制
文章目录
- 需求
- 一、参考资料
- 二、实现方案
- 三、实现思路
- 四、基本UI常识介绍
- 基本概念
- Preference 系统基于以下几个核心类:
- Preference 类型
- CheckBoxPreference
- EditTextPreference
- SwitchPreference
- PreferenceFragment
- 五、源码分析
- 找到需要添加UI元素的Fragment 及在哪个界面添加需求的界面 -SystemDashboardFragment
- 布局中新增UI元素- system_dashboard_fragment
- 核心字段 理解:
- 自定义控制器 - CloudBootParentPreferenceController
- 自定义跳转的Fragment - CloudBootSettings
- 跳转的Fragment 的布局 - cloud_boot_settings
- 控制子界面控制器-CloudBootPreferenceController
- 相关string字段-strings.xml
- 路由 -SettingsGateway
- 总结
需求
系统设置新增开关选项控制页面,比如 在系统设置里面添加一个 应用自启动开关。
一、参考资料
MTKAndroid12-13-开机应用自启功能实现
MTK-删除设置首页菜单项
Android 11 Settings源码入门
Android Preference简单介绍
RK-Android11-系统增加一个属性值
核心知识点:
- 理解Android 基本架构:代码逻辑、业务、UI层面 架构基本理解
- Settings App 用到的 Preference 这一套:Preference 、PreferenceScreen 、PreferenceFragment 或 PreferenceActivity 等基本的UI元素理解和其子UI元素及属性了解。
二、实现方案
新增文件:
/packages/apps/Settings/src/com/android/settings/cloud/CloudBootParentPreferenceController.java
/packages/apps/Settings/src/com/android/settings/cloud/CloudBootPreferenceController.java
/packages/apps/Settings/src/com/android/settings/cloud/CloudBootSettings.java
/packages/apps/Settings/res/drawable-hdpi/icon_yun_diannao.png
/packages/apps/Settings/res/xml/cloud_boot_settings.xml
修改文件:
/packages/apps/Settings/AndroidManifest.xml
/packages/apps/Settings/res/values-zh-rCN/strings.xml
/packages/apps/Settings/res/xml/system_dashboard_fragment.xml
/packages/apps/Settings/src/com/android/settings/core/gateway/SettingsGateway.java
三、实现思路
这里还是把思路简要说一下
- 系统界面的布局中添加这个item 这里添加:system_dashboard_fragment.xml
- 基于Settings 的UI架构,在布局中添加UI元素,总要有Controller->CloudBootParentPreferenceController 和 对应的界面 CloudBootSettings 吧,当然这里有一些配置属性 key 、title、order、icon、keywords 等
- 配置,可以外部访问,其实就是一个校验、网关的作用,源码架构决定。 SettingsGateway
-
四、基本UI常识介绍
基本概念
Preference 系统基于以下几个核心类:
- Preference - 所有设置项的基类
- PreferenceScreen - 设置界面的容器,相当于一个设置页面
- PreferenceFragment 或 PreferenceActivity - 用于承载 Preference 界面的组件
Preference 类型
CheckBoxPreference
-
复选框类型的设置项
-
保存 boolean 值
<CheckBoxPreferenceandroid:key="wifi_enabled"android:title="启用WiFi"android:summary="打开或关闭WiFi连接"android:defaultValue="true" />
EditTextPreference
- 可编辑文本的设置项
- 保存字符串值
<EditTextPreferenceandroid:key="user_name"android:title="用户名"android:summary="请输入您的用户名"android:dialogTitle="输入用户名"android:defaultValue="默认用户" />
SwitchPreference
- 开关类型的设置项(Android 4.0+)
- 保存 boolean 值
<SwitchPreferenceandroid:key="notifications_enabled"android:title="通知"android:summary="启用或禁用应用通知"android:defaultValue="true" />
PreferenceFragment
现代 Android 应用通常使用 PreferenceFragment 来显示设置界面
public class SettingsFragment extends PreferenceFragmentCompat {@Overridepublic void onCreatePreferences(Bundle savedInstanceState, String rootKey) {setPreferencesFromResource(R.xml.preferences, rootKey);// 可以在这里添加Preference变更监听器Preference preference = findPreference("key");preference.setOnPreferenceChangeListener((pref, newValue) -> {// 处理变更return true; // 返回true保存变更});}
}
暂且只 简单介绍这些,方便理解, Settings 里面的 UI 子元素蛮多的,用到的时候就自然理解了。
五、源码分析
找到需要添加UI元素的Fragment 及在哪个界面添加需求的界面 -SystemDashboardFragment
上面有所介绍,这里先找到对应的页面,当我们进入系统页面,会有这些提示:
找到这个Fragment最终的目的是找到它的 xml 布局,如下:
protected int getPreferenceScreenResId() {return R.xml.system_dashboard_fragment;}
布局中新增UI元素- system_dashboard_fragment
<Preferenceandroid:key="reset_dashboard"android:title="@string/reset_dashboard_title"android:summary="@string/reset_dashboard_summary"android:icon="@drawable/ic_restore"android:order="-50"android:fragment="com.android.settings.system.ResetDashboardFragment"settings:controller="com.android.settings.system.ResetPreferenceController"/><!-- add start --><Preferenceandroid:key="cloud_boot_summary"android:title="@string/cloud_boot_title"android:fragment="com.android.settings.cloud.CloudBootSettings"android:order="-110"android:icon="@drawable/ic_restore"settings:controller="com.android.settings.cloud.CloudBootParentPreferenceController"settings:keywords="@string/keywords_cloud_boot"/>
<!-- add end --><Preferenceandroid:key="pointer_speed_summary"android:title="@string/pointer_speed"android:fragment="com.android.settings.cloud.PointerSpeedSettings"android:order="-110"settings:controller="com.android.settings.cloud.PointerSpeedParentPreferenceController"settings:keywords="@string/keywords_cloud_boot"/>
核心字段 理解:
字段 | 类型 | 必需 | 说明 示例 | 特点 |
---|---|---|---|---|
key | String | 是 | 唯一标识符 “wifi_enabled” | 用于在 SharedPreferences 中存储和检索值;必须唯一;通过 findPreference(key) 方法可以查找特定 Preference |
title | String | 推荐 | 显示的主标题 “Wi-Fi” | 显示在 Preference 的主要位置;通常简短明了,直接说明设置项功能;可以是字符串资源或直接文本 |
order | int | 可选 | 显示顺序 100 | 数值越小,显示位置越靠上 ;如果不指定,将按照 XML 中的声明顺序显示 ;可用于动态调整 Preference 顺序 |
icon | Drawable | 可选 | 左侧图标 @drawable/ic_wifi | |
controlle | Class | 系统级 动态控制逻辑 | "com.android.settings.wifi.WifiPrefere | 用于系统设置中的高级控制逻辑;必须是 com.android.settingslib.core.AbstractPreferenceController 的子类;通常用于动态控制 Preference 的可用性/可见性 |
keywords | String | 可选 | 搜索关键词 “wireless,network” | 用于系统设置的搜索功能;多个关键词用逗号分隔;应包含相关术语和同义词 |
fragment | Class | 可选 | 跳转的Fragment "com.android.settings.wifi.Wif | 用于实现设置项的深层导航;必须是全限定类名(包含包名)通常用于打开子设置页面 |
summary | String | 可选 | 描述文本 | “Manage wireless networks” |
intent Intent | 可选 | 替代fragment的跳转方式 | 定义Intent动作 |
自定义控制器 - CloudBootParentPreferenceController
说白了就是当前 在 系统面板里面显示的内容,这里根据属性 显示不同的 字符串、文本内容呀。
package com.android.settings.cloud;
import android.content.Context;
import android.provider.Settings;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import android.os.SystemProperties;
/*** Parent menu summary of media controls settings*/
public class CloudBootParentPreferenceController extends BasePreferenceController {private String clouddesk;public CloudBootParentPreferenceController(Context context, String key) {super(context, key);clouddesk = SystemProperties.get("persist.sys.fise.packagename","com.ctg.itrdc.clouddesk");}@Overridepublic int getAvailabilityStatus() {return AVAILABLE;}@Overridepublic CharSequence getSummary() {int summary;if (clouddesk.equals(SystemProperties.get("persist.sys.launcher.packagename", ""))) {summary = R.string.cloud_boot_start;} else {summary = R.string.cloud_boot_stop;}return mContext.getText(summary);}
}
自定义跳转的Fragment - CloudBootSettings
package com.android.settings.cloud;
import android.app.settings.SettingsEnums;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.search.SearchIndexable;/*** Media control settings located in the sound menu*/
@SearchIndexable
public class CloudBootSettings extends DashboardFragment {private static final String TAG = "CloudBootSettings";@Overrideprotected int getPreferenceScreenResId() {return R.xml.cloud_boot_settings;}@Overrideprotected String getLogTag() {return TAG;}@Overridepublic int getMetricsCategory() {return 6;}public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =new BaseSearchIndexProvider(R.xml.cloud_boot_settings);
}
这里核心内容还是 xml 相关布局内容 cloud_boot_settings
@Overrideprotected int getPreferenceScreenResId() {return R.xml.cloud_boot_settings;}
跳转的Fragment 的布局 - cloud_boot_settings
<PreferenceScreenxmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:title="@string/cloud_boot_title"><SwitchPreferenceandroid:key="cloud_boot_resume_switch"android:title="@string/cloud_boot_summary"android:summary="@string/cloud_boot_resume_description"app:keywords="@string/keywords_cloud_boot"app:controller="com.android.settings.cloud.CloudBootPreferenceController"app:allowDividerAbove="true" /></PreferenceScreen>
这里面的 UI子字段属性暂不协助理解,自己可以查询下,见名知意的了,看多了其实很熟悉了的。
控制子界面控制器-CloudBootPreferenceController
package com.android.settings.cloud;import static android.provider.Settings.Secure.MEDIA_CONTROLS_RESUME;import android.content.Context;
import android.provider.Settings;import com.android.settings.core.TogglePreferenceController;
import android.os.SystemProperties;
/*** Toggle for media controls settings*/
public class CloudBootPreferenceController extends TogglePreferenceController {private String clouddesk;public CloudBootPreferenceController(Context context, String key) {super(context, key);clouddesk = SystemProperties.get("persist.sys.fise.packagename","com.ctg.itrdc.clouddesk");}@Overridepublic boolean isChecked() {return clouddesk.equals(SystemProperties.get("persist.sys.launcher.packagename", ""));}@Overridepublic boolean setChecked(boolean isChecked) {if (isChecked)SystemProperties.set("persist.sys.launcher.packagename", clouddesk);elseSystemProperties.set("persist.sys.launcher.packagename", "");return true;}@Overridepublic int getAvailabilityStatus() {return AVAILABLE;}
}
其实就是一个设置属性 、 默认 判断 CheckBox 状态的 控制器。
相关string字段-strings.xml
<string name="cloud_boot_title">云电脑自启动</string><string name="cloud_boot_summary">云电脑自启动开关</string><string name="cloud_boot_resume_description">选择云电脑是否开机自启动</string><string name="cloud_boot_stop">开机不自启动</string><string name="cloud_boot_start">开机自启动</string><string name="keywords_cloud_boot">云电脑</string>
路由 -SettingsGateway
/*** A list of fragment that can be hosted by SettingsActivity. SettingsActivity will throw a* security exception if the fragment it needs to display is not in this list.*/public static final String[] ENTRY_FRAGMENTS = {AdvancedConnectedDeviceDashboardFragment.class.getName(),CreateShortcut.class.getName(),WifiSettings.class.getName(),// add start CloudBootSettings.class.getName()//add end };
如同 方法注释,一个校验功能,如果不在路由里面配置,这个控制界面Fragment是无法展示出来的,非法。
总结
- 这里就是在Settings App 里面添加一个开关功能 。 这个扩展性很大,比如:自启应用、自启服务 等所有跟开关相关的业务都可以借助这个来实现。
- 了解一下开关在设置里面如何添加,进而了解一下Settigns 架构、逻辑、业务
- 结合属性来存储开关基本值的一个业务逻辑
- 这个开关逻辑的业务实现了,你会发现 列表、展示 相关的业务,在Settigns 里面的需求也就一样了,没多少难度。 Settings 本身没那么复杂,接触多了发现不难。