Android Jetpack MVVM数据绑定及UI更新

一.Jetpack简介

      2018年谷歌I/O大会发布了一系列辅助Android开发者的实用工具,合称Jetpack,以帮助开发者构建出色的 Android 应用。
      按照Google官方的说法,“Jetpack是一套库、工具和指南,可以帮助开发者更轻松地编写应用程序。Jetpack中的组件可以帮助开发者遵循最佳做法、摆脱编写样板代码的工作并简化复杂的任务,以便他们能将精力集中放在业务所需的代码上”。

Jetpack与AndroidX

      在2018年的Google I/O大会上,Google宣布用AndroidX代替Android Support Library,AndroidSupport Library在版本28之后就不再更新了,未来的更新会在AndroidX中进行。不仅如此,AAC(Android Architecture Component)中的组件也被并入AndroidX。所以,当使用Jetpack的组件时,经常会看到以“androidx”开头的包名,Android Support Library与AAC中的各种组件已经迁移到了AndroidX中。
      为什么Jetpack组件需要以兼容包的形式存在,而不是成为Framework的一部分呢?很简单,这是为了提供向后兼容,使Jetpack组件能够应对更加频繁的更新。除了Android Support Library和AAC,其他一些需要频繁更新和迭代的特性也被并入了AndroidX。
      我们平常使用的DataBinding,LifeCycle,ViewModel,LiveData等都是Jetpack旗下的,之前也做过相应的分析:
      Android Jetpack ViewModel详解
      Android Jetpack Lifecycle详解
      Android Jetpack LiveData原理分析
      Android JetPack DataBinding分析
      平常在开发的时候,都是会将其一起使用,比如:MVVM架构,使用DataBinding+LiveData+LifeCycle+ViewModel来进行实现,各司其职,将MVVM架构脱颖而出,本文通过一个MVVM实例来分析一下数据绑定及UI更新的实现原理。

二.MVVM实例

      结合MVVM实例来分析一下ViewModel在MVVM中起了什么作用,LiveData是如何与UI进行绑定及UI如何更新。

a.View
public class ViewModelLiveDataFragment extends BaseFragment {

    private MainViewModel mMainViewModel;

    @Override
    public int getLayoutId() {
        return R.layout.livedata_layout;
    }

    @Override
    public void initData(View view) {

    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        LivedataLayoutBinding binding = DataBindingUtil.inflate(inflater, R.layout.livedata_layout, container, false);
        View view = binding.getRoot();
        mMainViewModel = new ViewModelProvider(this.getViewModelStore(), new ViewModelProvider.NewInstanceFactory()).get(MainViewModel.class);
        binding.setViewModel(mMainViewModel);
        //通过以下逻辑来保证MutableLiveData变化时来更新UI
        //该方法中最终会调用到observe()方法
        binding.setLifecycleOwner(this);
        return view;
    }
}

      以上可以看到,在onCreateView()时,去new一个ViewModelProvider(),然后通过来get()传入类来获取MainViewModel,用来存储数据。

b.ViewModel
public class MainViewModel extends ViewModel {

    private ImageDepository mImageDepository;

    public MainViewModel() {
        mImageDepository = new ImageDepository();
        step.setValue(1);
        getImage(step.getValue());
    }

    public MutableLiveData<Integer> step = new MutableLiveData<>();
    public MutableLiveData<String> imageUrl = new MutableLiveData<>();
    public MutableLiveData<String> imageDescription = new MutableLiveData<>();

    public void onClick(View view) {
        if (view.getId() == R.id.up) {
            step.setValue(step.getValue() - 1);
        } else if (view.getId() == R.id.down) {
            step.setValue(step.getValue() + 1);
        }
        getImage(step.getValue());
    }

    private void getImage(int step) {
        Observable<ImageBean> observable = mImageDepository.getImage("js", step, 1);
        observable.subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<ImageBean>() {
                    @Override
                    public void onSubscribe(Disposable d) {
                    }

                    @Override
                    public void onNext(ImageBean imageBean) {
                        List<ImageBean.ImagesBean> imagesBeans = imageBean.getImages();
                        ImageBean.ImagesBean imagesBean = imagesBeans.get(0);
                        String url = ImageBean.ImagesBean.BASE_URL + imagesBean.getUrl();
                        String des = imagesBean.getCopyright();
                        imageUrl.setValue(url);
                        imageDescription.setValue(des);
                    }

                    @Override
                    public void onError(Throwable e) {

                    }

                    @Override
                    public void onComplete() {
                    }
        });
    }
}

      以上可以看到,MainViewModel内部定义了许多LiveData变量,如果MainViewModel是唯一的,那么LiveData也就被存储下来,当UI从后台处于前台时,可以将最新值同步更新到UI。

c.M
public class ImageDepository {

    private RetrofitApi mRetrofitApi;

    public ImageDepository() {
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("https://cn.bing.com/")
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build();
        mRetrofitApi = retrofit.create(RetrofitApi.class);
    }

    public Observable<ImageBean> getImage(String format, int idx, int n) {
        return mRetrofitApi.getImage(format, idx, n);
    }
}

      M就是数据模块,用来获取数据,比如:用Retrofit请求网络数据等。
      MVVM中,跟UI绑定的是LiveData,接下来看一下LiveData是如何与UI进行绑定的。

三.LiveData与UI进行绑定

      在工程的generated目录下会生成xxBindingImpl.java文件,该文件实现xxViewDataBinding类,xxViewDataBinding类实现ViewDataBinding类。

public class LivedataLayoutBindingImpl extends LivedataLayoutBinding  {
    private LivedataLayoutBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
        //调用父类的构造方法
        super(bindingComponent, root, 3
            , (android.widget.TextView) bindings[1]
            , (android.widget.TextView) bindings[4]
            , (android.widget.TextView) bindings[3]
            );
        this.description.setTag(null);
        this.down.setTag(null);
        this.mboundView0 = (android.widget.RelativeLayout) bindings[0];
        this.mboundView0.setTag(null);
        this.mboundView2 = (android.widget.ImageView) bindings[2];
        this.mboundView2.setTag(null);
        this.up.setTag(null);
        setRootTag(root);
        // listeners
        invalidateAll();
    }

    @Override
    public void invalidateAll() {
        synchronized(this) {
                mDirtyFlags = 0x10L;
        }
        requestRebind();
    }

    @Override
    protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {
        ......
    }

    @Override
    protected void executeBindings() {
       ........
       ........
        // batch finished
        if ((dirtyFlags & 0x1cL) != 0) {
            // api target 1
            //更新UI
            androidx.databinding.adapters.TextViewBindingAdapter.setText(this.description, viewModelImageDescriptionGetValue);
        }
        ......
        ......
}

      MutableLiveData是如何和UI进行绑定的?
      1.xxBindingImpl类的构造方法,会最终调用ViewDataBinding类的构造方法,会根据MutableLiveData的数量来初始化创建对应数量的WeakListener,后续LiveData value变化会通知UI更新。
      2.在xxBindingImpl构造方法中,执行了以下调用逻辑:
      ------>invalidateAll()
            ------>requestRebind()
                  ------>.......
                        ----->executePendingBindings()
                              ------>executeBindingsInternal()
                                    ------>executeBindings(),
在executeBindings()里面来注册及更新UI。
      3.在executeBindings()里面有一个updateLiveDataRegistration(0, viewModelImageUrl),此方法作为注册回调的入口,将对应的LiveData进行observe(),调用关系为:
      ------>updateRegistration(0, viewModelImageUrl, CREATE_LIVE_DATA_LISTENER)
            ------>registerTo()
                  ------>listener.setTarget(viewModelImageUrl);
      在registerTo()方法里面,获取到对应Observable[LiveData]的weakListener,然后将其赋值给步骤1创建的WeakListener数组里对应的值。

/**
 * Method object extracted out to attach a listener to a bound LiveData object.
 */
private static final CreateWeakListener CREATE_LIVE_DATA_LISTENER = new CreateWeakListener() {
    @Override
    public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) {
        return new LiveDataListener(viewDataBinding, localFieldId).getListener();
    }
};

private static class LiveDataListener implements Observer,
            ObservableReference<LiveData<?>> {
        final WeakListener<LiveData<?>> mListener;
        LifecycleOwner mLifecycleOwner;

        public LiveDataListener(ViewDataBinding binder, int localFieldId) {
            //创建WeakListener时把自己作为Observable传进去,后续执行在WeakListener中执行setLifecycleOwner会用到
            mListener = new WeakListener(binder, localFieldId, this);
        }

        @Override
        public void setLifecycleOwner(LifecycleOwner lifecycleOwner) {
            LifecycleOwner owner = (LifecycleOwner) lifecycleOwner;
            LiveData<?> liveData = mListener.getTarget();
            if (liveData != null) {
                if (mLifecycleOwner != null) {
                    liveData.removeObserver(this);
                }
                if (lifecycleOwner != null) {
                    //执行observe,主要做了两项操作:
                    //1.自身监听liveData的变化,有变化执行onChanged();
                    //2.liveData监听LifeCyclerOwner生命周期状态的变化
                    liveData.observe(owner, this);
                }
            }
            mLifecycleOwner = owner;
        }

        @Override
        public WeakListener<LiveData<?>> getListener() {
            return mListener;
        }

        @Override
        public void addListener(LiveData<?> target) {
            if (mLifecycleOwner != null) {
                target.observe(mLifecycleOwner, this);
            }
        }

        @Override
        public void removeListener(LiveData<?> target) {
            target.removeObserver(this);
        }

       //mutableLiveData变化时会回调该方法
        @Override
        public void onChanged(@Nullable Object o) {
            ViewDataBinding binder = mListener.getBinder();
            if (binder != null) {
                binder.handleFieldChange(mListener.mLocalFieldId, mListener.getTarget(), 0);
            }
        }
    }

      4.后续会执行binding.setLifecycleOwner(this),在该方法中将在步骤1中创建的WeakListener分别执行setLifecycleOwner(this),会执行LiveDataListener.setsetLifecycleOwner(this)------>liveData.observe(owner, this),此方法主要做了两个操作:
      ①.MutableLiveData建立了对LifecycleOwner(activity/Fragment)生命周期的变化监听;
      ②.建立了对MutableLiveData的值变化监听;
      总的执行逻辑图如下:


image.png

      以上已经分析了LiveData是如何与UI进行绑定的,接下来看一下当LiveData发生变化后,是如何通知UI进行更新的。

四.LiveData如何通知UI更新

      MutableLiveData变化后如何进行UI更新?

    protected void setValue(T value) {
        assertMainThread("setValue");
        mVersion++;
        mData = value;
        dispatchingValue(null);
    }

      通过setValue()来对数据进行更新,先确保是主线程;然后将mVersion++,此处操作是为了避免重复发送更新;其次将value值进行存储在mData中,后续owner的生命周期变为active后,会利用到mData进行更新;最后执行dispatchingValue()进行实时更新。接下来看一下dispatchingValue()的实现:

void dispatchingValue(@Nullable ObserverWrapper initiator) {
        if (mDispatchingValue) {
            mDispatchInvalidated = true;
            return;
        }
        mDispatchingValue = true;
        do {
            mDispatchInvalidated = false;
            if (initiator != null) {
                considerNotify(initiator);
                initiator = null;
            } else {
                for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
                        mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
                    considerNotify(iterator.next().getValue());
                    if (mDispatchInvalidated) {
                        break;
                    }
                }
            }
        } while (mDispatchInvalidated);
        mDispatchingValue = false;
    }

       当initiator不为null时,是单独更新的,主要适应于owner生命周期状态变化时进行通知,由于传入的initiator为null,那么会执行else逻辑,遍历mObservers来进行通知observer进行更新UI,即调用到considerNotify(),看一下considerNotify()的实现:

    private void considerNotify(ObserverWrapper observer) {
        //如果observer不处于active,则不去更新,从而不会造成内存泄露
        if (!observer.mActive) {
            return;
        }
     
        if (!observer.shouldBeActive()) {
            observer.activeStateChanged(false);
            return;
        }
        if (observer.mLastVersion >= mVersion) {
            return;
        }
        observer.mLastVersion = mVersion;
        //回调observer的onChanged()更新UI
        observer.mObserver.onChanged((T) mData);
    }

      首先判断observer是否为active,否则直接返回;然后判断mLastVersion >= mVersion,避免重复更新,每次更新后都进行赋值;最后执行observer的onChanged()来进行回调更新。
简单总结一下:
      1.MutableLiveData进行setValue()后,执行的调用关系如下:
      ------>dispatchingValue(null)
            ----->considerNotify()
                  ------>observerWrapper.mObserver.onChanged((T) mData);
                        ------>LiveDataListener.onChanged()
      2.根据上述绑定UI的第4步observe()时传入的observer,会回调LiveDataListener里面的onChanged()方法。
      3.onChanged()方法中会调用handleFieldChange(),会先调用xxBindingImpl中的onFieldChange()方法,然后调用requestRebind()------>.....------>调用xxBindingImpl中的executeBindings()来更新UI,流程图如下:

image.png

      以上就是针对一个MVVM实例,对内部的数据绑定及UI更新进行了详细的分析,了解一个架构的实现原理,只能通过看源码!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,417评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,921评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,850评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,945评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,069评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,188评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,239评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,994评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,409评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,735评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,898评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,578评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,205评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,916评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,156评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,722评论 2 363
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,781评论 2 351

推荐阅读更多精彩内容