Jetpack(二)LiveData

二、LiveData

1. 作用

• LiveData 一个可观测的数据持有类
• 数据可以被观察者订阅
• 能够感知组件(Fragment、Activity、Service)的生命周期
• 只有在组件出于激活状态才会通知观察者有数据更新

2.优势

• 确保UI和数据状态匹配
当数据发生改变的时候,会自动通知UI进行更新
• 避免内存泄漏
Observers 绑定到 Lifecycle 对象上,当与其关联的 lifecycle 被销毁的时候,它们会自动被清理
• 避免了由于 Activity 停止而导致的闪退
Observer 绑定的 Lifecycle 处于非活跃状态时,不会收到任何 LiveData 事件
• 不再需要手动处理生命周期
LiveData 具有生命周期感知能力,它会自动对这些进行管理
• 数据总处于最新状态
Lifecycle 由非活跃状态变为活跃状态时,它将收到最新的数据
• 系统配置更改时,进行数据的保存和恢复,及 UI 的恢复
配合 ViewModel 可以规避旋转屏幕等问题
• 资源共享
可以使用单例模式来扩展 LiveData,这样能达到数据变化的时候,通知所有的观察者

3. 使用
3.1. 创建 LiveData

• 创建 LiveData 实例,让其持有特定的数据类型
• 通常是将 LiveData 放在 ViewModel 中使用

// 1.创建 LiveData
private val currentName: MutableLiveData<String> by lazy {
    MutableLiveData<String>()
}
3.2. 创建 Observe

• 创建 Observer 对象,并实现 onChanged() ,在此定义 LiveData 持有的数据改变时做何操作
• 可在此更新 UI ,一般 Observer 在 UI controller 中创建,比如 Activity 或者 Fragment

// 2.创建 Observe
val nameObserver = Observer<String> { newName ->
    binding.data = newName
}
3.3. Observer 添加到 LiveData

observe(LifecycleOwner owner, Observer observer)
• owner : LifecycleOwner对象,LiveData 能监听生命周期的能力来源
• observer : 监听器对象 Observer

// 3.Observer 添加到 LiveData
currentName.observe(this@MainActivity, nameObserver)
3.4. 改变 LiveData 值

• 在UI线程通过 value 改变 LiveData 值
• 在子线程通过 postValue() 修改 LiveData 值

// 点击时改变 LiveData 值
binding.button.setOnClickListener {
    // 4.UI 线程通过 value 修改 LiveData 值
    currentName.value = "2234"
}
// 点击时改变 LiveData 值
binding.button.setOnClickListener {
    Thread {
        var num = 0
        while (num < 5) {
            try {
                Thread.sleep(1000)
            } catch (e: InterruptedException) {
                e.printStackTrace()
            }
            // 4.子线程通过 postValue() 修改 LiveData 值
            currentName.postValue((++num).toString())
        }
    }.start()
}
4. 其他
4.1. 扩展 LiveData

• 如需观察者处于 active 状态时做操作,可通过继承 LiveData 或者 MutableLiveData后复写onActive()onInactive()

class StockLiveData(symbol: String) : LiveData<BigDecimal>() {

    private val stockManager = StockManager(symbol)

    // setValue(T) 方法将更新 LiveData 实例的值,并将更改通知给任何活跃观察者。
    private val listener = { price: BigDecimal ->
        value = price
    }

    // 当 LiveData 对象具有活跃观察者时,会调用 onActive() 方法。
    // 这意味着,您需要从此方法开始观察股价更新。
    override fun onActive() {
        stockManager.requestPriceUpdates(listener)
    }

    // 当 LiveData 对象没有任何活跃观察者时,会调用 onInactive() 方法。
    // 由于没有观察者在监听,因此没有理由与 StockManager 服务保持连接。
    override fun onInactive() {
        stockManager.removeUpdates(listener)
    }
}
4.2.转换 LiveData
  1. Transformations.map()
    • 可以通过Transformationsmap 操作符 修改数据
    • 可以将数据转换为其他类型

    // ====== Transformations.map 处理 类型转换 ======
    Transformations.map(currentName) {
        Function<String, String> { input -> "修改$input" }
    }.observe(this@MainActivity, { newName ->
        binding.data = newName.toString()
    })
    
    // ====== Transformations.map 处理 类型转换 (lambda简化后) ======
    Transformations.map(currentName) { input -> "修改$input" }.observe(this, { newName ->
        binding.data = newName
    })
    
  2. Transformations.switchMap()

    • 可以通过TransformationsswitchMap() 操作符 通过room 做数据查询等

    // ====== Transformations.map 处理 类型转换 ======
    val userLiveData = Transformations.switchMap(currentName, object : Function<String, LiveData<User>> {
        override fun apply(input: String?): LiveData<User> {
            // 根据 userId 返回一个 LiveData<User>,可通过 room 获取
            return dao.getUser()
        }
    })
    // ====== Transformations.map 处理 类型转换 (lambda简化后)  ======
    val userLiveDataL = Transformations.switchMap(currentName, Function<String, LiveData<User>> {
        // 根据 userId 返回一个 LiveData<User>,可通过 room 获取
        dao.getUser()
    })
    
  3. 自定义CustomTransformations
    • 如有更复杂需求可通过 MediatorLiveData 自定义 transformations
    Transformations 源码就是通过 MediatorLiveData 实现二次转发
    • 里面其实主要用的就是MediatorLiveData,通过该类能组合多个LiveData源。当任何一个LiveData源发生改变的时候,MediatorLiveDataObservers 都会被触发。比如有两个LiveData,一个是从数据库获取,一个是从网络获取。通过MediatorLiveData就能做到,当二者任何一个获取到最新数据,就去触发监听。

    public class Transformations {
    
        private Transformations() {
        }
    
        @MainThread
        @NonNull
        public static <X, Y> LiveData<Y> map(
                @NonNull LiveData<X> source,
                @NonNull final Function<X, Y> mapFunction) {
            final MediatorLiveData<Y> result = new MediatorLiveData<>();
            result.addSource(source, new Observer<X>() {
                @Override
                public void onChanged(@Nullable X x) {
                    result.setValue(mapFunction.apply(x));
                }
            });
            return result;
        }
    
        @MainThread
        @NonNull
        public static <X, Y> LiveData<Y> switchMap(
                @NonNull LiveData<X> source,
                @NonNull final Function<X, LiveData<Y>> switchMapFunction) {
            final MediatorLiveData<Y> result = new MediatorLiveData<>();
            result.addSource(source, new Observer<X>() {
                LiveData<Y> mSource;
    
                @Override
                public void onChanged(@Nullable X x) {
                    LiveData<Y> newLiveData = switchMapFunction.apply(x);
                    if (mSource == newLiveData) {
                        return;
                    }
                    if (mSource != null) {
                        result.removeSource(mSource);
                    }
                    mSource = newLiveData;
                    if (mSource != null) {
                        result.addSource(mSource, new Observer<Y>() {
                            @Override
                            public void onChanged(@Nullable Y y) {
                                result.setValue(y);
                            }
                        });
                    }
                }
            });
            return result;
        }
    
        @MainThread
        @NonNull
        public static <X> LiveData<X> distinctUntilChanged(@NonNull LiveData<X> source) {
            final MediatorLiveData<X> outputLiveData = new MediatorLiveData<>();
            outputLiveData.addSource(source, new Observer<X>() {
    
                boolean mFirstTime = true;
    
                @Override
                public void onChanged(X currentValue) {
                    final X previousValue = outputLiveData.getValue();
                    if (mFirstTime
                            || (previousValue == null && currentValue != null)
                            || (previousValue != null && !previousValue.equals(currentValue))) {
                        mFirstTime = false;
                        outputLiveData.setValue(currentValue);
                    }
                }
            });
            return outputLiveData;
        }
    }
    

Demo地址 : LiveData[module]

2020年9月2日

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350