聊天页面消息列表重构及其线程安全保障

71 阅读5分钟

本质问题: 列表数据层的增删查改操作耦合在UI层,导致在不同的子线程与主线程同时操作列表数据造成线程安全问题与列表刷新操作随心所欲化,所以回到最根本问题还是要做代码解耦合

主要解决两个现实代码问题:

1.多线程对聊天列表数据源进行增/删/查/改,造成的线程安全问题

2.不同业务场景对聊天列表数据源增/删/查/改后的,刷新列表UI规范不一致的问题

一.目前现状

  1. MXChatViewModel中的messageItemList列表在包括主线程在内的N多个任意子线程中赋值与访问,但是从以下截图代码来看,messageItemList的访问并没有做线程安全保护,所以后面必将造成线程安全问题
  2. 同理MXChatListViewModel中的cellViewModelList也将在N多任意线程中访问
  3. MXChatViewModel中的updateRxDataSource方法被太多不同的业务场景任意调用,就是说我们在太多各自的业务场景地方可以任意刷新聊天列表UI

二.目标结构

9a0c9516-c240-45b3-bfc6-c3920276d957.png

  1. 聊天页面模块新增一个MXChatCellViewModelFactory的对象,该对象的工作职责是:[MIMMessage]数组转为可直接渲染聊天消息cellUI的[MXChatxxxCellViewModel],并最终将[MXChatxxxCellViewModel]数组封装为完整的列表UI数据[MessageItem];注意右边的messageTrigger每次提交的数据都是当前UI列表完整的数据源,MXChatViewModel不需要也不允许再对数据[MessageItem]列表数据进行任何改动,只需要直接绑定rxdatasource刷新UI即可。
  2. MXChatCellViewModelFactory对象维护并持有整个UI列表所需的[MIMMessage]列表数据,为了保证MXChatCellViewModelFactory中的[MIMMessage]列表数据线程安全,需要在MXChatCellViewModelFactory中对[MIMMessage]列表数据的增/删/改/查操作都保持在一个固定的串行队列中执行。每次完成一次对[MIMMessage]列表数据的增/删/改操作后,需要将[MIMMessage]列表数据转换为[MessageItem]UI列表渲染所有数据通过messageTrigger发送给MXChatViewModel,MXChatViewModel收到messageTrigger通知时,可以做一定的debounce数据防抖处理后,通知rxDataSource刷新列表UI
  3. MXChatCellViewModelFactory的数据输入源,可以是任意线程来源的输入数据源,其中携带的[MIMMessage]数组只是局部的消息数组,比如进入聊天页面初始化加载的一页25条数据,新收到的N条新消息数据;但是转到MXChatCellViewModelFactory内部都需要切换到固定的串行队列中执行,然后根据数据来源的增/删/改操作,传递进来的[MIMMessage]数组,对MXChatCellViewModelFactory维护的完整[MIMMessage]数组进行增/删/改操作完成了之后。此时MXChatCellViewModelFactory中维护的[MIMMessage]数组列表又是此时聊天页面列表完整的消息列表数据。将完整的[MIMMessage]转换为[MXChatxxxCellViewModel]数组后,封装为[MessageItem]数组,最后通过messageTrigger通知列表刷新UI。
  4. MXChatCellViewModelFactory为了保持其职责单一性,他里面不做查询本地消息数据库,也不做pull远程消息数据操作,更不做像消息多语言翻译的操作;就是他不做输入源中MIMMessage的准确性与最新性校验,需要输入源本身保证input给MXChatCellViewModelFactory的[MIMMessage]数组就是当前最准确最新的消息数据。再次明确MXChatCellViewModelFactory其只做: [MIMMessage]数组到[MessageItem]数组的转换,并维护整个聊天页面完整的[MIMMessage]数组顺序;
  5. 从以上结构图中发现MXChatCellViewModelFactory并没有查询操作,因为目前预估大部分的消息查询造作应该是在主线程,比如查询当前聊天列表的最顶部/最底部消息的时间戳,再决定上下拉消息数据的起始时间。这里建议MXChatViewModel通过获取当前列表的rxdatasource数据源来获取,所有查询的操作直接根据rxdatasource的数据源数组来查。
  6. 整个结构最前面的拉取初始化消息,收到新消息时需要做数据状态检查;比如初始化消息,检查消息数量,决定是否需要同步远程消息;收到新消息时检查是否需要翻译后再展示,当然也可以先将未翻译的新消息通过后续结构展示先,等消息翻译完成再通过更新[MIMMessage]操作再次刷新最新的UI,但是检查与调用翻译的流程是在最前置的流程。而不是将消息传递给MXChatCellViewModelFactory加工渲染UI后,再去检查消息数据的状态。检查完消息数据后,前置流程还需要尽可能的完成除了消息列表UI以外的其他零散的UI;比如:收到at消息时,更新‘有人@我’UI;下拉加载完历史页数据时,endHeaderRefresh 等等零散的局部零散的UI更行。只有一些需要等带消息列表刷新完才能实现的UI操作才需要通过input参数传递进入MXChatCellViewModelFactory加工完成后再执行,比如进入聊天页面初始完成列表UI,需要定位到某条消息并闪烁;比如需要刷新完UI后定位聊天页面最底部。 【特别关注这一点,因为我们发现MXChatViewModel中很多地方的代码,不符合该要求】