美文网首页
Android 结构组件之LiveData

Android 结构组件之LiveData

作者: Ggx的代码之旅 | 来源:发表于2017-06-07 10:51 被阅读816次

LiveData

LiveData是一个数据持有类,它持有一个数据,且允许这个数据被观察。不同于一般的Observable,LiveData关联了App组件(如activity,fragment,service)的生命周期,这样确保LiveData只更新处于活动生命周期状态下的应用程序组件观察者。

小提示:如何添加LiveData可以查看文章Android 结构组件之Adding Components to your Project

LiveData包含了一个观察者,由Observer类表示,如果它的生命周期在STARTED 或者RESUMED,则处于活动状态.LiveData只通知处于活动的观察者去更新,而非活跃的注册对象不会接受到通知更新。
您可以注册一个观察者,并与一个实现了LifecycleOwner接口的对象所配对。当对应的生命周期对象的状态发生变化时,这种关系允许删除观察者。这对于Activity和Fragment是非常有用的,因为当他们的生命周期被销毁时,他们就会立即取消订阅,这样就可以不用担心Activity或Fragment的内存泄露。

LiveData的好处

  1. 确保UI与数据状态相匹配
    LiveData遵循观察者模式,当生命周期状态发送改变时,LiveData会通知观察者对象,您可以合并代码以更新这些观察者对象中的UI。您的观察者可以在任意变化的情况下更新UI,而不是每次数据发生变更时更新UI。
  2. 没有内存泄露
    观察者被绑定到生命周期对象上,并在其关联的生命周期被销毁后会进行清理。所以不会有内存泄露。
  3. 不会因为Activity停止而崩溃
    如果观察者的生命周期处于不活动状态下,例如Activity在后台栈中,那不会接收到任意的LiveData事件。
  4. 不在需要手动管理生命周期
    UI组件只需要观察相关的数据,不需要停止和恢复观察。LiveData自动管理这些,因为他知道相关的生命周期的变化。
  5. 始终保持最新数据
    如果一个生命周期变得不活动,它将在再次激活时接收最新的数据。例如,后台的某个Activity在返回到前台后会接收最新的数据。
  6. 适当的配置更改
    如果一个Activity或者Fragment由于配置改变而重新创建,例如设备旋转,它会立即接收到最新的可用数据。
  7. 共享资源
    您可以使用单例模式扩展LiveData对象,以包装系统服务,以便它们可以在您的应用程序中共享。LiveData对象一旦连接到系统服务,然后任何需要资源的观察者都可以看到LiveData对象。更多信息,查阅 Extend LiveData.

使用LiveData对象工作

下面这些步骤教您使用LiveData对象进行工作:

  1. 创建LiveData实例来保存特定类型的数据。
  2. 创建一个Observer对象,该对象定义了一个onChanged()方法,该方法控制LiveData对象的数据更改时发生的情况。
  3. 使用observe()方法将Observer对象附加到LiveData对象上。observe()方法使用LifecycleOwner 对象。Observer对象订阅了LiveDara对象,这样以便通知其更改。一般来说我们将Observer对象附加到UI控制器中,如Activity或者Fragment.

注意:您可以使用observeForever(observer)方法注册一个没有关联的生命周期所有者对象的观察者。在这种情况下,观察者被认为总是活跃的,因此总是会被告知修改。您可以删除这些观察者,调用removeObserver(Observer)方法。

当你更新存储在LiveData对象中的值时,它会触发所有注册在LiveData中附加的LifecycleOwner是活跃的的观察者们。

LiveData允许UI控制器的观察者们去订阅和更新。当LiveData对象所持有的数据发生变化时,UI会自动更新响应。

创建LiveData对象

LiveData能够被用来包裹任意的数据,包括对象和集合。LiveData对象通常存储在ViewModel对象中,并通过getter方法访问。如下所示:

public class NameViewModel extends ViewModel {

// Create a LiveData with a String
private MutableLiveData<String> mCurrentName;

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

// Rest of the ViewModel...
}

最初LiveData对象中的数据是不能设置的。
注意:确保在ViewModel对象中存储更新UI的LiveData对象,而不是活动或片段,原因如下:

  • 避免膨胀的活动和碎片。现在这些UI控制器负责显示数据,但不负责保存数据状态。
  • 将LiveData实例与特定的活动或片段实例解耦,并允许LiveData对象在配置更改中存活。

观察LiveData对象

大多数情况下,App组件的onCreate()方法是开始观察LiveData对象适合的地方,原因如下:

  • 确保了系统不会再Activity和Fragment的onResume方法中冗余调用。
  • 确保了Activity和Fragment在活跃后立即显示数据,一旦应用程序组件处于START的状态,它就从它所观察到的LiveData对象接收最新的值。只有在设置要观察的LiveData对象时才会发生这种情况。

一般来说,LiveData只在数据更改时才会提供更新,并且只提供给活动的观察者。一个例外的行为是,当观察者从非活动状态变为活动状态时,也会收到更新。此外,如果观察者第二次从非活动状态变为活动状态,那么它只会接收到自上次激活后值发生变化的更新。
下面的代码展示了如果启动观察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);
    }
}

observe()方法被调用后,nameObserver传递了进去,onChanged()方法被立即调用,提供以mCurrentName存储的最新值,如果LiveData对象没有设置mCurrentName的值,那么onChanged()就不会被调用。

更新LiveData对象

LiveData对象没有提供公开的方法去更新存储的值。不过MutableLiveData这个类暴露了setValue(T)postValue(T)公共方法可以变更在LiveData对象中存储的值。通常在ViewModel中使用MutableLiveData,而ViewModel只向观察者公开不可变的LiveData对象。

当你设置了观察者关系之后,你就可以更新LiveData的值,就像下面事例一样,当点击按钮后会触发所有的观察者:

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

在这个示例中,调用setValue(T)方法会导致观察者将其onChanged()方法与John Doe的值联系起来。
注意:你必须调用setValue(T)方法去从主线程中去更新LiveData的值,如果代码的执行是在子线程,你必须使用postValue(T)方法去更新LiveData对象的值。

在Room中使用LiveData

Room是一个数据持久化库支持可观察查询并返回一个LiveData对象。
当数据库更新时,Room会生成所有必需的代码来更新LiveData对象。生成的代码是异步运行在后台线程上的。此模式有助于将数据显示在UI中与存储在数据库中的数据同步。您可以阅读更多关于Room和DAOs在Room persistent library guide

LiveData 拓展

如果观察者的生命周期处于STARTED状态或RESUMED状态,则LiveData认为观察者处于活动状态。下面的代码展示如何拓展LiveData:

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()
    这个方法当LiveData被一个观察者激活的时候调用。这意味着我们需要开启一个从这个设备位置更新的观察。
  • onInactive()
    这个方法当LiveData没有任何一个观察者的时候被调用。由于没有正在监听的观察者,也就没有理由一直连接到定位管理器服务。这很很重要的,因为保持着连接将占用大量的电池电源还没有任何好处。
  • setValue()
    调用这个方法会更新LiveData实例的值,并且迅速的通知观察者关于值的变化。
    我们可以像下面一样使用StockLiveData:
public class MyFragment extends Fragment {
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        LiveData<BigDecimal> myPriceListener = ...;
        myPriceListener.observe(this, price -> {
            // Update the UI.
        });
    }
}

observe()方法传递fragment,这是LifecycleOwner的一个实例,作为第一个参数。这样做意味着该观察者绑定到与所有者关联的生命周期对象,意义如下:

  1. 如果生命周期对象不是处于活动状态,那么即使值发生变化,观察者也不会被调用。
  2. 在生命周期对象被销毁后,观察者将被自动删除。

LiveData对象是生命周期的,这意味着您可以在多个活动、片段和服务之间共享它们。为了保持示例的简单性,可以将LiveData类作为一个单例来实现:

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

你可以在Fragment中使用它:

public class MyFragment extends Fragment {
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        StockLiveData.get(getActivity()).observe(this, price -> {
            // Update the UI.
        });
    }
}

可能会有多个fragmentactivity来观察我们的MyPriceListener实例,LiveData可以完美的管理了它们,只有当他们之中有任何一个是可见的时候,就会将其连接到系统服务。

Transformations of LiveData

某些时候你或许会想在派发给观察员之前改变LiveData的值或者你需要返回一个不同的基于另外一个值的LiveData的实例。
Lifecycle包提供了一个Transformations类包含了一些可用的方法来完成这些操作。

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

使用这些转换允许在链中传递观察者生命周期信息,这样,除非观察者观察返回LiveData,否则这些转换不会被计算。这种延迟计算的性质允许隐式地传递与生命周期相关的行为,而不需要添加显式的调用或依赖项。
当你认为你需要有一个生命周期的对象在ViewModel中时,Transformations可能是解决方案。
下面的例子,我们假设我们有一个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);
    }
}

如果是这样来实现,UI上将每次调用getPostalCode()方法的时候需要注销上一个LiveData并且重新注册一个新的实例,此外,如果UI界面被重新创建,将会触发另外的repository.getPostCode()方法而不是以前的调用的那个对象结果。
你可以使用让地址输入作为邮政编码信息的转换来替代这种方法的实现。

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作为转换,当addressInput的值被改变的时候,如果它的观察这是可用状态则repository.getPostCode()将会被调用,如果调用时没有可用的观察者,则不进行计算,直到新的观察者被添加。
这种机制允许在应用中创建LiveData对象的级别放低,可以被延迟计算,ViewModel可以很容易的获取他们并在其实定义转换规则。

  • Creating new transformations

在你的应用中或许会用很多不同的转变特性,但是他们不是被默认提供的。你可以使用MediatorLiveData类来实现你自己的转变,这是专门为了正确地监听其他Livedata实例和过程事件发出的。MediatorLiveData会将它的活动/不活动状态正确地传播到源LiveData中。您可以查看Transformations 类的实现以获得详细信息。

欢迎共同探讨更多安卓,java,c/c++相关技术QQ群:392154157

相关文章

网友评论

      本文标题:Android 结构组件之LiveData

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