fWX228941 / project

all by myself
1 stars 0 forks source link

融合业务 #59

Open fWX228941 opened 6 months ago

fWX228941 commented 6 months ago

1

fWX228941 commented 6 months ago

融合应用UI方案设计 分层架构,设计原则 1)UI与业务分离 2)应用内部组件通信方式 3)不同模式下应用UI适配 4)不同屏幕下应用UI适配 应用UI架构 界面层UI Layer和数据层Data Layer 界面层职责 在屏幕上显示应用数据。当数据变化时,界面随之更新 在屏幕上呈现界面元素UI elements,例如fragment, activity或者其他View 向界面提供数据以及处理逻辑的状态容器StateHolder,ViewModel 维护数据一致性 数据层职责 负责应用业务逻辑,以及应用数据的存储,管理,更改 双层通信 为了让界面层的界面元素与状态容器职责分离,采用状态向下流动、事件向上流动的单向数据流UDF架构 界面状态更新流程 从下到上 ViewModel 存储界面UI状态XXXUIState,使用LiveData包装 从数据层获取到相应数据,经过转换后得到界面UI状态UI State,于是进行状态修改 界面UI元素(例如Activity),观察到ViewModel中的UI状态变化时,调用View等界面元素进行状态的更新 界面事件处理流程 从上到下 界面UI元素进行操作时,会调用 ViewModel 事件处理接口 ViewModel事件接口中会调用数据层业务接口,最后触发应用数据变化 触发相应ViewModel界面状态修改,最后进行界面的刷新 优点 1)数据一致性 ViewModel 保证两边数据的一致性,保证了界面UI状态只有一个数据来源,仅来自数据层Data Layer存储的数据或者界面本身作为数据的唯一来源,解决了同一条信息有多个来源带来的数据不一致问题 还真的是这样的 切换模式有很多数据来源 确实是可以解决很多问题 2)可维护性 界面UI元素不负责管理UI显示数据 所以任何数据逻辑都要转移到ViewModel中处理 只是通过观察UI状态变化,来进行显示信息 融合应用架构划分 1)GotaSystem作为独立的服务组件被引入,负责应用核心通信业务实现以及业务数据管理,同时提供相关服务使用,查询,事件上报等接口。(内部数据库使用ROOM实现) 2)提供给UI层ViewModel引用的各模块业务,例如呼叫CallManager, 设置SettingManager等,其是对GotaSystem接口进行了更复杂的逻辑封装,更适合UI层的ViewModel类使用 3)其每个界面至少关联一个ViewModel类,而ViewModel类至少关联一个XXUiState界面状态以及对应业务处理类。 难点是ViewModel的划分标准是什么 ViewModel 和UIstate 又是什么关系 应用UI层设计 1)定义和公开界面各种UI状态XXXUiState 把所有数据源定义全部抽离出来 在界面上向用户显示的信息即是界面状态 与UI 强关联显示的数据 界面是界面状态的呈现 界面状态由相应的ViewModel这种状态容器进行维护,通过LiveData这种可观察的数据容器包装,将界面状态公开 3)定义和实现界面UI各种事件接口 UI只是单纯的显示刷新了 ViewModel向界面UI提供相应事件接口,用于执行用户逻辑 原来是直接调用pdtsystem 现在又封装了一层 2)界面观察界面状态,进行UI更新 界面观察界面状态,进行UI更新,去观察该界面所有界面状态XXUiState变化,当该状态变化时,界面UI元素来呈现这些变化,实现了数据驱动UI界面 4)设计合适的业务逻辑类 ViewModel执行的所有业务逻辑,通过调用数据层相关业务类xxxManager来完成的 对于业务异步操作使用RxJava实现,用于替换Thread,AsyncTask 来简化异步任务操作,同时方便线程切换。 RxJava 这个实现 应用内部组件通信 组件之间可以利用ViewModel实例进行通信,实现数据共享 可通过构造的ViewModelStoreOwner作用域来实现Activity,Fragment之间数据共享 如果Owner是某个Activity,则从该Activity获取的所有Fragment都拥有相同的ViewModel实例,如果Owner是某个ApplicationInstance实例,则整个应用都可以获取到这ViewModel实例 列表项点击时,调用ViewModel记录当前选中的项index, 当进入联系人详情界面时,就可以通过ViewModel拿到该index对应的联系人信息 例如联系人列表ContactListFragment,点击某个联系人,进入联系人详情界面ContactInfoFragment,可以使用一个Activity作用域的ViewModel 数据共享 使用LiveEventBus,RxBus这些消息总线,进行线程之间,UI界面层各组件之间,数据层与UI层之间的消息通信 LiveEventBus利用了LiveData能够感知Activity等生命周期,不需手动取消消息订阅,可避免出现内存泄露,同时消息接收是在主线程,可直接用于界面UI更新。对于RxBus可以使用线程分发 例如新建短信界面,从联系人界面选择某个联系人,进行发送。新建短信界面,订阅选择的联系人消息 数据层的工作模式,通话状态等系统事件变化,通过RxBus将变化消息发送给小部件,联系人等UI层界面 界面层ViewModel与数据层Model类之间通信,数据层提供异步接口返回LiveData, RxJava Observable对象,替换回调方式,可以方便异步处理结果切换到主线程更新UI 使用ROOM数据库,异步查询数据层通话记录,其接口返回LiveData 用RxJava则接口返回的Observable对象 ViewModel调用Model的loadCallLogs方法,观察Observable, 主线程更新UI 界面Fragment化管理 Activity页面跳转会占用更多内存,跳转不平滑 Activity需要显示在Launcher图标的 Activity只作为页面Fragment的显示容器 实现页面模块化以及屏幕适配 Fragment切换是轻量级 为了更好的统一管理页面Fragment之间的跳转,返回栈,使用Jetpack的Navigation组件库来开发 根据交互需求,划分各页面UI Fragment 例如短信功能Activity,包含短信列表MessageListFragment, 新建消息MessageNewFragment,短信详情MessageInfoFragment 设计Fragment页面跳转导航图 通过Android Studio的Navigation Editor 或直接修改配置文件nav_graph.xml配置操作id,destination 根据条件,跳转Fragment时,执行NavController的导航操作action即可 不同模式下UI适配 在不同工作模式下,应用一些UI界面是有差异的,例如PDT常规,集群模式下,小部件,设置项等显示都是不同的 UI层的界面元素,ViewModel界面状态,数据层业务接口三部分都需要进行适配 工作模式发生变化,则显示的界面元素,界面显示所需数据,界面事件处理逻辑等都不同 数据层Model提供的业务接口,需要多模式支持(具体看GotaSystem实现) 不同模式下,界面显示的状态数据是不同的。因此ViewModel维护的界面UI状态XXXUiState, 需要包含工作模式workMode。ViewModel监听工作模式变化,重新调用数据层Model接口获取该模式下的数据,然后更新XXXUiState,触发界面UI状态变化 如果不同模式下,显示的界面元素不同,则需要设计不同模式下显示的界面元素 例如包含PDTDmaWidgetView, PDTDmdWidgetView, EchatWidgetView,UI状态变化后,根据当前工作模式workMode去获取对应需要显示的界面元素,然后更新UI 屏幕适配原则 在不同设备下,应用同一个功能可能存在不同的UI显示以及交互 为了避免针对不同屏幕实现多套代码,尽量让同一个Fragment界面能灵活适应不同屏幕 应用进行数据驱动UI架构设计后,界面Fragment UI元素,界面状态数据,界面事件处理已经是分离的 细拆分的话,交互事件也是可以分离的 如何划分类,如何拆分 对于布局的组合显示,改成控件化 例如大小屏的标题栏,导航栏。尽量让大小屏的界面元素差异少。如果只有小屏有标题栏,而标题栏有多个控件,不对标题栏控件化的,则在Fragment中需进行多个判空处理 UI参考 launcher 可以细化 使用最小宽度smallestWidth等限定符,为不同屏幕提供不同的布局等资源 限定符本身就是为了适配屏幕 例如res/layout-sw600dp目录下创建布局文件,适配最小宽度为600dp的设备 一旦发现太多判空处理了,则需要考虑适配了 大屏布局上可能无TitleBar这个控件,因此在Fragment使用该控件时,需先判空处理 如果大小屏界面元素差异太多,则会在Fragment存在很多这种判空处理,因此尽量将布局中的组合界面元素进行控件化 如果应用同一个功能在不同屏幕下UI界面元素差异太大,则需通过代码根据屏幕最小宽度来动态加载不同的Fragment页面走不同的交互流程 这个我就建议交给Activity 或者fragment 来处理 界面使用Fragment实现,尽量不使用Activity,使用 Fragment 可以将界面组件模块化,方便在不同屏幕下组合使用 具体实施 应用UI基本结构 UI模块划分 整体架构 融合应用包结构 界面基类以及具体接口 1)界面UI元素 2)界面状态管理器ViewModel 3)业务处理Model 1.小部件实现 交互流程 1)WidgetViewModel 监听通话状态等,模式切换等事件,从CallManager获取小部件需要显示的呼叫信息,然后更新小部件状态WidgetUiState 2)小部件AppwidgetProvider监听到WidgetUiState状态变化,获取小部件RemoteView进行UI更新 3)旋钮按键触发WidgetViewModel调用CallManager执行选组逻辑等 2 划分小部件UI状态WidgetUiState 影响小部件UI显示的各种数据与状态 根据小部件UI涉及的所有状态 1)不同工作模式,小部件UI显示不同 2)普通/通话状态下信息显示,如下图所示。与工作模式,呼叫状态,快播组,群组名等信息有关 3)紧急呼叫,密码告警状态 4)选组状态,以及对应的选组 5)专家模式 如果不同工作模式下,显示的WidgetUiState界面状态数据结构不同。可以进一步根据模式划分EchatWidgetUiState, PDTDmdWidgetUiState 3 小部件界面UI WidgetUiState通过LiveData包装由WidgetViewModel来维护,当观察WidgetViewModel的WidgetUiState状态值变化时,获取对应模式的界面元素WidgetView,进行更新。在AppWidgetProvider的onEnable方法进行观察 每个工作模式的小部件view是不同的,因此根据工作模式,选用不同的布局的小部件EchatWidgetView, PDTTmdWidgetView等,最后调用updateWidget将WidgetUiStatus进行显示 4 WidgetViewModel事件接口 提供给小部件和按键的接口 对于旋钮按键,会触发进入退出选组状态,执行选组业务 按键实现 流程图 基本实现 按键由GotaKeyMonitor负责接收,并根据条件分发给不同模块进行处理 1)旋钮选中,左右旋,确定按键交由小部件处理 2)CallManager处理PTT, SOS等按键处理,进行呼叫流程。并提供IPttKeyHookListener接口给相关需要发起呼叫的界面例如联系人处理 系统事件处理 GotaSystem服务会提供一些注册接口,用于一些系统事件的通知 通过监听这些事件,用于通话,状态栏图标等控制和显示 1 状态图标实现 每个工作模式下所需显示的图标不同,而在模式切换后,只需要显示当前模式下业务的状态图标,上个模式的显示的图标不能再显示。例如PDT模式下,需要显示信号,扫描,功率级别等图标,但是在echat模式,不需要显示这些,而是需要显示注册等图标 为了统一管理当前模式下,各种业务图标在状态栏的显示与隐藏,使用StatusbarManager类来实现 状态图标的显示,有三种触发场景 1)应用初始化时 需要根据当前模式,根据当前模式支持显示的所有状态图标类型,遍历去获取对应的业务状态,然后再决定该状态图标的显示 2)应用业务变化时 先要判断该业务对应的状态图标是否当前模式支持的,如果支持,再去显示或隐藏该状态图标。 3)工作模式变化时 将当前已经显示的所有状态图标隐藏,然后再根据当前模式,支持的状态图标,以及对应的业务状态,来控制图标的显示,同前面初始化流程 StatusbarManager用一个集合来记录当前已显示的所有状态栏图标类 每个工作模式支持显示的图标不同,因此设计一个控制状态栏图标的模式基类 BaseStatusbarController,提供以下几个接口 1)initIcon初始化当前模式下应用所有状态图标的显示状态。 根据支持的所有图标,遍历查询其业务状态,然后控制图标是否需要显示 2)用于获取当前模式下,支持显示的所有状态图标类型,以及是否支持显示某个状态图标 设置UI实现 不同模式下,显示不同设置项,设置项显示所需数据不同 根据小部件UI涉及的所有状态,于是设计设置状态类SettingUiState 其中SettingData 表示每个设置项需要的数据。其中key表示界面元素Preference的唯一标识, data表示该Preference需要显示的数据。例如设置的功率级别需要高,低功率int型数据,而区域Zone需要的是List集合数据 SettingUiState界面状态被修改即设置UI更新情况 首次进入界面时,加载当前模式的设置项 工作模式变化,重新加载该模式的设置项 界面UI进行设置后,设置结果反馈 SettingViewModel类来维护SettingUiState状态 根据界面状态SettingUiState,动态加载需要显示的设置项,并更新设置项值 根据key,构造出对应的Preference界面元素,然后通过preference将界面状态中数据显示出来 SettingViewModel提供给UI的事件接口 又封装一层Gotasystem 短信UI实现 根据交互要求,划分所有的Fragment功能界面,设计Fragment页面跳转的导航图,包含短信MessageMainActivity,MessageListFragment, MessageInfoFragment, MessageEditFragment 界面状态数据划分 其实可以引入状态机 界面需要显示的短信列表以及总数 于是设计短信列表界面状态类MessageListUiState UI更新 MessageListFragment监听到MessageListUiState界面状态变化后,更新界面UI MessageListViewModel事件接口 并管理MessageListUiState MessageListViewModel事件接口 加载短信,选中选项,获取某条消息,任何数据接口,需要监听到数据层短信数据变化,如果数据变化,调用loadDatas重新加载短信数据,然后更新界面状态MessageListUiState,最后触发界面UI刷新。 对数据层短信查询以及删除都是通过MessageManager的接口实现,异步操作 与其他模块交互 1 Launcher配置,通过meta-data配置实现 1)配置Launcher小部件, hotseat 2)设置未读数 3)配置菜单项 2 熄屏,肩屏显示,调用接口。 3 语音播报,调用接口 4 SystemUI呼叫状态栏,各种信号图标,调用接口或发送广播方式

fWX228941 commented 6 months ago

融合通信应用UI方案设计

1 简述 本方案基于Google推荐的应用分层架构进行设计,主要从以下几个方面进行考虑: 1)应用界面UI与业务分离 2)应用内部组件通信方式 3)不同模式下应用UI适配 4)不同屏幕下应用UI适配 最后介绍一些应用UI模块具体实现。 2 整体方案设计 2.1 应用UI架构 应用使用Google推荐的分层架构,如下所示:

1 分层职责 整个应用被划分为两层:界面层UI Layer和数据层Data Layer。这两层明确了各自职责, 界面层职责: 负责在屏幕上显示应用数据。当数据变化时,界面随之更新,用来反映这些变化。

数据层职责: 负责应用业务逻辑,以及应用数据的存储,管理,更改。

整个界面层Data Layer设计成两部分: 1)在屏幕上呈现数据的界面元素UI elements,例如fragment, activity或者其他View。 2)向界面提供数据以及处理逻辑的状态容器StateHolder,这里使用ViewModel来实现,用来维护数据一致性。

2 界面层架构 为了让界面层的界面元素与状态容器职责分离,采用状态向下流动、事件向上流动的单向数据流UDF架构,如下:

界面状态更新流程如下: 1)ViewModel 存储界面UI状态XXXUIState,使用LiveData包装。 2)从数据层获取到相应数据,经过转换后得到界面UI状态UI State,于是进行状态修改。 3)界面UI元素(例如Activity),观察到ViewModel中的UI状态变化时,调用View等界面元素进行状态的更新。例如:

界面事件处理流程如下: 1)界面UI元素进行操作时,会调用 ViewModel 事件处理接口。例如在联系人界面,发起PTT呼叫,如下:

3)ViewModel事件接口中会调用数据层业务接口,最后触发应用数据变化。 4)于是触发相应ViewModel界面状态修改,最后进行界面的刷新。

通过这种单向数据流架构,可以实现以下几点: 1)数据一致性。 保证了界面UI状态只有一个数据来源,仅来自数据层Data Layer存储的数据(或者界面本身作为数据的唯一来源,例如点击搜索,UI进入搜索状态),解决了同一条信息有多个来源带来的数据不一致问题。

2)可维护性 界面UI元素不负责管理UI显示数据,只是通过观察UI状态变化,来进行显示信息。而界面UI状态更改是UI事件以及进行应用数据拉取来触发的。界面UI状态维护与界面UI更新进行了分离。

3 融合应用架构划分

应用整体划分三部分: 1)GotaSystem作为独立的服务组件被引入,负责应用核心通信业务实现以及业务数据管理,同时提供相关服务使用,查询,事件上报等接口。(内部数据库使用ROOM实现)

2)提供给UI层ViewModel引用的各模块业务,例如呼叫CallManager, 设置SettingManager等,其是对GotaSystem接口进行了更复杂的逻辑封装,更适合UI层的ViewModel类使用。

3)UI层部分。对于联系人,短信,设置等每个UI模块(一般为fragment),其每个界面至少关联一个ViewModel类,而ViewModel类至少关联一个XXUiState界面状态以及对应业务处理类。

4 应用UI层设计 根据前面应用架构设计,对于UI层主要工作划分以下几部分: 1)定义和公开界面各种UI状态XXXUiState 根据交互要求,在界面上向用户显示的信息即是界面状态,界面是其界面状态的呈现。例如对于联系人界面ContactFragment,在未搜索状态下,显示所有联系人列表数据,在搜索状态下,需要显示搜索到的联系人列表数据,另外还需显示搜索框。因此联系人列表界面状态可以用以下结构描述,如下:

界面状态由相应的ViewModel这种状态容器进行维护,通过LiveData(代替使用DataBinding)这种可观察的数据容器包装,将界面状态公开。如下:

2)界面观察界面状态,进行UI更新 在Activity, Fragment等界面中,去观察该界面所有界面状态XXUiState变化,当该状态变化时,界面UI元素来呈现这些变化,实现了数据驱动UI界面。例如联系人列表界面,观察如下:

3)定义和实现界面UI各种事件接口 根据用户的UI界面操作,ViewModel向界面UI提供相应事件接口,用于执行用户逻辑。例如,联系人列表界面,进入界面时,需要接口去触发界面UI状态初始化, 还可以按PTT发起呼叫,点击搜索,可以进入搜索模式等,因此接口至少包含如下:

4)设计合适的业务逻辑类 ViewModel执行的所有业务逻辑,通过调用数据层相关业务类xxxManager来完成的。对于业务异步操作使用RxJava实现,用于替换Thread,AsyncTask 来简化异步任务操作,同时方便线程切换。

2.2 应用内部组件通信 1 应用Activity与 Fragment组件之间可以利用ViewModel实例进行通信,实现数据共享。 ViewModel实例是从ViewModelStoreOwner获取的,如果Owner是某个Activity,则从该Activity获取的所有Fragment都拥有相同的ViewModel实例,如果Owner是某个ApplicationInstance实例,则整个应用都可以获取到这ViewModel实例。因此可通过构造的ViewModelStoreOwner作用域来实现Activity,Fragment之间数据共享。例如联系人列表ContactListFragment,点击某个联系人,进入联系人详情界面ContactInfoFragment,可以使用一个Activity作用域的ViewModel,如下:

列表项点击时,调用ViewModel记录当前选中的项index, 当进入联系人详情界面时,就可以通过ViewModel拿到该index对应的联系人信息。

2 使用LiveEventBus,RxBus这些消息总线,进行线程之间,UI界面层各组件之间,数据层与UI层之间的消息通信。 LiveEventBus利用了LiveData能够感知Activity等生命周期,不需手动取消消息订阅,可避免出现内存泄露,同时消息接收是在主线程,可直接用于界面UI更新。对于RxBus可以使用线程分发。 例如新建短信界面,从联系人界面选择某个联系人,进行发送。新建短信界面,订阅选择的联系人消息,如下:

联系人界面选择联系人返回时,发送消息,如下:

数据层的工作模式,通话状态等系统事件变化,通过RxBus将变化消息发送给小部件,联系人等UI层界面。

3界面层ViewModel与数据层Model类之间通信,数据层提供异步接口返回LiveData, RxJava Observable对象,替换回调方式,可以方便异步处理结果切换到主线程更新UI。

例如使用ROOM数据库,异步查询数据层通话记录,其接口返回LiveData,如下:

ViewModel得到该LiveData进行转换,得到界面状态LiveData。

用RxJava则接口返回的Observable对象,如下:

ViewModel调用Model的loadCallLogs方法,观察Observable, 主线程更新UI,如下:

2.3 界面Fragment化管理 Activity页面跳转会占用更多内存,跳转不平滑,而Fragment切换是轻量级的,因此整个融合应用页面UI展示和交互逻辑尽量都使用Fragment实现,除非只能是Activity实现的,例如需要显示在Launcher图标的。大部分情况下Activity只作为页面Fragment的显示容器,另外Fragment可以实现页面模块化以及屏幕适配。 为了更好的统一管理页面Fragment之间的跳转,返回栈,使用Jetpack的Navigation组件库来开发。根据界面UI功能划分联系人,设置,短信等Activity,主要工作包含如下: 1)根据交互需求,划分各页面UI Fragment,例如短信功能Activity,包含短信列表MessageListFragment, 新建消息MessageNewFragment,短信详情MessageInfoFragment等。

2)设计Fragment页面跳转导航图。 通过Android Studio的Navigation Editor 或直接修改配置文件nav_graph.xml配置操作id,destination。例如:

3)根据条件,跳转Fragment时,执行NavController的导航操作action即可。如下:

2.4 不同模式下UI适配 在不同工作模式下,应用一些UI界面是有差异的,例如PDT常规,集群模式下,小部件,设置项等显示都是不同的。不同模式下的影响如下:

根据应用架构,UI层的界面元素,ViewModel界面状态,数据层业务接口三部分都需要进行适配:

1)数据层Model提供的业务接口,需要多模式支持(具体看GotaSystem实现)。

2)不同模式下,界面显示的状态数据是不同的。因此ViewModel维护的界面UI状态XXXUiState, 需要包含工作模式workMode。ViewModel监听工作模式变化,重新调用数据层Model接口获取该模式下的数据,然后更新XXXUiState,触发界面UI状态变化。

3)如果不同模式下,显示的界面元素不同,则需要设计不同模式下显示的界面元素。例如包含PDTDmaWidgetView, PDTDmdWidgetView, EchatWidgetView,UI状态变化后,根据当前工作模式workMode去获取对应需要显示的界面元素,然后更新UI。例如:

2.5 屏幕适配原则 在不同设备下,应用同一个功能可能存在不同的UI显示以及交互。在应用进行数据驱动UI架构设计后,界面Fragment UI元素,界面状态数据,界面事件处理已经是分离的,因此对于应用在不同屏幕下适配相对简单些。为了避免针对不同屏幕实现多套代码,尽量让同一个Fragment界面能灵活适应不同屏幕,需要遵循一些原则如下: 1)对于布局的组合显示,改成控件化。 例如大小屏的标题栏,导航栏。尽量让大小屏的界面元素差异少。如果只有小屏有标题栏,而标题栏有多个控件,不对标题栏控件化的,则在Fragment中需进行多个判空处理。

2)使用最小宽度smallestWidth等限定符,为不同屏幕提供不同的布局等资源。 在Fragment界面初始化View时,如果不同布局有不同控件id,则先要判断控件id是否存在,然后执行View更新。例如res/layout-sw600dp目录下创建布局文件,适配最小宽度为600dp的设备,该布局文件如下:

大屏布局上可能无TitleBar这个控件,因此在Fragment使用该控件时,需先判空处理,如下:

如果大小屏界面元素差异太多,则会在Fragment存在很多这种判空处理,因此尽量将布局中的组合界面元素进行控件化。

3)如果应用同一个功能在不同屏幕下UI界面元素差异太大,则需通过代码根据屏幕最小宽度来动态加载不同的Fragment页面走不同的交互流程。如下:

4)界面使用Fragment实现,尽量不使用Activity。 使用 Fragment 可以将界面组件模块化,方便在不同屏幕下组合使用。

3 具体实施 3.1应用UI基本结构 1 应用UI模块划分

2 融合应用整个工程,如下:

其中融合通信服务组件,pdt服务组件,echat服务组件不做介绍。

3 融合应用包结构如下:

其中: Appwidget:负责小部件实现 bus:消息总线rxbus Exception:应用异常处理 Model:业务逻辑类 ViewModel:状态容器 Service:后台服务 Data数据管理 Key:按键操作

4 界面基类包含几个接口 界面基类BaseFragment以及BaseActivity,至少需要提供初始化ViewModel接口,布局id,对于Fragment来说,需要提供获取NavController接口,如下:

根据前面应用UI架构,主要分为三部分: 1)界面UI元素 2)界面状态管理器ViewModel 3)业务处理Model 下面介绍几个应用UI模块大体实现。 3.2小部件实现 1 整个交互,如下:

1)WidgetViewModel 监听通话状态等,模式切换等事件,从CallManager获取小部件需要显示的呼叫信息,然后更新小部件状态WidgetUiState 2)小部件AppwidgetProvider监听到WidgetUiState状态变化,获取小部件RemoteView进行UI更新。 3)旋钮按键触发WidgetViewModel调用CallManager执行选组逻辑等。

2 划分小部件UI状态WidgetUiState 1)不同工作模式,小部件UI显示不同 2)普通/通话状态下信息显示,如下图所示。与工作模式,呼叫状态,快播组,群组名等信息有关。 3)紧急呼叫,密码告警状态 4)选组状态,以及对应的选组 5)专家模式

根据小部件UI涉及的所有状态,于是设计小部件状态类WidgetUiState,如下:

如果不同工作模式下,显示的WidgetUiState界面状态数据结构不同。可以进一步根据模式划分EchatWidgetUiState, PDTDmdWidgetUiState等。

3 小部件界面UI WidgetUiState通过LiveData包装由WidgetViewModel来维护,当观察WidgetViewModel的WidgetUiState状态值变化时,获取对应模式的界面元素WidgetView,进行更新。在AppWidgetProvider的onEnable方法进行观察如下:

每个工作模式的小部件view是不同的,因此根据工作模式,选用不同的布局的小部件EchatWidgetView, PDTTmdWidgetView等,最后调用updateWidget将WidgetUiStatus进行显示,EchatWidgetView的updateWidget方法如下:

4 WidgetViewModel事件接口 提供给小部件和按键的接口,如下:

对于旋钮按键,会触发进入退出选组状态,执行选组业务。

5 CallManager提供的接口

3.3按键实现 1 按键流程图,如下所示:

2 基本实现 按键由GotaKeyMonitor负责接收,并根据条件分发给不同模块进行处理。

1)旋钮选中,左右旋,确定按键交由小部件处理。 2)CallManager处理PTT, SOS等按键处理,进行呼叫流程。并提供IPttKeyHookListener接口给相关需要发起呼叫的界面例如联系人,如下:

3.4系统事件处理 GotaSystem服务会提供一些注册接口,用于一些系统事件的通知,系统事件包含如下:

通过监听这些事件,用于通话,状态栏图标等控制和显示等。 下面介绍下状态栏图标以及通话状态相关UI显示的实现。 1 状态图标实现 每个工作模式下所需显示的图标不同,而在模式切换后,只需要显示当前模式下业务的状态图标,上个模式的显示的图标不能再显示。例如PDT模式下,需要显示信号,扫描,功率级别等图标,但是在echat模式,不需要显示这些,而是需要显示注册等图标。

为了统一管理当前模式下,各种业务图标在状态栏的显示与隐藏,使用StatusbarManager类来实现。状态图标的显示,有三种触发场景,如下:

1)应用初始化时 需要根据当前模式,根据当前模式支持显示的所有状态图标类型,遍历去获取对应的业务状态,然后再决定该状态图标的显示。

2)应用业务变化时 先要判断该业务对应的状态图标是否当前模式支持的,如果支持,再去显示或隐藏该状态图标。

3)工作模式变化时 将当前已经显示的所有状态图标隐藏,然后再根据当前模式,支持的状态图标,以及对应的业务状态,来控制图标的显示,同前面初始化流程。

StatusbarManager用一个集合来记录当前已显示的所有状态栏图标类。如下:

每个工作模式支持显示的图标不同,因此设计一个控制状态栏图标的模式基类 BaseStatusbarController,提供以下几个接口: 1)初始化当前模式下应用所有状态图标的显示状态。 根据支持的所有图标,遍历查询其业务状态,然后控制图标是否需要显示。如下:

2)用于获取当前模式下,支持显示的所有状态图标类型,以及是否支持显示某个状态图标,如下:

3)控制具体某个状态图标的显示与隐藏,如下:

根据工作模式设计相应子类,例如PDTStatusbarController, EchatStatusbarController,实现上面接口即可。

StatusbarManager则管理当前模式显示的所有状态图标,同时根据当前模式,调用相应的BaseStatusbarController,来进行状态图标的控制。如下: 1)初始化时

2)监听到工作模式变化

3)监听到业务状态变化

这里只介绍了状态图标的显示,对于指示灯,也使用相同方式实现。 2 通话状态相关实现 通话状态变化后,工作流程图,如下:

对于背景,秘密呼叫,不需要显示通话相关UI,其他呼叫,则需要在状态栏上显示呼叫状态(大屏)或者显示通话悬浮窗口(小屏)。如下:

设计CallStatusManager用于监听通话状态变化的事件,负责整个通话状态变化的处理。 主要功能是实现通话UI显示以及声音控制,小部件的刷新通过Rxbus发送消息触发。 对于通话UI,根据通话状态有不同的显示,设计通话状态接口ICallStatusListener如下:

在呼叫各状态下,进行相关声音以及UI显示,具体实现交由CallStatusController来完成,如下:

呼叫状态栏信息的显示与隐藏通过调用SystemUi提供的接口或者相应广播实现。 呼叫悬浮窗通过在后台服务中通过WindowManager添加一个悬浮窗来实现。

3.5设置UI实现 1设置主界面各模块交互如下所示:

2 界面状态划分 不同模式下,显示不同设置项,设置项显示所需数据不同。

根据小部件UI涉及的所有状态,于是设计小部件状态类SettingUiState,如下:

其中SettingData 表示每个设置项需要的数据。其中key表示界面元素Preference的唯一标识, data表示该Preference需要显示的数据。例如设置的功率级别需要高,低功率int型数据,而区域Zone需要的是List集合数据。

创建SettingViewModel类来维护该状态,如下:

SettingUiState界面状态被修改即设置UI更新情况如下: 1)首次进入界面时,加载当前模式的设置项。 2)工作模式变化,重新加载该模式的设置项。 3)界面UI进行设置后,设置结果反馈。

3 界面UI更新 整个设置界面使用SettingFragment实现,当观察SettingViewModel的SettingUiState状态值变化时,如下:

根据界面状态SettingUiState,动态加载需要显示的设置项,并更新设置项值,如下:

根据key,构造出对应的Preference界面元素,然后通过preference将界面状态中数据显示出来。

4 SettingViewModel提供给UI的事件接口

3.6短信UI实现 1)根据交互要求,划分所有的Fragment功能界面,设计Fragment页面跳转的导航图。 包含短信MessageMainActivity,MessageListFragment, MessageInfoFragment, MessageEditFragment,设计Fragment之间导航图。 下面实现以MessageListFragment短信列表界面为示例。

2 界面状态划分

界面需要显示的短信列表以及总数。于是设计短信列表界面状态类MessageListUiState,如下:

设计MessageListViewModel类维护该界面状态,如下:

3 界面UI更新 MessageListFragment监听到MessageListUiState界面状态变化后,更新界面UI,如下:

4 MessageListViewModel事件接口

另外需要监听到数据层短信数据变化,如果数据变化,调用loadDatas重新加载短信数据,然后更新界面状态MessageListUiState,最后触发界面UI刷新。 对数据层短信查询以及删除都是通过MessageManager的接口实现,异步操作返回Observable,如下:

MessageListViewModel的loadDatas方法,如下:

3.7与其他模块交互 1 Launcher配置,通过meta-data配置实现。 1)配置Launcher小部件, hotseat 2)设置未读数 3)配置菜单项

2 熄屏,肩屏显示,调用接口。 3 语音播报,调用接口。 4 SystemUI呼叫状态栏,各种信号图标,调用接口或发送广播方式。

fWX228941 commented 6 months ago

Image

Image

Image

Image

Image

Image