Open fWX228941 opened 2 months ago
2.UI框架
PreferenceFragment本质还是Fragment ,在差异化小的基础上,如何统一化preference布局? 1)办法一:在配置文件中,声明每个Preference的布局
2)办法二:利用主题统一配置
3)具体实例 怎么统一修改Dialog的风格?
1)应用定义自己的主题
2)在自定义主题中定义dialog的style
3)在自定义dialog的style中定义dialog的layout
4)代码中用标准API创建对话框即可
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
2、在平台化设置的AndroidManifest中监听自定义的action
优缺点: 1.每一个action都只会拉起平台化设置,不会与原生设置冲突 2.需要修改ROM,且修改不可逆,如果后续决定不用平台化设置,需要撤销修改 3.如果平台化设置也监听相同的action,跳转的时候弹出选择界面,让用户选择跳转哪个应用,这是缺点,将平台化设置的intent-filter优先级设置的比原生设置高,保证同一个action多个响应变成只响应平台化设置
在PKMS中进行过滤,让原生设置屏蔽接受隐式广播 1、通过PackageManager的queryIntentActivities接口查询某个action有哪些组件监听 2、遍历查询结果,将非平台化设置的组件通过PackageManager的setComponentEnabledSetting接口禁用掉。禁用完后,相当于只有平台化设置才监听这个action,那么也就不存在多个响应的问题
屏蔽问题解决思路: 一个是SystemUI根据自己的Feature屏蔽一些平台化设置不支持的功能,不够灵活需要编译ROM版本 一个是平台化设置定义ContentProvider,对外提供查询接口,让其他应用知道自己支持什么不支持什么,其他应用根据查询结果做动态处理,实现起来比较复杂,需要设计好查询接口,但非常灵活,SystemUI只需要写通用的逻辑
原生设置入口的解决思路: 1.通过暗码打开原生设置 2.通过连续点击平台化设置的Android版本打开原生设置 平台化的思路: Settingslib.jar存在版本差异,如果不依赖Settingslib.jar,或者处理好不同版本之间Settingslib.jar的差异,说不定可以做成平台化的设置,原生设置都是一个Activity呈现一个Fragment,其实可以尝试一个Activity展示所有Fragment,这样做对低内存的终端特别友好,不会出现跳转时卡顿的现象,当然代价就是要维护好所有Fragment的状态,否则就会出现奇奇怪怪的显示问题。
类图
设置的PreferenceFragment ,缺点: · DeviceInfoSettings的职责过于复杂,即要负责初始化,又要处理各个菜单的响应。
· 难于维护,修改某个菜单的功能,可能会不小心修改了其他菜单的逻辑
· 可读性差,一个界面的代码,动不动就几百行
把DeviceInfoSettings 进行拆分,重点是业务拆分为各式各样的Controller AbstractPreferenceController,抽象类,定义了Preference的一些回调函数
其实也可以把Prefence 作为一个监听者来监听 ,自定义View 有点多此一举了
业务拆分 - 关于手机
1.设置框架 1.1.版本差异 1)P之前:Activity+PreferenceFragment+Preference 【Activity负责界面跳转,PreferenceFragment负责显示具体的业务界面与对应的业务逻辑,Preference(通过xml的形式)提供每个选项的基础信息】
2)P以后对PreferenceFragment进行了UI和业务逻辑的分离
2.核心代码
getPreferenceScreenResId加载整体布局,通过createPreferenceControlllers方法,将每个Preference和其对应的Controller进行绑定
原生的只有androidx包下来的才支持生命周期,PreferenceFragment是不支持的,所以需要自己添加生命周期,这就有了
设置里面的这个可观察带生命周期的Fragment就是标准写法
基类,统一管理标题栏与导航栏,堆栈,以及提供一些包含Activity的方法,有借鉴意义
基本布局preference_list_fragment
在主题中添加布局
Controller 监听lifecycle
比如设置
本质就是典型的MVP模式,区别就是controller 一定是绑定一个Prefrence,里面是有View的引用的,controller来控制Prefrence的刷新
//dusplayPrefernce 就是绑定的UI 构造函数中的key是唯一的 确定绑定关系
让controller能感知到PreferenceFragment生命周期的变化,onStart 就意味着PreferenceFragment位于前台,那这个时候就可以监听业务变化了,onStop快销毁的时候,就放弃监听
具体的业务,相互可以监听
然后由Preference来提供UI接口
OnBindViewHolder 就是首次加载 updateView 就是外面触发刷新,所以controller这个还可以抽出来
可定制是否可用,是否显示(不支持动态么 应该也是可以支持的),还有一个问题是SystemUpgradPrefencerController后台刷新数据后,因为添加了PrefenceFragment的生命周期的监听,所以当Prefence位于前台时就会刷新View。 由PrefenceFragment负责刷新Prefence 委托给PrefencerController 来刷新 感觉就跟自定义View + Fragment +ViewModel 是一模一样的
包括人机交互都是可以交给Viewmodel 比较灵活
优点:如果要修改某个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
updatePreference()代表界面刷新,这里可以检查Preference的状态是否有变化
displayPreference()代表Preference已经显示出来了,这里可以做一些初始化,一个key代表一个preference,布局统一由PreferenceScreen管理
handlePreferenceTreeClick()代表Preference被点击了,这里可以做具体的人机交互逻辑
接口定义完后,由一个类来DashboardFragment统一管理回调,
Controller可以通过配置文件也可以通过代码来动态添加
每次更新都是重新绘制,先删除View 然后添加View
首次加载布局
getPreferenceScreenResId()方法,由子类实现此方法,即子类决定加载哪个配置文件
addPreferencesFromResource()加载配置文件后,回调Controller的displayPreference方法
本质还是生命周期 只不过如果用ViewModel
onResume()依次回调Controller的updatePreference()方法 更新界面
前台刷新,由controller来控制刷新,findPreferenceKey key必须唯一,这个controller 其实本质就是ViewModel ,自带生命周期来控制刷新
DeviceInfoSettings,是一个具体的关于手机界面,
onPreferenceTreeClick()回调对应Controller的handlePreferenceTreeClick()方法
处理按键,封装是因为一些合法性检查,一个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资源。
一级菜单对应的xml资源,一级菜单一般是用来跳转到二级菜单,不会有自己的业务逻辑,所以只需要指定要跳转的Fragment即可
二级菜单以蓝牙为例,新建一个package,将所有与蓝牙相关的都放到这里,方便后期维护
多级菜单,
这里只使用了两个Preference,一个负责打开和关闭蓝牙,由BluetoothSwitchPreferenceController负责蓝牙开关的业务逻辑,一个负责显示终端的蓝牙名称,由BluetoothDeviceNamePreferenceController负责更新蓝牙名称 同一个业务,一个简单的逻辑都是要交给controller,而且是跟UI耦合很紧,其他UI用不上,所以还可以分化