有个需求:
从activityA,打开activityB, activityC 或者还存在viewpager上的其他Fragment。甚至activityB,又打开了activityBA。
在这些界面上,大家都有相同的数据Bean(name, info, isFavourite)去展示成卡片列表。
这种情况下,当操作了某个界面的Bean数据,更新当前自己是很容易的;但是当想更新其他界面,其他activity就比较麻烦了。
这里给出一个实现方案。
interface ICrossNotify<T> {
@MainThread
fun onCrossNotify(data:List<T>?)
}
/**
* @date :2024/7/18 19:34
* @description: 跨activity通知。
* 必须把它变成一个单例(或存储在单例里面,唯一。但可以多个实例。)
*
* 用法在Activity或者Fragment
* 1. onCreate()里面调用callOnCreate(this)
* 2. 然后,给它实现ICrossNotify接口。一切就已经完成。
*
* 更新数据,调用changeData,必须保证是主线程。
*
* 内部会针对,监听的前后,分别通知不同的时机的数据。
*/
class CrossActivityNotifyListObserver<T : Any>(private val mainHandler:Handler) : DefaultLifecycleObserver{
//value的左边代表是否监听后,有更新过。右边代表更新后的值。
private data class CrossActivityNotifyListInfo<T>(var isResumed:Boolean,
val data: ArrayList<T> = ArrayList(4))
private val TAG = "crossNotify"
private val callbackList = HashMap<LifecycleOwner, CrossActivityNotifyListInfo<T>>(8)
/**
* 如果你希望数据进行去重,比如Info(a, b, c),其中以a字段如果相同就认为是同一份数据,
* 本类中将会进行去重。避免多次操作同一个对象。
*/
var distinct:((a:T, b:T)->Boolean)? = null
/**
* 每次resume都调用本函数。内部做了处理。
*/
fun callOnCreate(owner: LifecycleOwner) {
//如果不存在,就将入监听列表
if (owner !is ICrossNotify<*>) {
throw IllegalArgumentException("Do not call callOnCreate() because owner is not ICrossNotify!")
}
owner.lifecycle.addObserver(this)
callbackList[owner] = CrossActivityNotifyListInfo(isResumed = true)
if (BuildConfig.DEBUG) Log.d(TAG, "callOnCreate add owner $owner")
}
/**
* 暂定必须,不得为空。
* 会帮你运行在主线程。
*/
fun notify(d:T) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "changeData $d")
}
if (!isMainThread) {
mainHandler.post {
changeDataMainThread(d)
}
} else {
changeDataMainThread(d)
}
}
private fun changeDataMainThread(d: T) {
callbackList.forEach { (owner, info) ->
addWithDistinct(info, d)
if (info.isResumed) {
if (BuildConfig.DEBUG) Log.d(TAG, "changeData owner $owner isResumed onDateChanged")
owner.asOrNull<ICrossNotify<T>>()?.onCrossNotify(fetchAndClear(info))
} else {
if (BuildConfig.DEBUG) Log.d(TAG, "changeData owner $owner isNotResumed just save")
}
}
}
override fun onResume(owner: LifecycleOwner) {
super.onResume(owner)
//如果已经存在,就把当前的通知出来。
val info = callbackList[owner]!!
info.isResumed = true
if (info.data.isNotEmpty()) {
if (BuildConfig.DEBUG) Log.d(TAG, "onResumeCall exist $owner onDataChanged ${info.data}")
val outList = fetchAndClear(info)
owner.asOrNull<ICrossNotify<T>>()?.onCrossNotify(outList)
} else {
if (BuildConfig.DEBUG) Log.d(TAG, "onResumeCall exist $owner no change.")
}
}
private fun addWithDistinct(info: CrossActivityNotifyListInfo<T>, data:T) {
distinct?.let { dist->
var size = info.data.size
while (size > 0) { //倒序遍历移除
size--
if (dist(info.data[size], data)) {
info.data.removeAt(size)
}
}
}
info.data.add(data)
}
private fun fetchAndClear(info: CrossActivityNotifyListInfo<T>): List<T>? {
if (info.data.isEmpty()) {
return null
}
val outList = ArrayList<T>(info.data)
info.data.clear()
return outList
}
override fun onPause(owner: LifecycleOwner) {
super.onPause(owner)
callbackList[owner]?.isResumed = false
if (BuildConfig.DEBUG) Log.d(TAG, "onPause owner $owner")
}
override fun onDestroy(owner: LifecycleOwner) {
super.onDestroy(owner)
callbackList.remove(owner)
if (BuildConfig.DEBUG) Log.d(TAG, "onDestroy owner remove $owner")
}
}
在全局的某个单例中定义全局单例变量:
val changeData = CrossActivityNotifyListObserver<Pair<String, Boolean>>(mainHandler).also {
it.distinct = { a, b->
a.first == b.first
}
}
在更新数据的时候,changeData.notify(bean)。
想监听的activity、fragment,在onCreate函数中调用callOnCreate,并让它实现ICrossNotify即可。
这个简易的框架的目的是当onResume状态下,就把当前通知的单条变化直接通知到;
非resume状态下,就收集后,等待resume一来就发送。
代码设计为避免了粘性数据,即从某个界面监听开始,后续的变化它才会接受到。而且提供了计算去重distinct 的逻辑,比如Bean(name, info, isFavourite), 你可以判断a.name == b.name 就认为它是相同的,就允许覆盖。
简易好用。