关于MVVM的一些秘密

前言

MVVM作为一种架构模式,在Android中的主要落地实践脱离不开两个核心类LiveData和ViewModel。阅读之前需要你具备使用LiveData和ViewModel的基本使用。

有的放矢,带着目的的看这篇文或许你会更有收获。这篇文能帮你解除这些疑惑

  • ViewModel怎么实现多个Fragment之间数据共享?
  • Activity横竖屏切换时,ViewModle是怎么死里逃生的?
  • ViewModleScope是怎么感知组件生命周期而自杀(cancel)?
  • LiveData如何防止内存泄漏风险?
  • LiveData的观察者活跃时才响应是怎么回事?
  • 子线程中连续多次向LiveData发送值,observer能接受到所有的值吗?

1)ViewModel

ViewModel的职责,在以注重生命周期的方式存储和管理界面相关的数据

1.1) ViewModel 特性

1.1.1)注重生命周期

  • ViewModel注重组件生命周期主要体现在,当横竖屏转换等配置发生变化时导致Activity重建时,ViewModel 不会被销毁重建。换句话说,ViewModel的生命周期要比它所服务的组件(Activity/Fragment)的生命周期本身要长。

1.1.2) ViewModel相关类

a) ViewModelStoreOwner
public interface ViewModelStoreOwner {
    ViewModelStore getViewModelStore();
}
  • 提供并管理ViewModelStore,在当因配置改变发生重建时,会保存重建前的ViewModelStore。
  • 在Destroy的时候调用ViewModelStore的clear()方法
b) ViewModelStore
//有删减,伪代码
public class ViewModelStore {
    private final HashMap<String, ViewModel> mMap = new HashMap<>();
    final void put(String key, ViewModel viewModel) {
        ViewModel oldViewModel = mMap.put(key, viewModel);
        if (oldViewModel != null) {
            oldViewModel.onCleared();
        }
    }
    final ViewModel get(String key) {
        return mMap.get(key);
    }
    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.clear();
        }
        mMap.clear();
    }
}
  • ViewModelStore里有一个Map,用以存储ViewModel;
  • 对外提供获取、存储和清空 ViewModel 的方法。
  • 当clear的时候会调用 ViewModel 的 clear()方法。
c) ViewModel
public abstract class ViewModel {
    private final Map<String, Object> mBagOfTags = new HashMap<>();
    final void clear() {
        ..
      synchronized (mBagOfTags) {
           for (Object value : mBagOfTags.values()) {             
                if (obj instanceof Closeable) 
                     ((Closeable) obj).close();
            }
       }
        onCleared();
    }
    <T> T setTagIfAbsent(String key, T newValue) {... }

    <T> T getTag(String key) { ...}
    }
}
  • 关键点内部维护了一个Map类的mBagOfTags,用以追踪与该ViewModel对象相关的数据,如viewModelScope,SavedSateHandle数据
  • 另,被调用clear()方法时,对遍历出mBagOfTags中 Closeable 接口的value,执行其close方法。同时会调用自身的onClear()方法

1.2)ViewModel 的构建

通常我们使用

//伪代码
public <T extends ViewModel> T get(Class<T> class){
        ViewModel viewModle =  viewModelStrore.get(key)
        if(viewModel == null){
                viewModle  = factory.create(class)
                viewModelStore.put(key,viewModel);
     }
        return viewModel
}
flowViewModel = ViewModelProvider(this).get(FlowViewModel::class.*java*)
  • ViewModelProviderl两个成员属性,ViewModeStore和ViewModelProvider.Factory。
  • 默认生成的每个ViewModle分配一个key ("androidx.lifecycle.ViewModelProvider.DefaultKey + modelClass.*canonicalName"*)
  • 反射调用ViewModel的构造方法,ViewModelStoreOwner中获取出ViewModelStore起到 [缓存作用],这样就能够实现在一个ViewModelStoreOwner范围内能够实现ViewModel的共享。即,多个组件共同使用一个ViewModel。
  • 最终达到的效果是在一个ViewModelStoreOwner范围内只会创建出一个ViewModel。
  • 哪些 Factory:KeyFactory、OnRequeryFactoryAndroidViewModelFactory

1.3) ViewModel 与 Kotlin 的 Coroutine 结合

getLifecycle().addObserver(new LifecycleEventObserver() {
            @Override
            public void onStateChanged(@NonNull LifecycleOwner s,Lifecycle.Event e) {
                if (e == Lifecycle.Event.ON_DESTROY) {
                    if (!isChangingConfigurations()) {
                        getViewModelStore().clear();
                    }
                }
  • Fragment/Activity都实现了ViewModelStoreOwner接口。当组件生命周期状态切换到ON_DESTORY的的时候就会调用ViewModelStroe的clear()方法。这里有个isChangeingConfigurations()的判断。当因为配置变化导致的Activity重建时不会清除ViewModelStore中的数据,ViewModelStore会在新Activity创建时重新被使用。

  • 当调用其clear()方法的时候,会遍历出内部 Closeable 类型的值调用其close方法。其中viewModelScope作为ViewModel的一个扩展属性,如下

  • 获取 ViewModelScope 的时候,这个 CloseableCoroutineScope 对象会被添加到 ViewModle的成员属性mBagOfTags 这个Map里。同时CloseableCoroutineScope实现了 Closeble 接口。当ViewModle clear的时候被调用到 CoroutineCotext.cancel() 已达到取消协程Job的效果。

2) LiveData

  • ViewModel与View层建立起连接主要依靠LiveDataI(当然也可Flow)。能够达到View层持有ViewModel的引用,ViewModel不持有View的引用。这样的好处也显而易见,View层和ViewModel层解耦;提高ViewModel的可测性。
  • 实现方式使用带自动解注册的观察者模式,具体内部实现见下一章节(2.1),View层拿到ViewModel层的liveData然后注册观察者,当LiveData的数据发生变化时,自动更新UI。
  • 因为具备自动解注册功能,所以天然的能够防止内存泄漏(destory时清除掉原理见下文)

2.1 ) 自动解注册是怎么实现的?

在看自动解注册之前先看怎么注册的观察者的

 //伪代码有删减
 @MainThread
    public void observe( LifecycleOwner owner,  Observer<? super T> observer) {
        LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
        ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
        owner.getLifecycle().addObserver(wrapper);
    }

LiveData的Oberver()方法中主要做两件事,

  • 把observer存在自己的一个Map类型的mObservers属性里。
  • observer被包装成一个LifecycleBoundObserver,这个LifecycleBoundObserver继承了LifecycleEventObserver,因此这个observer也会被注册到livefeCycle上。

这样这个LifecycleBoundObserver对象就可以同时观察监听LiveData的事件和LifeCycle的事件。这样在对应的LifeCycle的事件里就可以做对应的处理。比如移除从注册,不活跃时不通知回调。

class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {
                ...
        @Override
        public void onStateChanged(@NonNull LifecycleOwner source,
                @NonNull Lifecycle.Event event) {    
            if (currentState == DESTROYED) {
                ObserverWrapper removed = mObservers.remove(observer); //从与LiveData解绑
                removed.detachObserver() //与生命周期组件接绑
            }
              ...
        }
        @Override
        void detachObserver() {
            mOwner.getLifecycle().removeObserver(this);
        }
    }

2.2)观察者活跃时才响应是怎么回事?

  • LiveData的监听者observer在内部被包装成了LifecycleBoundObserver如上一章节描述,这个observer所监听的生命周期组件如果处于ON_START 或者 ON_RESUME时该监听者才会被通知回调。

内部实现也比较简单,主要是依托observer监听了组件生命周期,自身进入决定自身是否是处于active状态。

  • 有一个问题,如果Observer从非活跃状态变为活跃状态一定会被回调吗?答案是不一定。

原因,liveData内部维护了一个版本,当恢复到活跃状态时,如果oberver中版本小于了liveData的版本,才会把最近的值分发非observer进而触发回调方法。liveData中版本会虽然每次新值的设置自动+1,代码如下。也就是说,当observer处于非活跃状态期间,如果liveData没有新值被设定进来,当observer回到活跃状态的时候不会被通知回调。

protected void setValue(T value) {
    assertMainThread("setValue");
    mVersion++;
    mData = value;
    dispatchingValue(null);
}
  • 又有另外一个问题,既然不会被重新回调,网上说的数据倒灌是怎么回事?

2.3) 子线程中连续多次向LiveData发送值,observer能接受到所有的值吗?

fun postValueIntWorkThread(){
    liveData.post(1)
    liveData.post(2)
}

LiveData有一个理念,只保留最新值。体现在两点

  • 短时间内多次从子线程向liveData post值的时候,observer不保证每个值都收到通知。具体实现如下代码:
  • 当observer处于非活跃状态期间,liveData中多次设置了值,当liveData恢复活跃状态后,只会接受到最新值。
private final Runnable mPostValueRunnable = new Runnable() {
        @Override
        public void run() {
            Object newValue;
            synchronized (mDataLock) {
                newValue = mPendingData;//注释3
                mPendingData = NOT_SET;//注释4
            }
            setValue((T) newValue);
        }
    };

protected void postValue(T value) {
        boolean postTask;
        synchronized (mDataLock) {
            postTask = mPendingData == NOT_SET;//注释5
            mPendingData = value;//注释2处,value赋值给mPendingData
        }
        if (!postTask) {//注释1:有值正在分发过程中
            return;
        }
        ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
    }

这段代码比较有意思

  • 1> 注释5处,进入被mDataLock这个monitor划定的临界区;计算出postTask的值,如果postTask为ture说明当前没有等待被分发的值,如果值为false说明有正在被分发的值。

  • 2> 注释2处,无论postTask的值为true或者false,都会把value的值赋值给mPendingData。

  • 3 >注释3处,如果有正在分发的值,就不执行接下来的操作了。此时注释2处已经把值赋值给了mPendingData,这个mPending还能分发吗?

  • 4>注释3处这段逻辑通过handler机制,最终在Main线程中执行,把mPendingData赋值给newValue,最终newValue被分发出去了

上面这个步骤下来,思考这样一种情况,连续post 1和2两个值,当post 2的时候,如果此时mPendingData值还为1,那么postTask就为false。但还是把值赋值给了mPendingData。接下来,注释1处正好符合判断条件,就不会向下执行。 当主线程执行到注释3处的runable时,会把2复制给newValue,最后把值分发出去了。

小结

本文主要围绕ViewModel和LiveData一些特性以及内部实现展开,阅读完之后,开头的那几个问题能找到答案了吗?

最后,我整理了一些学习资料,里面包括Java基础、Android进阶、架构设计、NDK、音视频开发、跨平台、底层源码等技术,还有2022年一线大厂最新面试题集锦,都分享给大家,助大家学习路上披荆斩棘~ 能力得到提升,思维得到开阔~ 有需要的可以点击下方公号链接免费获取。

公众号链接:https://mp.weixin.qq.com/s/ZDvWgZ_huo4natNk26ovlw

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

推荐阅读更多精彩内容