fWX228941 / project

all by myself
1 stars 0 forks source link

设置的设计思想 #65

Open fWX228941 opened 2 months ago

fWX228941 commented 2 months ago

1.设置框架 1.1.版本差异 1)P之前:Activity+PreferenceFragment+Preference 【Activity负责界面跳转,PreferenceFragment负责显示具体的业务界面与对应的业务逻辑,Preference(通过xml的形式)提供每个选项的基础信息】

2)P以后对PreferenceFragment进行了UI和业务逻辑的分离

2.核心代码

Image

getPreferenceScreenResId加载整体布局,通过createPreferenceControlllers方法,将每个Preference和其对应的Controller进行绑定

Image

原生的只有androidx包下来的才支持生命周期,PreferenceFragment是不支持的,所以需要自己添加生命周期,这就有了

Image

Image

设置里面的这个可观察带生命周期的Fragment就是标准写法

Image

基类,统一管理标题栏与导航栏,堆栈,以及提供一些包含Activity的方法,有借鉴意义

Image

Image

Image

基本布局preference_list_fragment

Image

在主题中添加布局

Image

Image

Controller 监听lifecycle

比如设置

Image

Image

本质就是典型的MVP模式,区别就是controller 一定是绑定一个Prefrence,里面是有View的引用的,controller来控制Prefrence的刷新

Image

//dusplayPrefernce 就是绑定的UI 构造函数中的key是唯一的 确定绑定关系

Image

Image 让controller能感知到PreferenceFragment生命周期的变化,onStart 就意味着PreferenceFragment位于前台,那这个时候就可以监听业务变化了,onStop快销毁的时候,就放弃监听

Image

具体的业务,相互可以监听

Image

然后由Preference来提供UI接口

Image

OnBindViewHolder 就是首次加载 updateView 就是外面触发刷新,所以controller这个还可以抽出来

Image

Image

可定制是否可用,是否显示(不支持动态么 应该也是可以支持的),还有一个问题是SystemUpgradPrefencerController后台刷新数据后,因为添加了PrefenceFragment的生命周期的监听,所以当Prefence位于前台时就会刷新View。 由PrefenceFragment负责刷新Prefence 委托给PrefencerController 来刷新 感觉就跟自定义View + Fragment +ViewModel 是一模一样的

包括人机交互都是可以交给Viewmodel 比较灵活

Image

优点:如果要修改某个Preference,根本不需要动DeviceInfoSettings,只需要修改对应的Controller,而且因为每个Preference都有自己对应的Controller,所以也不需要担心因为修改了这个Preference,而影响了别的Preference

简易版的设置应用框架 Activity+PreferenceFragment+AbstractPreferenceController+Preference+SettingsLib.jar(framework/base/packages/SettingsLib)

Activity:负责fragment之间的跳转,如果在xml中通过android:fragment指定了某个Fragment的类名,点击该Preference时,PreferenceFragment会回调OnPreferenceStartFragmentCallback接口中的onPreferenceStartFragment方法,所以Activity实现这个接口后,就可以知道被点击的Preference需要跳转到哪个Fragment,进而显示这个Fragment

Image

Image

updatePreference()代表界面刷新,这里可以检查Preference的状态是否有变化

Image

displayPreference()代表Preference已经显示出来了,这里可以做一些初始化,一个key代表一个preference,布局统一由PreferenceScreen管理

Image

handlePreferenceTreeClick()代表Preference被点击了,这里可以做具体的人机交互逻辑

接口定义完后,由一个类来DashboardFragment统一管理回调,

Image

Controller可以通过配置文件也可以通过代码来动态添加

Image

每次更新都是重新绘制,先删除View 然后添加View

Image

Image

首次加载布局

Image

getPreferenceScreenResId()方法,由子类实现此方法,即子类决定加载哪个配置文件

addPreferencesFromResource()加载配置文件后,回调Controller的displayPreference方法

本质还是生命周期 只不过如果用ViewModel

onResume()依次回调Controller的updatePreference()方法 更新界面

Image

前台刷新,由controller来控制刷新,findPreferenceKey key必须唯一,这个controller 其实本质就是ViewModel ,自带生命周期来控制刷新

Image

DeviceInfoSettings,是一个具体的关于手机界面,

onPreferenceTreeClick()回调对应Controller的handlePreferenceTreeClick()方法

Image

处理按键,封装是因为一些合法性检查,一个key(View) 绑定一个controller 清晰一点

通常我们的做法是在onResume(onStart)的时候注册蓝牙状态变化的广播,接收广播来更新蓝牙状态,然后在onPause(onStop)的时候反注册。但BluetoothSwitchPreferenceController是死的,并没有onCreate,onResume,onPuase等生命周期回调,让controller具有生命周期可感知的特性 引入Lifecycle机制,让PreferenceController拥有自己的生命周期,Lifecycle是在Android O开始, Google提供的一套组件,采用观察者模式,可以让每一个LifeCycleObserver(生命周期观察者,通常是一个普通的类,没有自己的生命周期)都能感受到LifeCycleOwner (生命周期拥有者,通常是有生命周期的组件,比如Activity)的生命周期变化,相当于自己也拥有了生命周期。

在这里使用SettingsLib.jar中的ObservablePreferenceFragement,其本质上也是一个PreferenceFragement,只不过对Android的Lifecycle进行了二次封装,使用起来更方便,当ObservablePreferenceFragement的onResume的方法执行时,会通知所有的LifeCycleObserver并回调他们的onResume方法,依次类推。 所以可以让DashboardFragment继承于ObservablePreferenceFragement,在收集完所有的PreferenceController后,依次遍历,将实现了LifeCycleObserver接口的PreferenceController,加入观察者队列中。 ObservablePreferenceFragement

好处,稳定的部分不修改,不受业务影响,继承这些基类,来实现特有的需求。比如继承DashboardFragment,实现getPreferenceScreenResId方法,返回自己的xml资源。继承AbstractPreferenceController实现对界面上每个Preference的业务逻辑,核心精华类其实就是这两个 首先定义一个MainMenuSettings来作为设置的主界面也就是一级菜单界面,同时 继承于DashboardFragment,重写getPreferenceScreenResId方法,返回一级菜单的xml资源。

Image

一级菜单对应的xml资源,一级菜单一般是用来跳转到二级菜单,不会有自己的业务逻辑,所以只需要指定要跳转的Fragment即可

Image

二级菜单以蓝牙为例,新建一个package,将所有与蓝牙相关的都放到这里,方便后期维护

Image

多级菜单,

Image

这里只使用了两个Preference,一个负责打开和关闭蓝牙,由BluetoothSwitchPreferenceController负责蓝牙开关的业务逻辑,一个负责显示终端的蓝牙名称,由BluetoothDeviceNamePreferenceController负责更新蓝牙名称 同一个业务,一个简单的逻辑都是要交给controller,而且是跟UI耦合很紧,其他UI用不上,所以还可以分化

fWX228941 commented 2 months ago

2.UI框架

Image

PreferenceFragment本质还是Fragment ,在差异化小的基础上,如何统一化preference布局? 1)办法一:在配置文件中,声明每个Preference的布局

Image

2)办法二:利用主题统一配置

Image

Image

3)具体实例 怎么统一修改Dialog的风格?

Image

1)应用定义自己的主题

Image

2)在自定义主题中定义dialog的style

Image

3)在自定义dialog的style中定义dialog的layout

Image

4)代码中用标准API创建对话框即可

Image

alertDialogStyle是AlertController代码里默认读取的属性,不能随便写,要和代码保持一致 ,主题中的名字不能随便命名 parent Theme,自定义dialog布局的时候,布局内容可以随便写吗,要遵守什么规则?不能随便写,id必须和代码中的id保持一致,否则容易引起空指针,建议基于默认布局来修改样式 (方式还是非常麻烦的,主题是个思路) 3.平台化设置 背景 每个Android版本的原生系统设置功能和界面上差异都比较大,冗余的功能,集群终端用不上,耗费人力维护与调试,把所有集群终端能用到的设置功能重新实现并平台化, 与原生设置的共存关系 考虑到平时调试,或者一部分功能,仍需要由原生系统设置来完成,所以并不能完全替换掉原生设置,而是和平台化设置共存,但仅有平台化设置可见,所以需要要屏蔽跳转到原生设置的所有可能性,比如SystemUI中长按WLAN菜单,会跳转到原生设置,需要修改跳转到平台化设置,平台化设置是原生设置的阉割版,除了跳转界面,任何与原生设置交互的外部界面与外面应用都需要屏蔽掉,比如原生设置支持的功能,会在状态栏的下拉菜单中有相对应的快捷菜单,如果在平台化系统设置不支持某个功能,则相应的在状态栏中也需要屏蔽该菜单

原生设置和平台化设置共同编译到ROM里,只要把原生设置的AndroidManifest中去掉Launcher属性,这样原生设置就无法在Launcher中显示出来

思路 跳转问题解决思路: 从UI操作上看有以下几个可能: 1、状态栏下拉菜单中的各个快捷菜单,长按快捷菜单基本上都是跳转到原生设置 2、Launcher上长按某个应用,有查看应用信息的入口 3、应用权限申请的弹窗有跳转应用信息到的入口 4、应用crash的弹窗有跳转到应用信息的入口 5、各种系统服务的通知点击后能跳转到原生设置 6、应用想打开系统功能时,会提示用户跳转到对于的设置菜单 从代码实现上看,主要分两种: 1、绝大多数都是通过固定的action来进行跳转 2、小部分是通过固定的包名类名来跳转 显式与隐式跳转

重新定义各个跳转界面的action,大体思路下: 1、找到定义action 的地方,大部分在android.provider.Setings中以静态常量的定义,修改其定义,比如改成caltta.settings.BLUETOOTH_SETTINGS

Image

2、在平台化设置的AndroidManifest中监听自定义的action

优缺点: 1.每一个action都只会拉起平台化设置,不会与原生设置冲突 2.需要修改ROM,且修改不可逆,如果后续决定不用平台化设置,需要撤销修改 3.如果平台化设置也监听相同的action,跳转的时候弹出选择界面,让用户选择跳转哪个应用,这是缺点,将平台化设置的intent-filter优先级设置的比原生设置高,保证同一个action多个响应变成只响应平台化设置

在PKMS中进行过滤,让原生设置屏蔽接受隐式广播 1、通过PackageManager的queryIntentActivities接口查询某个action有哪些组件监听 2、遍历查询结果,将非平台化设置的组件通过PackageManager的setComponentEnabledSetting接口禁用掉。禁用完后,相当于只有平台化设置才监听这个action,那么也就不存在多个响应的问题

Image

屏蔽问题解决思路: 一个是SystemUI根据自己的Feature屏蔽一些平台化设置不支持的功能,不够灵活需要编译ROM版本 一个是平台化设置定义ContentProvider,对外提供查询接口,让其他应用知道自己支持什么不支持什么,其他应用根据查询结果做动态处理,实现起来比较复杂,需要设计好查询接口,但非常灵活,SystemUI只需要写通用的逻辑

原生设置入口的解决思路: 1.通过暗码打开原生设置 2.通过连续点击平台化设置的Android版本打开原生设置 平台化的思路: Settingslib.jar存在版本差异,如果不依赖Settingslib.jar,或者处理好不同版本之间Settingslib.jar的差异,说不定可以做成平台化的设置,原生设置都是一个Activity呈现一个Fragment,其实可以尝试一个Activity展示所有Fragment,这样做对低内存的终端特别友好,不会出现跳转时卡顿的现象,当然代价就是要维护好所有Fragment的状态,否则就会出现奇奇怪怪的显示问题。

类图

Image

Image

设置的PreferenceFragment ,缺点: · DeviceInfoSettings的职责过于复杂,即要负责初始化,又要处理各个菜单的响应。

· 难于维护,修改某个菜单的功能,可能会不小心修改了其他菜单的逻辑

· 可读性差,一个界面的代码,动不动就几百行

Image

把DeviceInfoSettings 进行拆分,重点是业务拆分为各式各样的Controller AbstractPreferenceController,抽象类,定义了Preference的一些回调函数

Image

Image

Image

Image

Image

其实也可以把Prefence 作为一个监听者来监听 ,自定义View 有点多此一举了

Image

业务拆分 - 关于手机