Android之MVVM架构指南(四):LiveData

Livedata 是一个数据源的包装类,他可以有效的取代请求信息时用到callback接口,还可以配合Lifecycle感知程序组件生命周期。

正常我们请求网络数据时的代码为:

public class ListActivity extends AppCompatActivity {
    private TextView userNameTv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        userNameTv = findViewbyId(R.id.user_name);
        NetModel.getUserName().callback(new Callback(){

            @Override
            public void onSuccess(String userName){
                userNameTv.setText(userName);
            }

        });
    }
}

同样的功能使用LiveData可以写为:

public class ListActivity extends AppCompatActivity {

    private TextView userNameTv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        userNameTv = findViewbyId(R.id.user_name);
        LiveData<String> userName = NetModel.getUserName();
        userName.observe(this, new Observer<String>() {
            @Override
            public void onChanged(@Nullable String name) {
               userNameTv.setText(name);
            }
        });
    }
}

使用时看上去并没有简化多少,但是相比之前的原始代码,如果原始代码中不对setText()方法添加生命周期的判断,当activity销毁后可能会引发空指针异常。

而在LiveData的observe方法中需要传入一个LifecycleOwner对象,所以LiveData可以感知宿主的生命周期,从而不用担心此类问题。

创建Livedata对象

Livedata 属于包装类,所以需要先创建一个Livedata对象,然后在为其填充数据。

Livedata 是一个一个抽象类,不能直接实例化,Android 默认提供了一个它的子类MutableLiveData,其源码如下:

public class MutableLiveData<T> extends LiveData<T> {
    @Override
    public void postValue(T value) {
        super.postValue(value);
    }

    @Override
    public void setValue(T value) {
        super.setValue(value);
    }
}

有没有一脸懵逼的感觉?其实这么写的原因是因为Livedata类添加数据的setValue()postValue()方法的权限不是公开的,而MutableLiveData只是将这两个方法权限公开而已。

为什么要这样做呢?主要是考虑到架构模型的问题,在数据源请求处使用MutableLiveData对象添加数据,而在UI操作的地方使用LiveData对象就只能使用数据无法改变其数据,这样的话就做到了数据只能在一个地方发生改变提升系统稳定性。当然如果你希望在任何地方都可以改变数据源,直接全部使用MutableLiveData对象即可。

至于设置数据的两个方法:

  • setValue(),在UI线程设置数据。
  • postValue(),在worker线程设置数据。

具体使用方法:

final MutableLiveData<String> data = new MutableLiveData<>();
data.setValue("test");

// in Worker Thread used
data.postValue("test");

感知生命周期

当我们调用LiveData的observe(LifecycleOwner owner,Observer<T> observer)时,LiveData会在LifecycleOwner中添加一个监听生命周期的观察者,

  • 当生命周期处于STARTEDRESUMED这种活动状态时才会通知Observer数据更新
  • 当生命周期处于非活动状态Observer不会接收到数据更新的通知
  • 当生命周期处于DESTROYED时会将监听生命周期的观察者删除

扩展使用

在上面生命周期的三种情况中,LiveData 除了内部的逻辑操作外,还提供了跟生命周期状态有关的两个回调方法:

  • onActive() ,当生命周期观察者处于活动状态后调用。

  • onInactive(),当生命周期观察者处于非活动状态后调用。

基于此,我们可以继承 LiveData进行扩展实现定制化,下面举一个定位的例子:

public class LocationLiveData extends LiveData<Position> {

      // 定位管理器
    private LocationManager mLocationManager;

    public LocationLiveData() {
        mLocationManager = new LocationManager(new LocationCallback() {
            @Override
            public void onUpdate(Position position) {
                   // 当位置信息更新时重置livedata数据
                setValue(position);
            }
        });
    }

    @Override
    protected void onActive() {
          // 生命周期处于活动状态时开启定位
        mLocationManager.startLocation();
    }

    @Override
    protected void onInactive() {
           //生命周期处于非活动状态时关闭定位
        mLocationManager.stopLocation();
    }
}

上面的代码将定位功能封装到了Livedata中,隐藏细节的同时并具备了感知生命周期的能力,在实际使用中只需要像标准的LiveData使用一样即可:

public class LocationFragment extends Fragment {
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        
        LiveData<Position> position = new LocationLiveData();
        position.observe(this,new Observer(){
            @Override
            public void onChanged(Position position) {
                // 更新UI
            }
        });
    }
}

不仅如此,我们知道 LiveData 中的observe()方法需要传入一个LifecycleOwner对象用来感知生命周期,但是它并不是唯一值,也就是说我们能够通过observe()方法同时为一个liveData对象设置两个Activity/Fragment的生命周期宿主,也就是LifecycleOwner,基于这个特性,我们可以将LiveData设置成单例模式从而实现多个组件间共享数据。

public class LocationLiveData extends LiveData<Position> {
    private LocationManager mLocationManager;

    private LocationLiveData instance;

    public static LocationLiveData get(){
        if (instance == null) {
            instance = new LocationLiveData();
        }
        return instance;
    }


    private LocationLiveData() {
        mLocationManager = new LocationManager(new LocationCallback() {
            @Override
            public void onUpdate(Position position) {
                setValue(position);
            }
        });
    }

    @Override
    protected void onActive() {
        mLocationManager.startLocation();
    }

    @Override
    protected void onInactive() {
        mLocationManager.stopLocation();
    }
}

在不同的组件中调用:

public class Fragment1 extends Fragment {
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        
        LocationLiveData.get().observe(this,new Observer(){
            @Override
            public void onChanged(Position position) {
                // 更新UI
            }
        });
    }
}


public class Fragment2 extends Fragment {
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        
        LocationLiveData.get().observe(this,new Observer(){
            @Override
            public void onChanged(Position position) {
                // 更新UI
            }
        });
    }
}

变换

这个功能跟RxJava的变换很像,说白了就是抄的RxJava的功能,实现变换功能的是Transformations类,它提供了两个变化的方法。

map() 方法

假设我们要实现一个获取所有用户的用户名功能,但是我们只有获取所有用户的接口:

public class Fragment1 extends Fragment {
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
       
        NetModel.getAllUser().observe(this, new Observer<List<user>>() {
            @Override
            public void onChanged(@Nullable List<User> users) {
                // 获取到所有用户在去拿用户名
                List<String> allUserName = new ArrayList<>();
                for (User user : users) {
                    allUserName.add(user.getUserName());
                }
                // 更新UI
            }
        });
    }
}

使用 map() 方法效果如下:

public class Fragment1 extends Fragment {
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        
        LiveData<List<String>> data = Transformations.map(NetModel.getAllUser(), new Function<List<User>, List<String>>() {
            @Override
            public List<String> apply(List<User> input) {
                List<String> allUserName = new ArrayList<>();
                for (User user : users) {
                    allUserName.add(user.getUserName());
                }
                return allUserName;
            }
        });

        data.observe(this, new Observer<List<String>>() {
            @Override
            public void onChanged(@Nullable List<String> strings) {
                // 更新UI
            }
        });


    }
}

这样做的好处除了转变操作也能感知生命周期外,更重要的是业务逻辑分离,onChanged()方法中只做更新UI的操作,代码更加健壮。

switchMap() 方法

这两种方法唯一不同的地方就是map()的变换是从一个数据源变成另外一个数据源,而switchMap()是从一个数据源变成另外一个LiveData对象,具体有什么用呢?

举一个例子,我们想实现一个获取用户所有信息的功能,但是我们需要先获取用户的id,才能通过id获取信息:

public class Fragment1 extends Fragment {
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        NetModel.getUserId().observe(this,new Observer<String>(){
            @Override
            public void onChanged(String id){
                NetModel.getUserInfo(id).observe(Fragment1.this,new Observer<User>(){
                    @Override
                    public void onChanged(User user){
                        // 更新UI
                    }
                })
            }
       });
    }
}

很明显,看着就不爽,用switchMap() 实现效果如下:

public class Fragment1 extends Fragment {
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        LiveData<User> user = Transformations.switchMap(NetModel.getUserId(), new Function<String, LiveData<User>() {
            @Override
            public LiveData<User> apply(String id) {
                return Model.getUserInfo(id);
            }
        });

        user.observe(this, new Observer<User>() {
            @Override
            public void onChanged(@Nullable List<User> users) {
                // 更新UI
            }
        });

    }
}

同样的道理,这么做不光方便管理,代码更加健壮。

合并多个LiveData

假设我们有一个获取用户信息的功能,用户信息既可以在网络获取也可以在本地缓存中获取,需要怎么实现呢?

public class Fragment1 extends Fragment {
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
       
        LocalModel.getUserInfo().observe(this, new Observer<User>() {
            @Override
            public void onChanged(@Nullable List<User> users) {
                // 更新UI
            }
        });

        NetModel.getUserInfo().observe(this, new Observer<User>() {
            @Override
            public void onChanged(@Nullable List<User> users) {
                // 更新UI
            }
        });

    }
}

上面代码有一个严重的缺陷,那就是两个observer中更新UI代码其实是一样的,这是冗余代码很垃圾,那应该怎么做呢?Android提供了一个可以将多个LiveData 对象合并成一个Livedata的功能类:MediatorLiveData,使用方法如下:

public class Fragment1 extends Fragment {
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
       
        MediatorLiveData<User> data = new MediatorLiveData();
        data.addSource(LocalModel.getUserInfo(), new Observer<User>() {
            @Override
            public void onChanged(@Nullable User user) {
                data.setValue(user);
            }
        });
        data.addSource(NetModel.getUserInfo(), new Observer<User>() {
            @Override
            public void onChanged(@Nullable User user) {
                data.setValue(user);
            }
        });
        data.observe(this,new Observer<User>() {
            @Override
            public void onChanged(@Nullable User user) {
                // 更新UI
            }
        });

    }
}

MediatorLiveData 通过addSource()方法可以观察多个Livedata对象的数据变化。

下一节

Android之MVVM架构指南(五):ViewModel

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

推荐阅读更多精彩内容