一种全局数据变化而且是多个的通知实现

有个需求:
从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 就认为它是相同的,就允许覆盖。

简易好用。

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-20 09:26:02       52 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-20 09:26:02       54 阅读
  3. 在Django里面运行非项目文件

    2024-07-20 09:26:02       45 阅读
  4. Python语言-面向对象

    2024-07-20 09:26:02       55 阅读

热门阅读

  1. 极狐GitLab如何配置使用独立数据库?

    2024-07-20 09:26:02       18 阅读
  2. python 66 个冷知识 0716

    2024-07-20 09:26:02       16 阅读
  3. 【数据库技术NoSQL】MongoDB和Cassandra的使用

    2024-07-20 09:26:02       16 阅读
  4. live555搭建实时播放rtsp服务器

    2024-07-20 09:26:02       19 阅读
  5. 服务器相关总结

    2024-07-20 09:26:02       16 阅读
  6. ES6 字符串的新增方法(二十)

    2024-07-20 09:26:02       14 阅读
  7. C语言初学者入门指南

    2024-07-20 09:26:02       13 阅读
  8. 如何看待中国信息协会2024年网络安全大赛

    2024-07-20 09:26:02       14 阅读
  9. Symfony框架概述

    2024-07-20 09:26:02       16 阅读
  10. go reflect的工程使用

    2024-07-20 09:26:02       17 阅读
  11. RKE部署k8s

    2024-07-20 09:26:02       19 阅读
  12. 关于取模的相关注意

    2024-07-20 09:26:02       18 阅读
  13. nodejs使用request后端访问第三方接口

    2024-07-20 09:26:02       18 阅读
  14. docker compose 部署交互模式的容器-以Ubuntu为例

    2024-07-20 09:26:02       17 阅读