在IM項(xiàng)目(Android)中,聊天頁(yè)面幽崩,進(jìn)入會(huì)展示歷史消息遵蚜,而歷史消息存下來(lái)的發(fā)送者信息可能并不是最新的帖池,所以需要去刷新數(shù)據(jù)。單聊場(chǎng)景只需要刷新對(duì)方一個(gè)人信息谬晕,實(shí)現(xiàn)較為簡(jiǎn)單碘裕。但是到群聊,發(fā)送者眾多攒钳,不可能每次進(jìn)入頁(yè)面都去獲取全部成員的信息(數(shù)量大帮孔,獲取緩慢),所以需要制定策略去實(shí)現(xiàn)好的效果不撑。
需求分析
期望:
- 只去刷新顯示在屏幕上的發(fā)送者信息文兢。
- 每個(gè)發(fā)送者只需要刷新一次。(做個(gè)緩存)
- 屏幕滾動(dòng)很快焕檬,中途顯示的不去刷新姆坚。
- 如果其他地方緩存過了這個(gè)成員,就不再去獲取实愚。
- 群成員信息修改兼呵,及時(shí)刷新緩存數(shù)據(jù)。
方案設(shè)計(jì)
設(shè)計(jì):
- 在recycler的onBindVH里收集消息列表里的發(fā)送者的ID(imAccount)腊敲。
- 收集到數(shù)據(jù)池(只收集不是最新數(shù)據(jù)的击喂,防止反復(fù)收集),對(duì)imAccount去重碰辅,大小為10懂昂。利用LRU的緩存淘汰imAccount。
- 靜置0.5秒后開始將緩存池內(nèi)容發(fā)射請(qǐng)求没宾。(即屏幕停止了滑動(dòng)凌彬,或滑動(dòng)沒時(shí)新的item添加到屏幕)。
- 每個(gè)imAccount對(duì)應(yīng)一個(gè)鎖對(duì)象循衰,保證異步下同一個(gè)imAccount只會(huì)請(qǐng)求一次铲敛。
- 結(jié)合群成員信息做緩存。(群成員緩存獲取過了羹蚣,如進(jìn)過群成員頁(yè)等原探, 就不再去請(qǐng)求,直接使用緩存里的數(shù)據(jù))
- 刷新成功一個(gè)imAccount則會(huì)把整個(gè)列表里同一個(gè)發(fā)送者的信息都刷新掉。
- 數(shù)據(jù)刷新成功咽弦,回調(diào)刷新UI列表徒蟆。需要綁定聊天頁(yè)面生命周期。
- 收到群成員信息修改通知消息型型,修改緩存數(shù)據(jù)段审。
流程圖:
代碼實(shí)現(xiàn)
該部分功能需要結(jié)合成員緩存功能。請(qǐng)看:IM項(xiàng)目中群成員獲取與緩存策略
class SenderHelper private constructor() : DefaultLifecycleObserver {
companion object {
private const val CACHE_MAX_SIZE = 10
private const val COUNT_DOWN_DELAY = 500L
// 保證一對(duì)一的關(guān)系闹蒜。
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 }
}
}
// 回調(diào)的 liveData寺枉。
private val liveData = MutableLiveData<List<String>>()
// rx。
private var compositeDisposable = CompositeDisposable()
// 入?yún)⒕彺娉亍? private val cache = LruCache<String, Unit>(CACHE_MAX_SIZE)
// 結(jié)果列表绷落。
private val resultList = CopyOnWriteArrayList<String>()
// 鎖對(duì)象 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
}
/**
* 獲取最新數(shù)據(jù)筐喳。
*/
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()
}
/**
* 主動(dòng)刷新名稱梳毙。
*/
fun updateNickname(imAccount: String, nickname: String) {
memberSet.get(imAccount)?.let {
it.nickName = nickname
changeListAndPost(it)
}
}
/**
* 主動(dòng)刷新身份。
*/
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)
}
/**
* 延時(shí)計(jì)時(shí)账锹。
*/
private fun countDown() {
handler.removeCallbacksAndMessages(null)
handler.postDelayed(runnable, COUNT_DOWN_DELAY)
}
/**
* 任務(wù)。
*/
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)
}
}
}
}
/**
* 網(wǎng)絡(luò)操作。與工作同一個(gè)線程啤握。
*/
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)
}
}
/**
*
* 更新數(shù)據(jù)庫(kù)數(shù)據(jù)。
*/
private fun updateDb(bean: MemberBean) {
...修改數(shù)據(jù)庫(kù)實(shí)現(xiàn)不重要...
}
/**
* 刷洗數(shù)據(jù)及發(fā)送數(shù)據(jù)變化信號(hào)晶框。
*/
private fun changeListAndPost(bean: MemberBean) {
changeList(bean).trueRun { liveData.postValue(arrayListOf(bean.imAccount.orEmpty())) }
}
/**
* 刷新列表數(shù)據(jù)排抬。
*/
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())
/**
* 獲取鎖對(duì)象蹲蒲。
*/
private fun getLock(imAccount: String): Lock {
return lockMap[imAccount] ?: Lock().apply { lockMap[imAccount] = this }
}
/**
* add 到復(fù)合體。
*/
private fun Disposable.addToComposite() {
compositeDisposable.add(this)
}
}
使用:
初始化:
SenderHelper
.with(lifecyclerOwner, Observer { updateList() })
.init(sessionId, groupCode) { getSenderList() }
在recyclerView適配器的onBindVH處:
SenderHelper.get(lifecyclerOwner)?.bind(sender)
收到消息主動(dòng)刷新緩存:
// 更新名稱侵贵。
SenderHelper.get(sessionId)?.updateNickname(imAccount,nickName)
// 更新身份届搁。
SenderHelper.get(sessionId)?.updateGroupRole(imAccount,groupRole)
總結(jié)
要點(diǎn):
- 收集最新進(jìn)入的 imAccount,最多10個(gè)。
- 靜置 0.5 秒卡睦,將收集的數(shù)據(jù)分別請(qǐng)求宴胧。
- 同一個(gè) imAccount 只能請(qǐng)求一次。
- 綁定生命周期表锻,一對(duì)一關(guān)系恕齐。
- 與群成員緩存結(jié)合。
PS:從這個(gè)方案中瞬逊,可以擴(kuò)展到列表內(nèi)容局部數(shù)據(jù)請(qǐng)求接口刷新的場(chǎng)景显歧。