理解Android Architecture Component

作者: From64KB | 来源:发表于2017-10-24 16:34 被阅读2694次

LiveData

LiveData是一种持有可被观察数据的类。和其他可被观察的类不同的是,LiveData是有生命周期感知能力的,这意味着它可以在activities, fragments, 或者 services生命周期是活跃状态时更新这些组件。那么什么是活跃状态呢?上篇文章中提到的STARTEDRESUMED就是活跃状态,只有在这两个状态下LiveData是会通知数据变化的。

要想使用LiveData(或者这种有可被观察数据能力的类)就必须配合实现了LifecycleOwner的对象使用。在这种情况下,当对应的生命周期对象DESTORY时,才能移除观察者。这对Activity或者Fragment来说显得尤为重要,因为他们可以在生命周期结束的时候立刻解除对数据的订阅,从而避免内存泄漏等问题。

使用LiveData的优点

  • UI和实时数据保持一致 因为LiveData采用的是观察者模式,这样一来就可以在数据发生改变时获得通知,更新UI。
  • 避免内存泄漏 观察者被绑定到组件的生命周期上,当被绑定的组件销毁(destory)时,观察者会立刻自动清理自身的数据。
  • 不会再产生由于Activity处于stop状态而引起的崩溃 例如:当Activity处于后台状态时,是不会收到LiveData的任何事件的。
  • 不需要再解决生命周期带来的问题 LiveData可以感知被绑定的组件的生命周期,只有在活跃状态才会通知数据变化。
  • 实时数据刷新 当组件处于活跃状态或者从不活跃状态到活跃状态时总是能收到最新的数据
  • 解决Configuration Change问题 在屏幕发生旋转或者被回收再次启动,立刻就能收到最新的数据。
  • 数据共享 如果对应的LiveData是单例的话,就能在app的组件间分享数据。这部分详细的信息可以参考继承LiveData

使用LiveData

  1. 创建一个持有某种数据类型的LiveData (通常是在ViewModel中)
  2. 创建一个定义了onChange()方法的观察者。这个方法是控制LiveData中数据发生变化时,采取什么措施 (比如更新界面)。通常是在UI Controller (Activity/Fragment) 中创建这个观察者。
  3. 通过 observe()方法连接观察者和LiveData。observe()方法需要携带一个LifecycleOwner类。这样就可以让观察者订阅LiveData中的数据,实现实时更新。

创建LiveData对象

LiveData是一个数据的包装。具体的包装对象可以是任何数据,包括集合(比如List)。LiveData通常在ViewModel中创建,然后通过gatter方法获取。具体可以看一下代码:

public class NameViewModel extends ViewModel {

// Create a LiveData with a String 暂时就把MutableLiveData看成是LiveData吧,下面的文章有详细的解释
private MutableLiveData<String> mCurrentName;

    public MutableLiveData<String> getCurrentName() {
        if (mCurrentName == null) {
            mCurrentName = new MutableLiveData<String>();
        }
        return mCurrentName;
    }

// Rest of the ViewModel...
}

观察LiveData中的数据

通常情况下都是在组件的onCreate()方法中开始观察数据,原因有以下两点:

  • 系统会多次调用onResume()方法。
  • 确保Activity/Fragment在处于活跃状态时立刻可以展示数据。

下面的代码展示了如何观察LiveData对象:

public class NameActivity extends AppCompatActivity {

    private NameViewModel mModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Other code to setup the activity...

        // Get the ViewModel.
        mModel = ViewModelProviders.of(this).get(NameViewModel.class);

        // Create the observer which updates the UI.
        final Observer<String> nameObserver = new Observer<String>() {
            @Override
            public void onChanged(@Nullable final String newName) {
                // Update the UI, in this case, a TextView.
                mNameTextView.setText(newName);
            }
        };

        // Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.
        mModel.getCurrentName().observe(this, nameObserver);
    }
}

更新LiveData对象

如果想要在UI Controller中改变LiveData中的值呢?(比如点击某个Button把性别从男设置成女)。LiveData并没有提供这样的功能,但是Architecture Component提供了MutableLiveData这样一个类,可以通过setValue(T)postValue(T)方法来修改存储在LiveData中的数据。MutableLiveDataLiveData的一个子类,从名称上也能看出这个类的作用。举个直观点的例子:

mButton.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
        String anotherName = "John Doe";
        mModel.getCurrentName().setValue(anotherName);
    }
});

调用setValue()方法就可以把LiveData中的值改为John Doe。同样,通过这种方法修改LiveData中的值同样会触发所有对这个数据感兴趣的类。那么setValue()postValue()有什么不同呢?区别就是setValue()只能在主线程中调用,而postValue()可以在子线程中调用。

Room和LiveData配合使用

Room可以返回LiveData的数据类型。这样对数据库中的任何改动都会被传递出去。这样修改完数据库就能获取最新的数据,减少了主动获取数据的代码。详细的例子在前面的文章,不记得可以回去翻翻。

继承LiveData扩展功能

LiveData的活跃状态包括:STARTED或者RESUMED两种状态。那么如何在活跃状态下把数据传递出去呢?下面是示例代码:

public class StockLiveData extends LiveData<BigDecimal> {
    private StockManager mStockManager;

    private SimplePriceListener mListener = new SimplePriceListener() {
        @Override
        public void onPriceChanged(BigDecimal price) {
            setValue(price);
        }
    };

    public StockLiveData(String symbol) {
        mStockManager = new StockManager(symbol);
    }

    @Override
    protected void onActive() {
        mStockManager.requestPriceUpdates(mListener);
    }

    @Override
    protected void onInactive() {
        mStockManager.removeUpdates(mListener);
    }
}

可以看到onActive()onInactive()就表示了处于活跃和不活跃状态的回调。

public class MyFragment extends Fragment {
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        LiveData<BigDecimal> myPriceListener = ...;
        myPriceListener.observe(this, price -> {
            // Update the UI.
        });
    }
}

如果把StockLiveData写成单例模式,那么还可以在不同的组件间共享数据。代码如下:

public class StockLiveData extends LiveData<BigDecimal> {
    private static StockLiveData sInstance;
    private StockManager mStockManager;

    private SimplePriceListener mListener = new SimplePriceListener() {
        @Override
        public void onPriceChanged(BigDecimal price) {
            setValue(price);
        }
    };

    @MainThread
    public static StockLiveData get(String symbol) {
        if (sInstance == null) {
            sInstance = new StockLiveData(symbol);
        }
        return sInstance;
    }

    private StockLiveData(String symbol) {
        mStockManager = new StockManager(symbol);
    }

    @Override
    protected void onActive() {
        mStockManager.requestPriceUpdates(mListener);
    }

    @Override
    protected void onInactive() {
        mStockManager.removeUpdates(mListener);
    }
}

转换LiveData中的值(Transform LiveData)

这么说很容易和上文改变LiveData中的值搞混。这里的变换是指在LiveData的数据被分发到各个组件之前转换值的内容,各个组件收到的是转换后的值,但是LiveData里面数据本身的值并没有改变。(和RXJava中map的概念很像)Lifecycle包中提供了Transformations来提供转换的功能。

Transformations.map()

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

把原来是包含User的LiveData转换成包含String的LiveData传递出去。

Transformations.switchMap()

private LiveData<User> getUser(String id) {
  ...;
}

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

和上面的map()方法很像。区别在于传递给switchMap()的函数必须返回LiveData对象。
和LiveData一样,Transformation也可以在观察者的整个生命周期中存在。只有在观察者处于观察LiveData状态时,Transformation才会运算。Transformation是延迟运算的(calculated lazily),而生命周期感知的能力确保不会因为延迟发生任何问题。

如果在ViewModel对象的内部需要一个Lifecycle对象,那么使用Transformation是一个不错的方法。举个例子:假如有个UI组件接受输入的地址,返回对应的邮政编码。那么可以 实现一个ViewModel和这个组件绑定:

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

看代码中的注释,有个// DON'T DO THIS (不要这么干),这是为什么?有一种情况是如果UI组件被回收后又被重新创建,那么又会触发一次 repository.getPostCode(address)查询,而不是重用上次已经获取到的查询。那么应该怎样避免这个问题呢?看一下下面的代码:

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变量的修饰符是publicfinal,因为这个变量的是不会改变的。哎?不会改变?那我输入不同的地址还总返回相同邮编?先打住,postalCode这个变量存在的作用是把输入的addressInput转换成邮编,那么只有在输入变化时才会调用repository.getPostCode()方法。这就好比你用final来修饰一个数组,虽然这个变量不能再指向其他数组,但是数组里面的内容是可以被修改的。绕来绕去就一点:当输入是相同的情况下,用了 switchMap() 可以减少没有必要的请求。并且同样,只有在观察者处于活跃状态时才会运算并将结果通知观察者。

合并多个LiveData中的数据

MediatorLiveData是LiveData的子类,可以通过MediatorLiveData合并多个LiveData来源的数据。同样任意一个来源的LiveData数据发生变化,MediatorLiveData都会通知观察他的对象。说的有点抽象,举个例子。比如UI接收来自本地数据库和网络数据,并更新相应的UI。可以把下面两个LiveData加入到MeidatorLiveData中:

  • 关联数据库的LiveData
  • 关联联网请求的LiveData

相应的UI只需要关注MediatorLiveData就可以在任意数据来源更新时收到通知。

相关文章:
理解Android Architecture Components系列(一)
理解Android Architecture Components系列(二)
理解Android Architecture Components系列之Lifecycle(三)
理解Android Architecture Components系列之LiveData(四)
理解Android Architecture Components系列之ViewModel(五)
理解Android Architecture Components系列之Room(六)
理解Android Architecture Components系列之Paging Library(七)

相关文章

网友评论

  • 9153360c477d:问一下MyViewModel调用时,构造函数怎么传参数进去呢

    ViewModelProviders.of(this).get(MyViewModel.class);
    这样调用能调用到MyViewModel(PostalCodeRepository repository)这个吗
    jesse920524:可以通过dagger2将repository注入到vm中。

    当然也可以不用dagger2, 我是这样写的:

    private UserProfileRepository mRepository;

    public UserProfileVM(){
    this.mRepository = new UserProfileRepository();
    }
  • 海本默:MediatorLiveData,这个怎么用呢,addsource加了N个livedata进去,完全没反应
    From64KB:加了以后相应的liveData改变了吗?
  • Herbib:还是不太懂switchMap的意义何在,防止重复请求,直接在第一次请求用变量保存map()转换后的数据不行吗
    From64KB:@Herbib 额,我说的不清楚啊。又看了下文档,大致的意思是map会返回包含某个数据的LiveData,而switchMap会返回某个LiveData经过转换的LiveData。在使用switchMap的情况下,源LiveData发生改变,经过转换的LiveData也会发生改变。详细的文档见这里:https://developer.android.com/reference/android/arch/lifecycle/Transformations.html
    Herbib:@sunjenry 那map也是转换格式,而返回LiveData如果没有添加观察者的话有什么用,主要还是不懂有了map,switchMap的作用是什么,比如RxJava的flatMap是一对多的转换,但switchMap没有这个作用啊
    From64KB:@Herbib switchMap的意义在于转换数据的格式,并返回转换之后包含数据的LiveData

本文标题:理解Android Architecture Component

本文链接:https://www.haomeiwen.com/subject/ogxuuxtx.html