Preference库:为你的应用快速搭建一个「开发者选项」
前言
作为Android开发者,想必对Android系统的「开发者模式」不会陌生,因为其既是开启真机调试的必经途径,同时也提供了很多有用的选项,用以模拟不同环境来协助调试应用。那么,你有没有想过,是否也可以为你的应用增加这样一个「开发者模式」,用以模拟特定场景、快速定位问题呢?本文将为你讲解,如何使用Preference库,为你的应用快速搭建一个「开发者选项」。
知识储备
AndroidX Preference Library
该库为我们管理了页面,并负责与存储空间交互,让我们可以只关注于如何去定义设置选项。
具体则是通过XML文件去定义层次架构的。
<PreferenceScreen
xmlns:app="http://schemas.android.com/apk/res-auto">
<SwitchPreferenceCompat
app:key="notifications"
app:title="Enable message notifications"/>
<Preference
app:key="feedback"
app:title="Send feedback"
app:summary="Report technical issues or suggest new features"/>
</PreferenceScreen>
展现出来的样式如下:
让我们先了解一下常见的Preference组件:
- Preference - 基本构建块。没有特别的交互效果。
- EditTextPreference - 点按会弹出包含文本字段的对话框,通过修改文本字段更改String值。
- ListPreference - 点按会弹出包含一列带有对应标签的单选按钮的对话框,通过选中项更改String值。
- MultiSelectListPreference - 点按会弹出包含一列带有对应标签的复选框的对话框,通过选中项更改一组String值。
- SeekBarPreference - 可通过拖动对应拖动条更改整数值。
- SwitchPreferenceCompat - 可通过与对应的开关微件互动或点按更改布尔值。
- CheckBoxPreference - 可通过与对应的复选框互动或点按更改布尔值。
再来了解一下这些Preference组件部分重要的属性:
通用属性:
- title:标题。
- summary:摘要。可用于对设置选项的补充说明。
- key:唯一键。可用于找到指定的设置选项。
- enabled:可否互动。可用于针对不同权限的调试人员决定是否开放功能。
PreferenceCategory 属性:
- initialExpandedChildrenCount:表示 PreferenceGroup 中所显示子级的最大数量。系统会收起所有多余子级,用户可以点按展开按钮查看。
ListPreference / MultiSelectListPreference 属性:
- entries:向用户显示的列表条目的String数组。
- entryValues:与列表条目对应的值的String数组。需确保两个数组的长度一致。
默认情况下,Preference 使用 SharedPreferences 保存值,所用键与为 Preference 设置的键相同。Preference 库使用不公开的 SharedPreferences 实例,因此只有本应用可以访问。
如果屏幕上就有多个相关的 Preferences,可以使用PreferenceCategory进行分组:
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto"> <PreferenceCategory app:key="notifications_category" app:title="Notifications"> <SwitchPreferenceCompat app:key="notifications" app:title="Enable message notifications"/> </PreferenceCategory> <PreferenceCategory app:key="help_category" app:title="Help"> <Preference app:key="feedback" app:summary="Report technical issues or suggest new features" app:title="Send feedback"/> </PreferenceCategory> </PreferenceScreen> <PreferenceScreen
xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceCategory
app:key="notifications_category"
app:title="Notifications">
<SwitchPreferenceCompat
app:key="notifications"
app:title="Enable message notifications"/>
</PreferenceCategory>
<PreferenceCategory
app:key="help_category"
app:title="Help">
<Preference
app:key="feedback"
app:summary="Report technical issues or suggest new features"
app:title="Send feedback"/>
</PreferenceCategory>
</PreferenceScreen>
分组后的展示效果如下:
代码实践
既然是要模仿Android系统的「开发者模式」,那就干脆模仿得更彻底点。于是我们制定了通过在「关于」页面连续点击版本号,来进入调试选项界面的方案:
/**
* 调试器
*/
object Debugger {
/** 快速点击次数 */
private var clickCount = 0
/** 最后一次快速点击时间戳 */
private var lastClickTime: Long = 0
/**
* 快速点击监听
*/
fun onQuickClick(activity: AppCompatActivity) {
val currentTime = System.currentTimeMillis()
if (currentTime - lastClickTime < 500) {
clickCount++
if (clickCount > 10) {
clickCount = 0
DebugLoginDialogFragment.newInstance().show(activity.supportFragmentManager, "")
}
} else {
clickCount = 0
}
lastClickTime = System.currentTimeMillis()
}
}
当然,为了同时保证应用的安全性,我们还需要增加调试人员账号的验证步骤:
/**
* 调试模式登录验证弹框
*/
class DebugLoginDialogFragment : DialogFragment() {
companion object {
fun newInstance() = DebugLoginDialogFragment()
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let {
isCancelable = false // 需要对fragment同样设置cancelable
val builder = AlertDialog.Builder(it, android.R.style.Theme_Material_Light_Dialog)
val view = requireActivity().layoutInflater.inflate(R.layout.dialog_debug_login, null)
val username = view.findViewById<EditText>(R.id.username)
val password = view.findViewById<EditText>(R.id.password)
builder.setView(view)
.setTitle("验证身份")
.setPositiveButton("确定",
DialogInterface.OnClickListener { _, _ ->
// TODO 具体验证方式由接入方选择
if ("admin" == username.text.toString()
&& "123456" == password.text.toString()
) {
context?.let { it -> DebugActivity.startActivity(it) }
return@OnClickListener
}
})
.setNegativeButton("取消", null)
.setCancelable(false)
builder.create()
} ?: throw IllegalStateException("Activity cannot be null")
}
}
至于调试页面的搭建,Android Studio新建Activity时是提供了相应的模板的,为了达到我们“快速搭建”的目的,可以直接使用此方式:
我们重点关注root_preferences.xml文件,通过XML去定义我们的调试选项。
1.区分与调试选项交互的场景,以确定使用哪一种 Preference组件:
以我正在开发的应用举例,
·切换环境:单选列表项场景,使用ListPreference
·开启日志调试:开关场景,使用SwitchPreferenceCompat
·进入日志调试页:点击跳转场景,使用Preference
·修改应用版本:文本修改场景,使用EditTextPreference
2.对调试选项进行分组,根据关联性划分类别:
比如「开启日志调试」和「进入日志调试页」均归属日志类别,因此划分为同一组。
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceCategory app:title="线路">
<ListPreference
app:entries="@array/app_status"
app:entryValues="@array/app_status_values"
app:key="app_status"
app:title="切换环境" />
</PreferenceCategory>
<PreferenceCategory app:title="日志">
<SwitchPreference
app:key="log_debuggable"
app:summaryOff="已关闭,日志记录将只写入文件"
app:summaryOn="已开启,日志记录将允许输出到屏幕"
app:title="开启日志调试" />
<Preference
app:key="logcat_page"
app:title="进入日志调试页" />
</PreferenceCategory>
<PreferenceCategory app:title="内置浏览器">
<EditTextPreference
app:key="dummy_app_version"
app:title="修改应用版本"
app:useSimpleSummaryProvider="true" />
</PreferenceCategory>
</PreferenceScreen>
最终呈现出来的界面如下:
3.调用findPreference()找到指定键的调试选项,并监听选项值的的更改:
4.读取具体的选项值,执行相应的业务处理:
class DebugFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.root_preferences, rootKey)
initConfigControl()
}
private fun initConfigControl() {
initLineConfigControl()
initLogConfigControl()
initBrowserConfigControl()
}
private fun initLineConfigControl() {
findPreference<ListPreference>("app_status")?.setOnPreferenceChangeListener { _, newValue ->
val newStatus = Integer.valueOf(newValue as String)
// TODO 切换线路业务逻辑
true
}
}
private fun initLogConfigControl() {
findPreference<SwitchPreference>("log_debuggable")?.setOnPreferenceChangeListener { _, newValue ->
val isDebugMode = newValue as Boolean
// TODO 开启日志调试业务逻辑
true
}
findPreference<Preference>("logcat_page")?.setOnPreferenceClickListener {
// TODO 进入日志调试页业务逻辑
true
}
}
private fun initBrowserConfigControl() {
findPreference<EditTextPreference>("dummy_app_version")?.setOnPreferenceChangeListener { _, newValue ->
// TODO 修改应用版本业务逻辑
true
}
}
}
以上示例代码均已上传到GitHub,感觉对你有帮助的话给个Star吧~
https://github.com/madchan/AppDeveloperOption
总结
Preference库原本是为Android应用搭建「偏好设置」页面而设计的,然而现实里却因为很难做到与应用内其他页面样式的统一而很少使用。但对于以上快速搭建「开发者选项」的需求却甚有奇效,让我们可以只关心配置变化而无需关注界面和存储。怎么样,整套流程下来确实很简单吧,跟着我一起实践下吧~
参考
https://developer.android.google.cn/guide/topics/ui/settings?hl=zh-cn