翻译官方文档LiveData

官方文档链接:https://developer.android.google.cn/topic/libraries/architecture/livedata.html

1.前言


前面讲到了AAC框架对于生命周期的支持,这一篇来看看框架自己提供的可感知生命周期的类。除了学会怎么使用它,还能增加对框架的核心Lifecycle的认识。

2.使用案例


LiveData这个类持有数据并允许数据被观察。不同于普通的被观察者,它可以被指定组件的Lifecycle,按照该生命周期提供观察的通知。LiveData认为,只有当观察者绑定的Lifecycle.State处于STARTED或者RESUMED时,观察者才是活跃的(可以发送通知)。

public class LocationLiveData extends LiveData<Location> {
    private LocationManager locationManager;

    private SimpleLocationListener listener = new SimpleLocationListener() {
        @Override
        public void onLocationChanged(Location location) {
            setValue(location);
        }
    };

    public LocationLiveData(Context context) {
        locationManager = (LocationManager) context.getSystemService(
                Context.LOCATION_SERVICE);
    }

    @Override
    protected void onActive() {
        locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, listener);
    }

    @Override
    protected void onInactive() {
        locationManager.removeUpdates(listener);
    }
}

上面实现位置服务的监听有三个需注意的地方:

  • onActive(),当LiveData有一个活跃的观察者时就会被调用。可以在这里开始观察设备位置的更新。
  • onInactive(),当LiveData连一个活跃的观察者都没有时就会被调用。此时没有必要与位置服务保持连接,因为保持连接会消耗大量的电量而且没任何作用。
  • setValue(),调用此方法更新LiveData中的数据,并通知活跃的观察者发生的改变。

LocationLiveData可以按照以下方式使用:

public class MyFragment extends LifecycleFragment {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        LiveData<Location> myLocationListener = ...;
        Util.checkUserStatus(result -> {
            if (result) {
                myLocationListener.observe(this, location -> {
                    // update UI
                });
            }
        });
    }
}

observe()方法传入LifecycleOwner对象作为第一个参数,意味着之后传入的观察者将绑定到Lifecycle上:

  • 如果Lifecycle不在活跃状态(STARTED或RESUMED),当值发生改变后,观察者并不会收到通知。
  • 如果Lifecycle被销毁了,观察者将自动从观察队列中移除。

3.生命周期感知


实际上,LiveData的生命周期感知特性提供了新的选择:在Activity、Fragment等多个组件间共享数据。下面的例子为了简单,使用了单例模式:

public class LocationLiveData extends LiveData<Location> {
    private static LocationLiveData sInstance;
    private LocationManager locationManager;

    @MainThread
    public static LocationLiveData get(Context context) {
        if (sInstance == null) {
            sInstance = new LocationLiveData(context.getApplicationContext());
        }
        return sInstance;
    }

    private SimpleLocationListener listener = new SimpleLocationListener() {
        @Override
        public void onLocationChanged(Location location) {
            setValue(location);
        }
    };

    private LocationLiveData(Context context) {
        locationManager = (LocationManager) context.getSystemService(
                Context.LOCATION_SERVICE);
    }

    @Override
    protected void onActive() {
        locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, listener);
    }

    @Override
    protected void onInactive() {
        locationManager.removeUpdates(listener);
    }
}

现对上面的Fragment进行如下改写:

public class MyFragment extends LifecycleFragment {
    public void onActivityCreated (Bundle savedInstanceState) {
        Util.checkUserStatus(result -> {
            if (result) {
                LocationLiveData.get(getActivity()).observe(this, location -> {
                   // update UI
                });
            }
        });
  }
}

即使有多个Activity和Fragment在观察LocationLiveData对象,但仍能做到规范地管理,比如,至少有一个观察者是活跃的,才会连接到系统服务。所以,使用LiveData有以下好处:

  • 不会内存泄漏:既然观察者绑定到了自己的Lifecycle对象上,那么当生命周期结束时,会被自动清理。
  • 不会因为Activity停止而崩溃:如果观察者的Lifecycle不是活跃状态(比如在回退栈中),将不会收到改变的事件。
  • 始终保持数据最新:如果Lifecycle重新启动(比如,Activity从回退栈中恢复到STARTED状态),观察者将接收到最新的位置信息(除非还没有)。
  • 正确处理配置更改:如果Activity或Fragment由于配置更改(比如,旋转屏幕)而重建,观察者立刻接收到最新有效的位置信息。
  • 共享资源:可以保持单个LocationLiveData实例,只连接到系统服务一次,并且正确支持应用中所有的观察者。
  • 不需要手动处理生命周期:也许会注意到,例子中的Fragment仅在想要时观察数据,不用担心生命周期结束后,仍在观察或才开始观察。Lifecycle会自动管理所有的一切,只要Fragment在观察时提供了自己的Lifecycle。

4.LiveData转换


有时候,希望在给观察者分发数据之前改变LiveData的值,或者根据不同的值返回不同的LiveData对象。Lifecycle包提供了Transformations类,包含有助于执行这些操作的方法。

  • Transformations.map()

    适用于传入改变LiveData值的方法,并将结果向下游传递。

    LiveData<User> userLiveData = ...;
    LiveData<String> userName = Transformations.map(userLiveData, user -> {
        user.name + " " + user.lastName
    });
    
  • Transformations.switchMap()

    与map()方法类似,适用于传入改变LiveData的值再封装起来的方法,并将结果向下游传递。注意,传入的方法返回的LiveData对象必须包含Lifecycle。

    private LiveData<User> getUser(String id) {
      ...;
    }
    
    LiveData<String> userId = ...;
    LiveData<User> user = Transformations.switchMap(userId, id -> getUser(id) );
    

这些转换操作允许在调用链上携带传递观察者绑定的Lifecycle信息,当有观察者观察转换后的LiveData时,才真正开始执行这些转换(类似响应式编程)。这种方式允许隐式传递生命周期相关的行为,不需要添加显式的调用和依赖。任何时候,在ViewModel中需要一个Lifecycle对象,转换操作有可能解决问题。

举个例子,假设有个界面,用户输入一个地址,将收到该地址对应的邮编。

class MyViewModel extends ViewModel {
    private final PostalCodeRepository repository;
    public MyViewModel(PostalCodeRepository repository) {
       this.repository = repository;
    }

    private LiveData<String> getPostalCode(String address) {
       // DON'T DO THIS
       return repository.getPostCode(address);
    }
}

如果这样实现,界面需要在每次调用getPostalCode()方法时,先从之前的LiveData对象中注销观察者,再注册到新的LiveData对象中。此外,当界面重建时,将会重新调用getPostalCode()方法,得到的对象也不是之前调用的结果。建议实现一个从输入的地址到邮编信息的转换,来取代上面的方法。

class MyViewModel extends ViewModel {
    private final PostalCodeRepository repository;
    private final MutableLiveData<String> addressInput = new MutableLiveData();
    public final LiveData<String> postalCode =
            Transformations.switchMap(addressInput, (address) -> {
                return repository.getPostCode(address);
             });

  public MyViewModel(PostalCodeRepository repository) {
      this.repository = repository
  }

  private void setInput(String address) {
      addressInput.setValue(address);
  }
}

成员属性postalCode对象不会改变索引,可以使用public final修饰。定义了基于addressInput对象的转换,当它发生改变且有活跃的观察者时,将会调用getPostalCode()方法。若当时没有活跃的观察者,不会执行任何转换直到加入一个观察者。

这种机制允许在应用底层创建LiveData对象,并满足响应式地运行。而ViewModel能容易地获取它们,并基于它们定义转换规则。

5.创建新的转换


应用可能需要十几种不同类型地特定转换,但是默认没有提供。可以使用MediatorLiveData类来实现自己所需的转换,因为它是为了正确监听其它LiveData实例,并处理它们发出的事件而专门创建的类。MediatorLiveData关注于正确传递活跃/非活跃状态给原始的LiveData。

6.总结


LiveData目的就是为了控制反转(IOC),实时地反馈数据的变化来驱动界面的更新。因为数据变化的发生不受界面控制,若不加入对界面生命周期的监听,容易出现操作的发生与界面的生命周期不符。

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