使用 LiveData 进行数据绑定

livedata-observe.png

 LiveData 是对可观察数据的封装。不像其他可观察对象(例如 ObservableField) , LiveData 可以感知到生命周期。这就意味着它可以关联到其他拥有生命周期的组件上,比如 Activity、Fragment 或者 Service。这种感知,可以确保 LinveData 的更新只发生在一个组件的活动状态上。如下图所示:

viewmodel_scope.png

 对于一个观察者类而言,所谓的激活状态就是 STARTED或者 RESUMED 状态。非激活状态并不更新。
 对于 Activity 来说,在 onStart 之后,到 onPause 之前,就是 STARTED;在 onResume 调用之后,就是 RESUMED 状态。
 通常,我们总是定义一个实现了 LifecyclerOwner 接口对象作为观察者。这种关系,会使得其在 DESTROY 状态时,自动移除对数的观察。

LiveData 的优势

使用 LiveData 有以下优势:

  • 确保 UI 和当前的数据状态匹配:LiveData 提供了一种观察者模式。当观察者的生命周期状态发生变化时,它会适时更新将数据更新到 UI 上。而并非是任何时候,都会对 UI 进行更新。
  • 避免内存泄漏:观察者是一个 Lifecycle 对象。当 LiveData 所关联的观察者被销毁时,LiveData 会自动清理自己。
  • 避免因 stop activity 造成的奔溃:当观察者对象处于非活动状态时,比如 activity 返回到回退栈中,此时,它将无法接收到 LiveData 的数据更新事件。
  • 不用手动处理生命周期:UI 组件观察相关的数据,但是并不会主动停止或者继续这种观察。当观察者生命周期发生变化时,LiveData 会自动管理自己。
  • 总是更新到最新的数据:当组件从 非活动 状态转换到 活动 状态时,他讲更新到最新的数据。
  • 正确的处理 configuration 的变化:当 activity 或者 fragment 由于 configuration(比如说屏幕旋转) 的变化而被创建时,它会自动接收到最新的可用数据。
  • 资源共享:我们可以使用单例模式继承一个 LiveData,当然将它绑定到一个系统服务中,这种这个 LiveData 就可以共享了。

LiveData 的使用

  1. 首先,创建一个持有数据的 LiveData 对象。这一步通常是在 ViewModel 中完成。
  2. 创建一个 Observer 对象,并定义其 onChange() 方法。该方法将控制在 LiveData 所持有的数据发生变化时,观察者将发生怎样的变化。我们通常创建在 UI controller 中创建 Observer。而这类 UI controller 诸如 activity 和 fragment。
  3. 通过 observe() 方法,将 Observer(观察者)和 LiveData(被观察者)绑定在一起。这样以来,当 LiveData 数据发生变化时,只要 Observer 处于 活动 状态,将自动通知 Observer 。

创建 LiveData 对象

 LiveData 可以包裹任何数据,包括集合类,比如 List。LiveData 通常存储在 ViewModel 中,通过 getter 方法提供给观察者。

public class UserViewModel extends ViewModel {

    MutableLiveData userName;

    UserViewModel(){
        userName = new MutableLiveData<>();
    }

    public LiveData getUserName(){
        return userName;
    }

    public void setUserName(String name){
        userName.setValue(name);
    }
}

 综上,我们看到 UI controller,比如 activity 或者 fragment 仅仅负责显示数据,而不再管理数据状态。如此一来,将大大避免了 UI controller 的臃肿。

订阅 LiveData 对象

 通常,组件的 onCreate() 方法,是个合适的地方以建立对 LiveData 的观察或者说是订阅,理由如下:

  • onCreate() 方法在创建的时候,只会调用一次。
  • 确保 UI controller 处于 活动 状态时,能够有数据显示。

 LiveData 只会在数据变化,同时观察者处于 活动 状态时,才会通知观察者更新。当然,第一次初始显示数据除外,数据被初始化,直接通知处于 活动状态的 UI controller 进行数据更新。

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mainViewModel = ViewModelProviders.of(this).get(MainViewModel.class);
    mainViewModel.getEncryptedFileNum().observe(this, num -> {
            encryptedFileNumText.setText(String.format("文件 %d 个", num));
    });
}

 当 observe() 方法调用后,onChange() 方法被立即调用,为 encyptedFileNumText 提供最新的值。随后,只有 mainViewModel 中的 encryptedFileNum 发生变化,且该 UI controller 处于 活动 状态,encyptedFileNumText 才会更新相应 UI。

更新 LiveData 对象

 LiveData 本身没有公开可用的方法用以更新数据。MultableLiveData 则暴露了 setValue(T) 和 postValue(T) 方法来更新 LiveData 中的数据。注意,setValue 方法用于在主线程中更新值,而 postValue 则用于在工作线程中更新值。

private MutableLiveData addressName ;
public void setAddressName(String name) {
        addressName.setValue(name);
}

one-way data binding VS two-way data binding

 在单向绑定中,我们通过改变 LiveData 中的值,来更新 UI 。通常,我们还需要当用户对 UI 进行了操作之后,所带了的变化能反馈到 LiveData 的值上,即自动更新 LiveData 中的值。这一点,在 LiveData 中很容易做到。
单向绑定:


双向绑定:


 注意,单向绑定和双向绑定在 XML 中的唯一区别,就是 android:checked="@={pickerBean.selected}" 中 @ 后面是否有等号。

使用自定义属性进行双向绑定

 上个代码块中,我们对 checked 属性使用了双向绑定。那么,如果是我们自定义的属性该如何处理?
 为了达到这个目的,需要使用 @InverseBindingAdapter 和 @InverseBindingMethod 注解。
 以为 MyView 绑定设置 时间 为例。首先,需要使用 @BindingAdapter

@BindingAdapter("time")
public static void setTime(MyView view, Time newValue) {
    // Important to break potential infinite loops.
    if (view.time != newValue) {
        view.time = newValue;
    }
}

 然后,使用 @InverseBindingAdapter 注解,告诉它当 MyView 的属性发生变化时,该调用哪个方法:

@InverseBindingAdapter("time")
public static Time getTime(MyView view) {
    return view.getTime();
}

 应当注意,当使用双向绑定时,不要发生的无限调用的陷阱。当用户改变了 View 的属性,@InverseBindingAdapter 被调用了。LiveData 中的值发生了变化,这将导致 @BindingAdapter 所注解的方法被调用。如此一来,可能会在 @InverseBindingAdapter@BindingAdapter 两个注解方法中无限循环下去。为了防止这种事情发生,可以参考上述 setTime 方法中的应用。

应用场景

 观察者模式的应用场景本身就很丰富。订阅-发布,通过消息或者说事件将组件之间,组件和数据之间关联起来,这种应用体验非常友好。业务逻辑将更加清楚;同时,将少大量的冗余代码,使开发者更加关注和处理业务逻辑。以下,记录一些实例,做一些展开说明。

在 Room 中使用

 Room 是 Google 提供的组件库之一,是对 SQLite 的封装。它对 LiveData 的支持,使得操作数据库的数据,可以直接反应到为用户提供的 UI 展示上。进一步说,它的查询方法可以返回一个 LiveData 对象,这个对象的泛型可以是基础类型的包装类,例如 Integer 、Boolean、String、Long 这些包装类,也可以是 List。

@Query(" SELECT  " +
        "              a.*    ," +
        "              b.transStatus ,       " +
        "              b.fileLength ,       " +
        "              b.progress ,       " +
        "              b.needDecrypted ,       " +
        "              b.id as transId, " +
        "              b.uuid as transUuid, " +
        "              b.localFilePath as transPath , " +
        "              MAX(b.date) as transDate " +
        "              FROM    FileShareEntity a  " +
        "              LEFT JOIN FileTransEntity b " +
        "              ON a.uuid = b.uuid  " +
        "              WHERE a.isRec == 1 AND a.gid=:gid" +
        "              group by a.uuid  order by a.date desc"
)
LiveData> getFileShareSendItems(String gid);

 通过查询,得到了一个 LiveData 对象,然后通过 ViewModel,将其和上层 UI 绑定在一起。

public class ShareSendModule extends AndroidViewModel {
...
LiveData> getFileShareSendItems(String gid) {
    return shareDao.getFileShareSendItems(gid);
}
...
}

 最后,在 Fragment 中完成绑定(订阅):

module.getFileShareSendItems(gid).observe(this, adapter::setData);

 此时,当 List 数据发生任何变化,如果 Fragment 处于活动状态,就会被更新。注意到这里的 setData 方法,将更改 adapter 中的数据,结合 DiffUtil.Callback ,RecyclerView 的使用将变得非常非常清爽。

在 RecyclerView 中使用

 其实上面已经提到了 Room 和 RecyclerView 的结合。我们可以做进一步的绑定。将 List 中的数据和每个 Item 绑定在一起。直接操作数据变化,不在单独处理 UI 展示。

public AddressAdapter(AppCompatActivity activity) {
    addressModel = new AddressModel();
    addressModel.getAddresses().observe(activity, addressEntities -> {
        if (mItems.size() != 0) {
            AddressDiffCallback postDiffCallback = new AddressDiffCallback(mItems, addressEntities);
            DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(postDiffCallback, true);
            transformEntities2Beans(addressEntities, mItems);
            diffResult.dispatchUpdatesTo(this);
            //  notifyDataSetChanged();
        } else {
            transformEntities2Beans(addressEntities, mItems);
            notifyDataSetChanged();
        }
    });

    setHasStableIds(true); // this is required for swiping feature.
    mItems = new ArrayList<>();
}

@NonNull
@Override
public AddressViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    if (viewType == ITEM_TYPE_NORMAL) {
        ActivityAddressItemBinding binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.activity_address_item, parent, false);
        binding.setLifecycleOwner((LifecycleOwner) parent.getContext());
        return new AddressViewHolder(binding);
    } else {
        View header = LayoutInflater.from(parent.getContext()).inflate(R.layout.activity_address_item_add, null);
        return new AddressViewHolder(header);
    }
}

@Override
public void onBindViewHolder(@NonNull AddressViewHolder holder, int position) {
    AddressBean item = mItems.get(position);
    holder.bind(item);
}

@Override
public int getItemCount() {
    return mItems.size();
}

class AddressViewHolder extends RecyclerView.ViewHolder {

    ActivityAddressItemBinding binding;

    private boolean isHeader;

    AddressViewHolder(View root) {
        super(root);
        this.root = root;
        isHeader = true;
    }

    AddressViewHolder(ActivityAddressItemBinding binding) {
        super(binding.getRoot());
        this.binding = binding;
        isHeader = false;
    }

    void bind(AddressBean bean) {
        if (isHeader) {
            bindHeader();
        } else {
            bindItem(bean);
        }
    }

    void bindHeader() {
    .....
    }

    void bindItem(AddressBean bean) {
        binding.setAddressBean(bean);
        ......
    }
}

一些小技巧

 在使用过程中,还有一些小技巧,记录在此。

和方法的绑定
public class AddressBean extends ViewModel {
...
 public void onDelete(View view){
 ...
 }
...
}
// 在 xml 中

View 可见性绑定

    
    


 

总结

 绑定的基础,是观察者模式。只不过,这种观察者模式的细节实现,由这类 LiveData 和 ViewModel 帮助我们实现了。

参考

LiveData Overview
LiveData beyond the ViewModel — Reactive patterns using Transformations and MediatorLiveData
Android Architecture Patterns Part 3:
Model-View-ViewModel
AndroidViewModel vs ViewModel
MediatorLiveData
Advanced Data Binding: Binding to LiveData (One- and Two-Way Binding)
Two-way data binding

你可能感兴趣的:(使用 LiveData 进行数据绑定)