美文网首页
IM中群消息发送者信息刷新方案

IM中群消息发送者信息刷新方案

作者: wzmyyj | 来源:发表于2020-05-14 13:28 被阅读0次

    在IM项目(Android)中,聊天页面,进入会展示历史消息,而历史消息存下来的发送者信息可能并不是最新的,所以需要去刷新数据。单聊场景只需要刷新对方一个人信息,实现较为简单。但是到群聊,发送者众多,不可能每次进入页面都去获取全部成员的信息(数量大,获取缓慢),所以需要制定策略去实现好的效果。

    需求分析

    期望:

    1. 只去刷新显示在屏幕上的发送者信息。
    2. 每个发送者只需要刷新一次。(做个缓存)
    3. 屏幕滚动很快,中途显示的不去刷新。
    4. 如果其他地方缓存过了这个成员,就不再去获取。
    5. 群成员信息修改,及时刷新缓存数据。

    方案设计

    设计:

    1. 在recycler的onBindVH里收集消息列表里的发送者的ID(imAccount)。
    2. 收集到数据池(只收集不是最新数据的,防止反复收集),对imAccount去重,大小为10。利用LRU的缓存淘汰imAccount。
    3. 静置0.5秒后开始将缓存池内容发射请求。(即屏幕停止了滑动,或滑动没时新的item添加到屏幕)。
    4. 每个imAccount对应一个锁对象,保证异步下同一个imAccount只会请求一次。
    5. 结合群成员信息做缓存。(群成员缓存获取过了,如进过群成员页等, 就不再去请求,直接使用缓存里的数据)
    6. 刷新成功一个imAccount则会把整个列表里同一个发送者的信息都刷新掉。
    7. 数据刷新成功,回调刷新UI列表。需要绑定聊天页面生命周期。
    8. 收到群成员信息修改通知消息,修改缓存数据。

    流程图:

    Sander流程图.jpg

    代码实现

    该部分功能需要结合成员缓存功能。请看:IM项目中群成员获取与缓存策略

    class SenderHelper private constructor() : DefaultLifecycleObserver {
    
        companion object {
            private const val CACHE_MAX_SIZE = 10
            private const val COUNT_DOWN_DELAY = 500L
            // 保证一对一的关系。
            private val map = WeakHashMap<LifecycleOwner, SenderHelper>()
    
            fun with(owner: LifecycleOwner, observer: Observer<List<String>>): SenderHelper {
                return map[owner] ?: SenderHelper().apply {
                    map[owner] = this
                    with(owner, observer)
                }
            }
    
            fun get(owner: LifecycleOwner): SenderHelper? {
                return map[owner]
            }
    
            fun get(sessionId: String): SenderHelper? {
                return map.values.find { it.sessionId == sessionId }
            }
        }
    
        // 回调的 liveData。
        private val liveData = MutableLiveData<List<String>>()
        // rx。
        private var compositeDisposable = CompositeDisposable()
        // 入参缓存池。
        private val cache = LruCache<String, Unit>(CACHE_MAX_SIZE)
        // 结果列表。
        private val resultList = CopyOnWriteArrayList<String>()
        // 锁对象 map。
        private val lockMap = ConcurrentHashMap<String, Lock>()
        // data。
        private var groupCode: String = ""
        private var sessionId: String = ""
        private lateinit var dataList: (Unit) -> List<SenderModel>
        private val memberSet by lazy {
            MemberHelper.getIfAbsent(groupCode)
        }
    
        private val handler = Handler()
        private val runnable = Runnable {
            cache.snapshot().keys.apply {
                forEach { k -> cache.remove(k) }
                task(this.toList())
            }
        }
    
        /**
         * 初始化。
         */
        fun init(sessionId: String, groupCode: String, dataList: (Unit) -> List<SenderModel>) {
            this.sessionId = sessionId
            this.groupCode = groupCode
            this.dataList = dataList
        }
    
        /**
         * 获取最新数据。
         */
        fun bind(sender: SenderModel) {
            // 如果是自己,直接返回。
            if (sender.isSelf || sender.imAccount.isEmpty()) return
            // 如果最新,直接返回。
            memberSet.get(sender.imAccount)?.let {
                if (compare(sender, it).falseRun { changeListAndPost(it) }) return
            }
            // 存入缓存池。
            cache.get(sender.imAccount) ?: cache.put(sender.imAccount, Unit)
            countDown()
        }
    
        /**
         * 主动刷新名称。
         */
        fun updateNickname(imAccount: String, nickname: String) {
            memberSet.get(imAccount)?.let {
                it.nickName = nickname
                changeListAndPost(it)
            }
        }
    
        /**
         * 主动刷新身份。
         */
        fun updateGroupRole(imAccount: String, groupRole: Int) {
            memberSet.get(imAccount)?.let {
                it.groupRole = groupRole
                changeListAndPost(it)
            }
        }
    
        override fun onDestroy(owner: LifecycleOwner) {
            compositeDisposable.clear()
            handler.removeCallbacksAndMessages(null)
            map.remove(owner)
        }
    
        //---------private method-----------//
    
        /**
         * 绑定生命周期和观察。
         */
        private fun with(owner: LifecycleOwner, observer: Observer<List<String>>) {
            owner.lifecycle.addObserver(this)
            liveData.observe(owner, observer)
        }
    
        /**
         * 延时计时。
         */
        private fun countDown() {
            handler.removeCallbacksAndMessages(null)
            handler.postDelayed(runnable, COUNT_DOWN_DELAY)
        }
    
        /**
         * 任务。
         */
        private fun task(imAccountList: List<String>) {
            Observable
                    .fromIterable(imAccountList)
                    .flatMap { work(it) }
                    .doFinally {
                        if (resultList.isNotEmpty()) {
                            liveData.postValue(ArrayList(resultList))
                            resultList.clear()
                        }
                    }
                    .subscribe({}, {})
                    .addToComposite()
        }
    
        /**
         * 工作。各自开辟子线程。
         */
        private fun work(imAccount: String): Observable<*> {
            return Observable.just(imAccount)
                    .subscribeOn(Schedulers.io())
                    .flatMap {
                        synchronized(getLock(it).lock) {
                            if (memberSet.get(it) == null) {
                                netWork(it)
                            } else {
                                Observable.just(it)
                            }
                        }
                    }
        }
    
        /**
         * 网络操作。与工作同一个线程。
         */
        private fun netWork(imAccount: String): Observable<*> {
            return MemberHelper
                    .loadMember(sessionId, imAccount)
                    .filter { it.status && it.entry != null }
                    .map { it.entry!! }
                    .doOnNext {
                        resultList.add(it.imAccount.orEmpty())
                        memberSet.put(it)
                        updateDb(it)
                        changeList(it)
                    }
        }
    
        /**
         *
         * 更新数据库数据。
         */
        private fun updateDb(bean: MemberBean) {
            ...修改数据库实现不重要...
        }
    
        /**
         * 刷洗数据及发送数据变化信号。
         */
        private fun changeListAndPost(bean: MemberBean) {
            changeList(bean).trueRun { liveData.postValue(arrayListOf(bean.imAccount.orEmpty())) }
        }
    
        /**
         * 刷新列表数据。
         */
        private fun changeList(bean: MemberBean): Boolean {
            val isChange: Boolean
            dataList()
                    .filter { it.imAccount == bean.imAccount && compare(it, bean).not() }
                    .apply { isChange = this.isNotEmpty() }
                    .forEach {
                        it.nickName = bean.nickName.orEmpty()
                        it.avatar = bean.avatar?.toLoadUrl().orEmpty()
                        it.setGroupRole(bean.groupRole)
                    }
            return isChange
        }
    
        /**
         * 比较是否最新了。
         */
        private fun compare(sender: SenderModel, bean: MemberBean): Boolean {
            return (bean.groupRole == sender.groupRole
                    && bean.nickName == sender.nickName
                    && bean.avatar?.toLoadUrl() == sender.avatar)
        }
    
        /**
         * 锁。
         */
        class Lock(val lock: Any = Any())
    
        /**
         * 获取锁对象。
         */
        private fun getLock(imAccount: String): Lock {
            return lockMap[imAccount] ?: Lock().apply { lockMap[imAccount] = this }
        }
    
        /**
         * add 到复合体。
         */
        private fun Disposable.addToComposite() {
            compositeDisposable.add(this)
        }
    
    }
    

    使用:

    初始化:

    SenderHelper
                    .with(lifecyclerOwner, Observer { updateList() })
                    .init(sessionId, groupCode) { getSenderList() }
    

    在recyclerView适配器的onBindVH处:

     SenderHelper.get(lifecyclerOwner)?.bind(sender)
    

    收到消息主动刷新缓存:

    // 更新名称。
    SenderHelper.get(sessionId)?.updateNickname(imAccount,nickName)
    // 更新身份。
    SenderHelper.get(sessionId)?.updateGroupRole(imAccount,groupRole)                
    

    总结

    要点:

    1. 收集最新进入的 imAccount,最多10个。
    2. 静置 0.5 秒,将收集的数据分别请求。
    3. 同一个 imAccount 只能请求一次。
    4. 绑定生命周期,一对一关系。
    5. 与群成员缓存结合。

    PS:从这个方案中,可以扩展到列表内容局部数据请求接口刷新的场景。

    相关文章

      网友评论

          本文标题:IM中群消息发送者信息刷新方案

          本文链接:https://www.haomeiwen.com/subject/ybltohtx.html