LiveData——Google组件开发

2017-05-27  本文已影响0人  samychen

上一节曾今提到过LiveData是生命周期感知组件的示例。与ViewModel一起使用LiveData可以在遵循Android生命周期的情况下,更容易地使用数据填充UI。那么什么是LiveData?根据谷歌官方解释,LiveData是一个数据持有者类,它保持一个值,并允许观察该值。也就是说观察者可以指定某一个LifeCycle给LiveData,并对数据进行监听。与常规可观察不同,LiveData高度依赖应用程序组件的生命周期,以便Observer可以指定应遵守的生命周期。

如果观察者的生命周期处于STARTED或RESUMED状态,LiveData会将Observer视为活动状态。

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);
    }
}

当LiveData具有主动观察者时,将调用onActive()。这意味着我们需要从设备开始观察位置更新。

当LiveData没有任何活动的观察者时,将调用onInactive()。由于没有观察者正在收听,所以没有理由保持与LocationManager服务的连接。这点很重要,因为保持连接代表消耗更多的资源。

调用setValue()可更新LiveData实例的值,并通知活动的观察者。

按照上面的说法我们可以使用LocationLiveData了。

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

addObserver()方法将LifecycleOwner(LifecycleFragment)作为第一个参数传递。这样做表示这个观察者应该绑定到该生命周期,这意味着:

  1. 如果LifeCycle不在Started或者RESUMED这两个状态,即使数据发生了变化,观察者也无法接受到数据更新的回调。
  2. 如果LifeCycle销毁了,即生命周期结束,观察者将被自动从LiveData中移除。

既然LocationLiveData是生命周期感知的,那么我们就可以采用单例模式,让它可以被多个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);
    }
}

然后我们可以这么使用:

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

现在即使有多个Activity或者Fragment在使用LocationLiveData,它也能对其进行优雅的管理。不必理会界面销毁带来的诸多麻烦。

总结几点LiveData的优点:

LiveData的转换

有时候因为业务的需求,需要在LiveData将变化的数据通知给观察者前,改变数据的类型;或者是返回一个不一样的LiveData。

这里介绍一个类Transformations,它可以帮助完成上面的这些操作。

在LiveData数据的改变传递到观察者之前,在数据上应用一个方法:

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

这里我们如果只需要知道变化用户的名字,那么只要观察userName这个LiveData对象即可。它会从userLiveData数据中提取用户名并传递给它自己的观察者。

与Transformations.map()类似,只不过这里传递个switchMap()的方法必须返回一个LiveData对象。对触发器LiveData的更改做出反应,将给定的函数应用于触发器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方法后,UI代码中都需要对getPostalCode的返回值做注册观察者操作,并且还要移除上一个观察者,这样显然是低效率的。此外,如果这时UI因为配置的变化(屏幕旋转)重建了,那么它会触发再次调用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,因为它将始终不变,UI只要在需要用的时候将观察者注册到postalCode中就行。这是当用户调用setInput后,如果postalCode上有可活动的观察者,那么repository.getPostCode(address)就会被调用,如果此时没有可活动的观察者,则repository.getPostCode(address)不会被调用。

自定义转换

在你的应用中可能需要除了上面两种以外更多的LiveData的转换,为了实现这些转换,你可以使用MediatorLiveData类,它可以用来正确的处理其他多个LiveData的事件变化(addSource方法),并处理这些事件。MediatorLiveData会将自身的active/inactive状态变化正确的传递给它所处理的LiveData。

MediatorLiveData

MediatorLiveData除了onActive()和onInActive()方法之外多出了两个方法:

上一篇下一篇

猜你喜欢

热点阅读