探究Google力推的JetPack库<二>---------用官方Data Binding来构建MVVM架构、集成Lifecycles完善MVP框架
在之前https://www.cnblogs.com/webor2006/p/12463543.html咱们对于MVVM架构进行了学习,其中提到对于MVVM其实Google已经有现成的框架可以用了---databinding(https://github.com/android/databinding-samples),所以接下来则来看一下它是如何来搭建MVVM框架的。
基础使用:
初步绑定:
先来建一个实体类:
那这个实体类怎么跟我们的UI绑定呢?之前我们在手写MVVM时是写了一个ViewBinder,回忆一下:
而这里使用Google的这种框架则完全不一样了,先来对布局文件进行修改,可以将我们的实体直接绑定到布局元素上,有点类似于JSP的el表达式的感觉,如下:
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <!--此处定义该布局要用到的数据的名字和类型--> <variable name="user" type="com.android.mvvm.User" /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{`姓名:`+user.name}" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{`密码:`+user.password}" /> </LinearLayout> </layout>
看到这写法木有,直接将我们的实体给声明进去了,不过此时布局文件是报错的:
这是因为需要在gradle启用一下dataBinding才行,如何启动?
此时布局文件就不报错了,接下来则回到Activity中需要进行绑定,如何做?
此时编译运行一下:
是不是很神奇?而且这种开发模式感觉不要太爽哦~~
BaseObservable:实时更新处理
对于MVVM有一个很重要的功能就是当数据发生变化或者UI发生变化时则会相互感知变化,那对于咱们这种框架该如何来实现呢?这里需要修改一下User实体类:
此时咱们在Activity中来模拟数据变化的情况:
运行:
加载网络图片:
如果要在布局中显示一张网络图片又该如何办呢?这里先来加入两个依赖包供待会会使用到的:
此时则需要在Model中增加一个头像属性:
然后绑定到布局上,此时跟之前的会有区别啦:
那。。貌似没有看到加载图片的代码呀,怎么来显示呢?这里加载方法还是需要定义在User类中,不过需要按规则来写:
此时咱们来使用一下看下效果:
在运行之前需要添加一下网络权限:
运行:
列表显示:
目前只显示了一条User信息,那如果要显示很多条到列表上又该如何写呢?咱们先在布局中增加一个ListView:
此时咱们则在Activity中需要添加数据源,这块跟平常我们写的没啥区别:
package com.android.mvvm; import android.os.Bundle; import android.widget.ListView; import androidx.appcompat.app.AppCompatActivity; import java.util.ArrayList; import java.util.List; public class MainActivity extends AppCompatActivity { ListView list; List<User> data = new ArrayList<>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); list = (ListView) findViewById(R.id.listView); data.add(new User("1", "1", "https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=36337156,310763697&fm=26&gp=0.jpg")); data.add(new User("2", "2", "https://dss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=158710161,3575221320&fm=26&gp=0.jpg")); data.add(new User("3", "3", "https://dss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=2749356163,3589966479&fm=26&gp=0.jpg")); data.add(new User("4", "4", "https://dss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1002883910,489905458&fm=26&gp=0.jpg")); list.setAdapter(new CommAdapter<User>(this, getLayoutInflater(), R.layout.item, BR.user, data)); } }
package com.android.mvvm; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import androidx.databinding.DataBindingUtil; import androidx.databinding.ViewDataBinding; import java.util.List; public class CommAdapter<T> extends BaseAdapter { private Context context; private LayoutInflater inflater; private int layoutId; private int variableId;//会自动生成 private List<T> list; public CommAdapter(Context context, LayoutInflater inflater, int layoutId, int variableId, List<T> list) { this.context = context; this.inflater = inflater; this.layoutId = layoutId; this.variableId = variableId; this.list = list; } @Override public int getCount() { return list.size(); } @Override public Object getItem(int position) { return list.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewDataBinding dataBinding; if (convertView == null) { dataBinding = DataBindingUtil.inflate(inflater, layoutId, parent, false); } else { //就重就之前的 dataBinding = DataBindingUtil.getBinding(convertView); } dataBinding.setVariable(variableId, list.get(position)); return dataBinding.getRoot().getRootView(); } }
其中对于列表中的显示的代码则改用DataBinding了,注意一下它的用法,接下来再准备一个条目的布局文件:
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> <variable name="user" type="com.android.mvvm.User" /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <ImageView android:layout_width="50dp" android:layout_height="50dp" app:header="@{user.header}" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" android:text="@{user.name}" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" android:text="@{user.password}" /> </LinearLayout> </layout>
接下来运行一下:
点击事件处理:
那如果要给View增加点击事件又是怎么处理的呢?如下:
而这个click方法居然是定义在Model当中的。。
感觉好不适应的样子,先学着吧,说不定哪天项目就用了这种方式了,运行看一下:
对于DataBinding的简单使用就先到这,重点是要看它的核心实现原理。
原理剖析:
先思考个问题:
在平常我们写布局时应该不可能会用它,那难道这个DataBinding就是牛逼能直接这样用么?很显然肯定是做了解析了的,下面咱们切到project视图瞅一下:
那这里也没有设置值呀,怎么就能跟我们的Model绑定上呢?其实这里还生成了另一个文件:
<?xml version="1.0" encoding="utf-8" standalone="yes"?> <Layout directory="layout" filePath="/Users/xiongwei/Documents/workspace/studio/JetpackStudy/mvvm/src/main/res/layout/item.xml" isBindingData="true" isMerge="false" layout="item" modulePackage="com.android.mvvm" rootNodeType="android.widget.LinearLayout"> <Variables name="user" declared="true" type="com.android.mvvm.User"> <location endLine="8" endOffset="42" startLine="6" startOffset="8" /> </Variables> <Targets> <Target tag="layout/item_0" view="LinearLayout"> <Expressions /> <location endLine="34" endOffset="18" startLine="11" startOffset="4" /> </Target> <Target tag="binding_1" view="ImageView"> <Expressions> <Expression attribute="android:onClick" text="user.click"> <Location endLine="17" endOffset="42" startLine="17" startOffset="12" /> <TwoWay>false</TwoWay> <ValueLocation endLine="17" endOffset="40" startLine="17" startOffset="31" /> </Expression> <Expression attribute="app:header" text="user.header"> <Location endLine="20" endOffset="38" startLine="20" startOffset="12" /> <TwoWay>false</TwoWay> <ValueLocation endLine="20" endOffset="36" startLine="20" startOffset="26" /> </Expression> </Expressions> <location endLine="20" endOffset="41" startLine="16" startOffset="8" /> </Target> <Target tag="binding_2" view="TextView"> <Expressions> <Expression attribute="android:text" text="user.name"> <Location endLine="26" endOffset="38" startLine="26" startOffset="12" /> <TwoWay>false</TwoWay> <ValueLocation endLine="26" endOffset="36" startLine="26" startOffset="28" /> </Expression> </Expressions> <location endLine="26" endOffset="41" startLine="22" startOffset="8" /> </Target> <Target tag="binding_3" view="TextView"> <Expressions> <Expression attribute="android:text" text="user.password"> <Location endLine="32" endOffset="42" startLine="32" startOffset="12" /> <TwoWay>false</TwoWay> <ValueLocation endLine="32" endOffset="40" startLine="32" startOffset="28" /> </Expression> </Expressions> <location endLine="32" endOffset="45" startLine="28" startOffset="8" /> </Target> </Targets> </Layout>
其中加粗的部分是不是能清楚的看到其model跟view的映射关系?那大胆的猜测一下DataBinding的原理应该就是根据上面这两个xml文件,然后再通过反射其Model里面对应的方法来达到View的显示的,下面继续探究,对于我们在Model中编写的这段代码其实也是框架生成的:
咱们找一找:
好,接下来则从调用的角度来分析一下源码的实现:
呃,这个类咋这么熟呢?这不是之前在找BR这个类时看到的么,由框架帮我们生成的,回忆一下:
好,回到主流程:
跟进去:
而ItemBindingImpl也是框架帮我们生成的类:
看一下它里面干了啥?会有个吃惊了发现。。
所以光从这点来看,MVVM目前还没有真正流行起来的原因我觉得性能是个主要因素,不过随着手机配置越来越高未来肯定是个趋势,因为开发效率实在是太高了,所以好好提前先掌握它还是很有必要的。好,先来分析一下它的构造方法:
再回到主流程继续:
这里先略过,先来回退上一个流程:
而它里面有一个静态代码块,瞅一下:
而它的回调方法的触发是我们调了这个方法:
所以先看一下这个回调做了啥?
此时就又调到框架生成的代码了:
@Override protected void executeBindings() { long dirtyFlags = 0; synchronized(this) { dirtyFlags = mDirtyFlags; mDirtyFlags = 0; } java.lang.String userName = null; com.android.mvvm.User user = mUser; java.lang.String userHeader = null; android.view.View.OnClickListener userClickAndroidViewViewOnClickListener = null; java.lang.String userPassword = null; if ((dirtyFlags & 0xfL) != 0) { if ((dirtyFlags & 0xbL) != 0) { if (user != null) { // read user.name userName = user.getName(); } } if ((dirtyFlags & 0x9L) != 0) { if (user != null) { // read user.header userHeader = user.getHeader(); // read user::click userClickAndroidViewViewOnClickListener = (((mUserClickAndroidViewViewOnClickListener == null) ? (mUserClickAndroidViewViewOnClickListener = new OnClickListenerImpl()) : mUserClickAndroidViewViewOnClickListener).setValue(user)); } } if ((dirtyFlags & 0xdL) != 0) { if (user != null) { // read user.password userPassword = user.getPassword(); } } } // batch finished if ((dirtyFlags & 0x9L) != 0) { // api target 1 this.mboundView1.setOnClickListener(userClickAndroidViewViewOnClickListener); com.android.mvvm.User.getImage(this.mboundView1, userHeader); } if ((dirtyFlags & 0xbL) != 0) { // api target 1 androidx.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView2, userName); } if ((dirtyFlags & 0xdL) != 0) { // api target 1 androidx.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView3, userPassword); } }
哦,发现我们之前的猜想错了,其实是直接调用User类中的方法,没有用到反射,因为能拿到User这个对像,另外为啥我们在加载图片时需要定义成static:
是因为框架就是静态来访问的:
至此关于DataBinding的原理就清楚了。
集成Lifecycles完善MVP框架
集成Lifecycles:
之前https://www.cnblogs.com/webor2006/p/12483158.html咱们已经学习过Jetpack中的Lifecycles的用法了,而之前https://www.cnblogs.com/webor2006/p/12445157.html我们从0搭建过一个MVP的框架,其中的P层是不是可以加入这个Lifecycles来达到生命周期的管理呢?这样就省得我们要在Activity或Fragment中的各个生命周期中来调用P层的相关代码来达到生命周期的管理了,所以回到当初我们写的那个MVP的工程中来处理一下,具体代码就不回忆了,这里首先先确保Activity是用的androidx的:
此时就可以开始绑定了:
此时则让P来实现一下接口:
package com.android.mvparcstudy.presenter; import androidx.lifecycle.Lifecycle; import androidx.lifecycle.LifecycleObserver; import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.OnLifecycleEvent; import com.android.mvparcstudy.view.IBaseView; import java.lang.ref.WeakReference; public class BasePresenter<T extends IBaseView> implements LifecycleObserver { //持有左边(VIEW) WeakReference<T> iMainView; public void attachView(T view) { this.iMainView = new WeakReference<>(view); } public void detachView() { if (iMainView != null) { iMainView.clear(); iMainView = null; } } @OnLifecycleEvent(Lifecycle.Event.ON_ANY) void onAny(LifecycleOwner owner) { } @OnLifecycleEvent(Lifecycle.Event.ON_CREATE) void onCreate(LifecycleOwner owner) { } @OnLifecycleEvent(Lifecycle.Event.ON_START) void onStart(LifecycleOwner owner) { } @OnLifecycleEvent(Lifecycle.Event.ON_STOP) void onStop(LifecycleOwner owner) { } @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) void onResume(LifecycleOwner owner) { } @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) void onPause(LifecycleOwner owner) { } @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) void onDestory(LifecycleOwner owner) { } }
接下来咱们在子类中打印一下日志,看好不好使:
运行看一下:
确实这个组件很好用,待之后工作中有需要到时则用一下它。
原理剖析:
那具体它是如何实现的呢?下面来剖析一下它的原理:
这里先大致想一下,既然是用到了注解:
只有两种可能性:一种是通过注解生成器来生成代码最终来调用这些生命周期相关的方法;另一种则是通过反射。好下面来看一下源码到底它是怎么实现的:
这里直接来看AppCompatActivity这个类:
再定位一下它的父类:
再往父类找,就会发现实现了一个跟生命周期的接口了:
咱们来定位一下它的onCreate():
又是Fragment,难道生命周期的管理又是靠的它,还记得之前学习Glide框架时对于图片生命周期的管理是不是也是用Fragment呀,好,接下来继续跟踪:
貌似这块都是来管理生命周期的呢?好,此时需要看一下dispatch:
回到主流程:
再往里跟:
其实需要往上回到它的构造方法中来找询:
至此,整个Lifecycle的实现原理就剖析清楚了~~