Jetpack mvvm 三部曲(二) LiveData

在上篇讲了下ViewModel 这次接着讲LiveData

下一篇 Jetpack mvvm 三部曲(三) DataBinding
先放下本jetpak系列在学习过程写的demojetpackDemo
  • 先贴下官方的链接LiveData

  • 根据官方给的说发LiveData可为数据提供观察者(就是一个接口),在数据进行改变的时候活跃的观察者可以获取到数据的变化非活跃的观察者是不能收到变化通知的

  • 我在这里写了个计时器先记录生命周期的状态以及LiveData数值,当数据产生变化打印看下生命周期方法以及当前的数值,可以看到在app回到桌面进入到onPause后观察者就收不到通知了,而重新从后台回到app进入onResume状态观察者又能收到通知了。


    log
  • LiveData能做到这点还是要归功于LifecycleOwner接口,当然AndroidX的AppCompatActivity类已经帮我们实现了LifecycleOwner接口

  • 先看下怎么实现在看下源码

  • 引用(2.2.0目前是最新版本) 其余版本可参考

    implementation 'androidx.lifecycle:lifecycle-livedata:2.2.0'
官方提示
public class MyViewModel extends ViewModel {

    public MutableLiveData<String> stringMediatorLiveData;

    public MyViewModel() {
        stringMediatorLiveData = new MutableLiveData<>();
    }

}

  • 实现监听LiveData数据的变化使用observe方法传入LifecycleOwner,在实现观察者Observer接口就行了,通过setValue 或者 postValue改变LiveData的值就能在Observer接口的onChanged方法收到改变后的数值了。
  • 这里有一个注意的点在主线程使用可以用setValue,如果是子线程改变值用postValue,因为在多线程中操作数据是不安全的,而postValue是通过synchronized关键字确保线程安全


    官方提醒

    postValue方法
  MyViewModel  viewModel = new ViewModelProvider(this).get(MyViewModel.class);
        viewModel.stringMediatorLiveData.observe(this, new Observer<String>() {
            @Override
            public void onChanged(String s) {
                Log.e("MainActivity",s+"----");
            }
        });
        textView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                viewModel.stringMediatorLiveData.setValue("哈哈哈哈");    
            }
        });
  • 实现的代码敲完了,接着看下源码。


    图1

    图2

    图3

    图4

    图5

    图6

    图7

    图8

    图9
  • 图一是传LifecycleOwner过去

  • 图二是传递保存在一个map对象中

  • 图3是把LifecycleObserver拿到更新生命周期状态

  • 图4是回调LiveDataon中LifecycleBoundObserver类的StateChanged方法见图5

  • 图6调用dispatchingValue改变值的时候会通过mObservers这个map对象进行遍历见图7,从map集合中取出观察者ObserverWrapper接口调用onChanged方法进行回调通知注意图8的第一个红框return如果observer.mActiveactivity的活跃状态为false就直接不走下面的方法了,这个mAtive参数的赋值见图5 activeStateChanged(shouldBeActive()); shouldBeActive这个方法见图9

-------------------------------分割线-------------------------------

  • 到这里LiveData你就有了初步的认知,在上面的例子是直接通过MutableLiveData去实现的。
  • MutableLiveData既可以去改变值也可以监听值的变化但是为了数值的维护客观性(ps.不要直接通过MutableLiveData去setValue如果多个地方改变值不太容易看出来不方便后期),我们可以通过LiveData去写(MutableLiveData其实是继承自LiveData只不过就是暴露了下setValue跟postValue方法)


  • 鉴于LiveData的setValue和postValue方法修饰关键词是protected(ps.包名不同无法调用该方),我们可以这么去实现。
public class MyViewModel extends ViewModel {
    private MutableLiveData<String> stringMediatorLiveData;
    public LiveData<String> stringLiveData;

    public MyViewModel() {
        stringMediatorLiveData = new MutableLiveData<>();
        stringLiveData = stringMediatorLiveData;
        this.user = new User("张三",11);
        this.count = 13;
    }

    public void update(String s){
        stringMediatorLiveData.setValue(s);
    }
}
  • 使用
 MyViewModel  viewModel = new ViewModelProvider(this).get(MyViewModel.class);
        viewModel.stringLiveData.observe(this, new Observer<String>() {
            @Override
            public void onChanged(String s) {
                Log.e("MainActivity",s+"----");
            }
        });
       
        textView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                viewModel.update("哈哈哈哈");
            }
        });
  • 虽然多写了写代, 但是从代码维护来看更合理写(ps.官方也是建议这么做的)

-------------------------------分割线-------------------------------

如果就想去实时监听数据更新不管页面可见不可见那怎么办

  • 这个时候observeForever方法就出马了
  • 看了上面得源码部分就应该知道LiveData的observe接口是怎么根据可见不可见状态被调用的
  • observeForever方法直接把状态改成了全程可见,而不像observe方法由LifecycleBoundObserver根据shouldBeActive方法去设定了。


    image.png

    image.png

    image.png
  • 把observe改成observeForever方法就能实时去监听数值变化了,毕竟observeForever直接把状态设置成可见了,源码那快图8那个considerNotify方法就不会因为不可见状态return调了不去执行下面接口回调的方法了
  Observer<Integer> integerObserver = new Observer<Integer>() {
            @Override
            public void onChanged(Integer integer) {
                Log.e("LiveDataActivity",string+"--->"+integer);
            }
        };
        model.time.observeForever(integerObserver);
  • 注意observeForever要自己去removeObserver掉监听,因为observe方法中LifecycleBoundObserver自己处理了


    image.png
在扯下MediatorLiveData
  • MediatorLiveData是MutableLiveData的子类

  • MutableLiveData这个类是继承LiveData把postValue和setValue暴露出来


    image.png
  • MediatorLiveData这个就有点意思了正如他的名字Mediator(中介的意思)

  • MediatorLiveData的核心是私有静态内部类Source



    image.png
  • 先记住红框的部分很重要,addSource就是把另一个LiveData由Source进行代理,监听当被代理的LiveData数值发生改变那么他就会去回调传入的观察者onChanged方法

  • 原理很简单那有什么用呢

  • 假设下面的场景实时知道文本框输入了多少个字


    image.png
  public MutableLiveData<String> message = new MutableLiveData<>();
  public MediatorLiveData<Integer> messageNumber = new 
  MediatorLiveData<>();
  messageNumber.setValue(0);
  messageNumber.addSource(message, new Observer<String>() {
            @Override
            public void onChanged(String s) {
                messageNumber.setValue(s.length());
        }
 });
<LinearLayout
        android:layout_width="match_parent"
        android:orientation="horizontal"
        android:layout_height="wrap_content">
        <EditText
            android:id="@+id/edit"
            android:layout_width="200dp"
            android:layout_marginLeft="10dp"
            android:paddingLeft="10dp"
            android:paddingRight="10dp"
            android:text="@={mode.message}"
            android:singleLine="true"
            android:background="@drawable/b2"
            android:layout_height="50dp"/>

        <TextView
            android:layout_width="wrap_content"
            android:layout_gravity="center"
            android:layout_marginLeft="10dp"
            android:textColor="#cc00"
            android:text='@{String.format("输入了%d个字",mode.messageNumber)}'
            android:layout_height="wrap_content"/>
    </LinearLayout>
  • 这样实现就行了
  • 当然绝对不能忘记注销LiveData的监听,原因嘛在observeForever也讲了的,我们去ViewModel的onCleared方法做就好了,毕竟onCleared是ViewModel执行的最后一个方法。
   @Override
    protected void onCleared() {
        super.onCleared();
        messageNumber.removeSource(message);
    }
总结下
  • 普通数据使用MutableLiveData和LiveData就行了
  • 设计到数据联动的时候考虑下MediatorLiveData
最后在说1个东西转换
  • 在项目中我们经常回运到数据转换的问题比如int转换成string或者int转换成另一个对象
map
public class LiveDataViewModel extends ViewModel {
    private MutableLiveData<User> userMutableLiveData;
    public LiveData<Integer> age;
    
    private List<User> users;
    public LiveDataViewModel() {
        userMutableLiveData = new MutableLiveData<>();
        age = Transformations.map(userMutableLiveData, new Function<User, Integer>() {
            @Override
            public Integer apply(User input) {
                return input.getAge();
            }
        });
     public void addUser(User user){
        //这里值得注意的是setValue主线程用没问题,如果是子线程那就要用postValue
        // userMutableLiveData.postValue(user);
        if(Looper.myLooper() == Looper.getMainLooper()){
            userMutableLiveData.setValue(user);
        }else {
            userMutableLiveData.postValue(user);
        }
    }
}
  • 这时直接去监听age就行了,只要userMutableLiveData的值有改变我们就能立马监听到User类中的年龄了
   model.age.observe(this, new Observer<Integer>() {
            @Override
            public void onChanged(Integer integer) {
                Log.e("MainActivity",integer+"--");
            }
        });
switchMap
  • switchMap和map同样属于数据转换的一种,在实际项目中switchMap使用的频率回比map更高map是通过监听一个对象的变换然后进行转换的,假设有另外一个场景通过用户id进行网络请求去拿到这个用户的信息,按着map的写法岂不是要有好几个步骤先通过网络请求去拿到user对象、再去赋值给LiveData、在转换给LiveData、在对LiveData进行监听改变ui。
  • 下面直接模拟下子线程查询进行用户查询
public class LiveDataViewModel extends ViewModel {
    public LiveData<User> userLiveData;
    private MutableLiveData<Integer> userIndex = new MutableLiveData<>();

    private List<User> users;
    public LiveDataViewModel() {
        users = new ArrayList<>();
        users.add(new User("李四",16));
        users.add(new User("张三",18));
        users.add(new User("王武",17));
        users.add(new User("钱六",20));
        userLiveData =  Transformations.switchMap(userIndex, new Function<Integer, LiveData<User>>() {
            @Override
            public LiveData<User> apply(Integer input) {
                return getUser(input);
            }
        });
    }
    public void queryUser(int userId){
        userIndex.setValue(userId);
    }
    //模拟查询
    public  LiveData<User> getUser(int userId){
        MutableLiveData<User> userMutableLiveData = new MutableLiveData<>();
        if(userId>users.size()-1){
            userMutableLiveData.setValue(new User("没有该用户",0));
            return userMutableLiveData;
        }
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                userMutableLiveData.postValue(users.get(userId));
            }
        }).start();
        return userMutableLiveData;
    }
}
  • 监听
    model.userLiveData.observe(this, new Observer<User>() {
            @Override
            public void onChanged(User user) {
                dataBinding.name.setText("名字:"+user.getName());
                dataBinding.tvAge.setText("年龄:"+user.getAge());

            }
        });
  • 当调用了queryUser方法就会去触发switchMap方法中的Function接口apply方法去查询用户数据了
最后的最后说下onChanged回调是在主线程的,不然会抛异常这也解释了子线程得用postValue去更新数值
  • setValue


    image.png

    image.png
  • postValue


    image.png

    image.png

到此LiveData便就结束了 写的不对不好的地方请大家指点

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