Android架构组件使用和原理分析:ViewModel+LiveData

2,121 阅读11分钟

一、应用架构

1.1、设计目的

使用或者设计某个应用架构的目的是什么?

简单的概括来说,是为了满足开闭原则,在不修改原有代码的情况下给程序扩展功能,而不是直接修改原有代码。

最终的目的是为了 提升开发测试效率,降低程序维护成本(降本增效)

1.2、架构选择

基于上面的认知,我们选择使用一些其他的设计来实现开闭原则的目的。包括但不限于:单一职责,抽象接口,继承多态、解耦等等方式。

Android官方推荐的MVVM应用架构(现在已经不是官方最推荐架构了),主要是通过拆分View层(Activity/Fragmet)职责,简化View层的逻辑,分离View层和Model层之间的耦合的方式来实现开闭原则的目的

MVVM架构模型如下(每个组件仅依赖下一级组件):

image.png

这里需要强调的是:

  • 尽管该架构本身是实现程序开闭原则的比较好实践之一,但是并不是所有的应用界面代码设计必须使用该架构。

  • 应用架构的选择应该根据实际情况选择,没有最好的架构,仅仅只有当前场景的最优选择


下面主要分析下该架构下最重要的两个架构组件ViewModel和LiveData的使用和原理

二、组件使用

ViewModel教程

LiveData教程

简单使用示例(详细教程可查看如上官方教程):

ViewModel和LiveData一般组合使用(也可以单独使用,根据实际场景选择):

界面由MainActivity和对应的布局activity_main.xml组成。

首页需要请求页面数据,我们使用HomeViewModel请求和存储这些数据,定义了以下文件:

MainActivity

activity_main.xml

HomeViewModel

以下代码段显示了这些文件的起始内容(为简单起见,省略了布局文件)

MainActivity:

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // Use the 'by viewModels()' Kotlin property delegate from the activity-ktx artifact
        // or use ' ViewModelProvider(this).get(HomeViewModel::class.java)'
        val viewModel: HomeViewModel by viewModels()
        viewModel.pageData.observe(this, { data ->
            Log.d("mumu","data: $data")


        })
    }

HomeViewModel:

class HomeViewModel : ViewModel() {


    val pageData: MutableLiveData<String> by lazy {
        MutableLiveData<String>()
    }


    init {
        // 请求返回字符串并保存
        requestNetData(object : IHttpCallBack {
            override fun onError(error: IHttpCallBack.HttpError?) {
                pageData.value = null
            }


            override fun onEnd(data: String) {
                pageData.value = data
            }

        })
    

说明:

  • 在ViewModel层调用网络数据层的方法请求数据并保存到ViewModel的LiveData存储器中。
  • 在View层(Activity)中注册观察者对象,观察LiveData的数据更新。无需在onStop停止观察,因为LiveData框架具备生命周期感知,检测观察者onStop了不会通知观察者数据更新。

三、组件原理

3.1、LiveData

LiveData 是一种可观察的数据存储器。应用中的其他组件可以使用此存储器监控对象的更改,而无需在它们之间创建明确且严格的依赖路径。LiveData 组件还能感知应用组件(如 Activity、Fragment 和 Service)的生命周期状态,能够自动清理观察者以防止对象泄漏和过多的内存消耗。

3.1.1、注册观察者

@MainThread
    public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
        assertMainThread("observe");
        //应用组件已经销毁,忽略 
        if (owner.getLifecycle().getCurrentState() == DESTROYED) {
            // ignore
            return;
        }
        //将观察者对象封装成一个LifecycleBoundObserver对象(详见3.1.2)
        LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
        ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
        //不为空说明已经在SafeIterableMap集合中了,同一个应用组件(Activity/Fragment)不能重复添加相同的观察者
        if (existing != null && !existing.isAttachedTo(owner)) {
            throw new IllegalArgumentException("Cannot add the same observer"
                    + " with different lifecycles");
        }
        if (existing != null) {
            return;
        }
        //LifecycleBoundObserver实现LifecycleEventObserver接口,绑定Lifecycle的生命周期
        owner.getLifecycle().addObserver(wrapper);
    }

说明: 该步骤主要讲观察者对象observer封装成一个LifecycleBoundObserver对象,然后保存到集合中,并且通过LifeCycle绑定应用组件的生命周期

3.1.2、LifecycleBoundObserver观察者包装类

//内部类
    class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {
        @NonNull
        final LifecycleOwner mOwner;


        LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<? super T> observer) {
            super(observer);
            mOwner = owner;
        }


        //判断观察者是否激活,应用组件生命周期处在onStart和onResume状态该方法才会返回true
        @Override
        boolean shouldBeActive() {
            return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
        }


        //应用组件生命周期发生变化时回调该方法(Lifecycle组件的功能)
        @Override
        public void onStateChanged(@NonNull LifecycleOwner source,
                @NonNull Lifecycle.Event event) {
            Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState();
            //判断应用组件已经destroy了则自动解注册
            if (currentState == DESTROYED) {
                removeObserver(mObserver);
                return;
            }
            Lifecycle.State prevState = null;
            while (prevState != currentState) {
                prevState = currentState;
                //状态方式改变,调用该方法改变LiveData状态和分发数据(详见3.1.3)
                activeStateChanged(shouldBeActive());
                currentState = mOwner.getLifecycle().getCurrentState();
            }
        }


        @Override
        boolean isAttachedTo(LifecycleOwner owner) {
            return mOwner == owner;
        }


        @Override
        void detachObserver() {
            mOwner.getLifecycle().removeObserver(this);
        }
    }


//LiveData的解注册方法(LiveData的方法)
@MainThread
public void removeObserver(@NonNull final Observer<? super T> observer) {
    assertMainThread("removeObserver");
    //从集合中移除观察者包装对象
    ObserverWrapper removed = mObservers.remove(observer);
    //为空说明已经移除了
    if (removed == null) {
        return;
    }
    //Lifecycle解注册
    removed.detachObserver();
    //调用activeStateChanged方法同步状态改变
    removed.activeStateChanged(false)

3.1.3、activeStateChanged状态改变同步方法

void activeStateChanged(boolean newActive) {
            if (newActive == mActive) {
                return;
            }
            // immediately set active state, so we'd never dispatch anything to inactive
            // owner
            mActive = newActive;
  //该方法是通过加一减一来判断当前LiveData对象内是否有处在集合状态的观察者
  //如果从无到有则调用onActive方法,从有到无则调用onInactive方法
  //两个方法均为LiveData的空实现方法,我们可以做一些初始化或者数据重置的操作
            changeActiveCounter(mActive ? 1 : -1);
            if (mActive) {
 //真正开发分发数据的方法(只有观察者处于激活状态才会分发数据,详见3.1.4)
                dispatchingValue(this);
            }
        }

3.1.4、dispatchingValue分发数据方法

void dispatchingValue(@Nullable ObserverWrapper initiator) {
        if (mDispatchingValue) {
            //mDispatchInvalidated标记数据是否失效,如果失效重新分发最新数据
            mDispatchInvalidated = true;
            return;
        }
        mDispatchingValue = true;
        do {
            mDispatchInvalidated = false;
            if (initiator != null) {
            //状态改变同步数据initiator不为空(首次注册情况下如果需要同步也是走到这里)
                considerNotify(initiator);
                initiator = null;
            } else {
                //更新数据情况同步所有观察者数据改变
                for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
                        mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
               //通知观察者数据改变分发(详见3.1.5)
                    considerNotify(iterator.next().getValue());
                    if (mDispatchInvalidated) {
                        break;
                    }
                }
            }
        } while (mDispatchInvalidated);
        mDispatchingValue = false;
    }

3.1.5、considerNotify通知观察者数据改变方法

private void considerNotify(ObserverWrapper observer) {
//观察者没有激活不处理
        if (!observer.mActive) {
            return;
        }
        // Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet.
        //
        // we still first check observer.active to keep it as the entrance for events. So even if
        // the observer moved to an active state, if we've not received that event, we better not
        // notify for a more predictable notification order.
        //再判断一次观察者状态,防止状态没有及时同步
        if (!observer.shouldBeActive()) {
            //如果状态方式改变就会调用上面的状态改变同步分发
            observer.activeStateChanged(false);
            return;
        }
      //判断数据是否已经通知了该观察者对象,防止重复更新(LiveData数据每更新一次,mVersion就会加一)
        if (observer.mLastVersion >= mVersion) {
            return;
        }
        observer.mLastVersion = mVersion;
        //没有通知过则调用观察者的onChanged方法通知数据更新
        observer.mObserver.onChanged((T) mData);
    }

说明:

走到这里,LiveData的一次完整数据观察和数据更新流程就结束了。

通过上述流程可知,LiveData会通过Lifecycle自动观测应用组件生命周期变化,组件销毁了自动解注册;组件激活了将观察者变为激活状态并及时同步数据给观察者。观察者只需要在数据更新方法中做相应的核心逻辑处理,而无需关心数据丢失或者数据重复回调,也无需担心内存泄露,LiveData会自动解注册观察者。

最后还有就是LiveData的数据更新方法(setValue和postValue方法),详见3.1.6说明

3.1.6、更新数据方法

setValue或者postValue(区别是postValue支持异步调用,最终还是会切换到主线程调用setValue方法)

@MainThread
    protected void setValue(T value) {
        assertMainThread("setValue");
//数据改变,version加一
        mVersion++;
        mData = value;
//调用3.1.4的分发数据方法,不过这里的观察者包装类对象为null因此会循环集合中所有的观察者对象通知数据更新
        dispatchingValue(null);
    }

3.2、ViewModel

ViewModel 对象为特定的界面组件(如 Fragment 或 Activity)提供数据,并包含数据处理业务逻辑,以与模型进行通信。例如,ViewModel 可以调用其他组件来加载数据,还可以转发用户请求来修改数据。ViewModel无法感知界面组件,因此不受配置更改(如在旋转设备时重新创建 Activity)的影响

ViewModel是如何保证同一个Activity对象仅有一个ViewMode对象?

ViewModel对象创建是通过ViewModelProvider对象的get方法来进行创建的,通过ViewModelProvider对象来保证ViewModel的唯一

3.2.1、ViewModelProvider构造方法

ViewModelProvider共有三个构造方法,如下:

public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
        this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
                ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
                : NewInstanceFactory.getInstance());
    }


 //ViewModelStoreOwner是个接口只有一个方法getViewModelStore,由Activity/Fragment实现(详见3.2.2)
public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
        this(owner.getViewModelStore(), factory);
    }


 //最终会调用的该构造方法,ViewModelStore封装了HashMap集合保存ViewModel对象
 //ViewModelStore详见3.2.3
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
        mFactory = factory;
        mViewModelStore = store; 

3.2.2、实现ViewModelStoreOwner接口

下列代码已Activity为例,Fragment类似

@NonNull
    @Override
    public ViewModelStore getViewModelStore() {
        if (getApplication() == null) {
            throw new IllegalStateException("Your activity is not yet attached to the "
                    + "Application instance. You can't request ViewModel before onCreate call.");
        }
        ensureViewModelStore();
        return mViewModelStore;
    }


    @SuppressWarnings("WeakerAccess") /* synthetic access */
    void ensureViewModelStore() {
 //mViewModelStore是ComponentActivity的成员变量,内部通过HashMap存储数据ViewModel
        if (mViewModelStore == null) {
            //如果为空,先尝试从mLastNonConfigurationInstances中取,
            //mLastNonConfigurationInstances是activity的成员变量,这个值在Acivity attach 的时候赋值
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                // Restore the ViewModelStore from NonConfigurationInstances
                mViewModelStore = nc.viewModelStore;
            }
//如果取值为空则重新创建一个
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
    }

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


    Set<String> keys() {
        return new HashSet<>(mMap.keySet());
    }


    /**
     *  Clears internal storage and notifies ViewModels that they are no longer used.
     */
    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.clear();
        }
        mMap.clear();
   

说明: 其实就是个HashMap,用来保存ViewModel对象

3.2.4、ViewModelProvider.get(ViewModel.class)方法获取ViewModel实例

    @NonNull
    @MainThread
    public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
        String canonicalName = modelClass.getCanonicalName();
        if (canonicalName == null) {
            throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
        }
        return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
    }
    @NonNull
    @MainThread
    public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        ViewModel viewModel = mViewModelStore.get(key);
        //优先从ViewModelStore中获取,不为空则直接返回
        if (modelClass.isInstance(viewModel)) {
            if (mFactory instanceof OnRequeryFactory) {
                ((OnRequeryFactory) mFactory).onRequery(viewModel);
            }
            return (T) viewModel;
        } else {
            //noinspection StatementWithEmptyBody
            if (viewModel != null) {
                // TODO: log a warning.
            }
        }
       //为空则通过mFactory创建,
       //这里的mFactory如果ViewModeStoreOwner没有实现HasDefaultViewModelProviderFactory
       //则默认使用NewInstanceFactory通过反射构建ViewModel对象。
       //Componentactivity默认是实现了HasDefaultViewModelProviderFactory接口的,返回  SavedStateViewModelFactory,
       //如果是ViewModel是无参构造则最后还是会通过NewInstanceFactory构建对象
        if (mFactory instanceof KeyedFactory) {
            viewModel = ((KeyedFactory) mFactory).create(key, modelClass);
        } else {
            viewModel = mFactory.create(modelClass);
        }
        mViewModelStore.put(key, viewModel);
        return (T) viewModel;
    }

3.2.5、NewInstanceFactory工厂类

NewInstanceFactory通过反射构建ViewModel对象,方法比较简单,其他工厂类似

    public static class NewInstanceFactory implements Factory {


        private static NewInstanceFactory sInstance;


        @NonNull
        static NewInstanceFactory getInstance() {
            if (sInstance == null) {
                sInstance = new NewInstanceFactory();
            }
            return sInstance;
        }


        @SuppressWarnings("ClassNewInstance")
        @NonNull
        @Override
        public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
            //noinspection TryWithIdenticalCatches
            try {
                return modelClass.newInstance();
            } catch (InstantiationException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            }
        }
   

说明: 上述5个步骤即保证了ViewModel对象在Activity/Fragment的唯一性

3.2.6、配置改变导致Activity重建状态保存

如何保证配置状态改变导致Activity重建的情况下,ViewModel不变?

主要是靠NonConfigurationInstances对象,代码说明如下

    @Nullable
    public final Object onRetainNonConfigurationInstance() {
        Object custom = this.onRetainCustomNonConfigurationInstance();
        ViewModelStore viewModelStore = this.mViewModelStore;
        ComponentActivity.NonConfigurationInstances nci;
        if (viewModelStore == null) {
            nci = (ComponentActivity.NonConfigurationInstances)this.getLastNonConfigurationInstance();
            if (nci != null) {
                viewModelStore = nci.viewModelStore;
            }
        }


        if (viewModelStore == null && custom == null) {
            return null;
        } else {
            nci = new ComponentActivity.NonConfigurationInstances();
            nci.custom = custom;
            //保存viewModelStore
            nci.viewModelStore = viewModelStore;
            return nci;
        }
    

四、实践总结

4.1、内存泄露

1、 ViewModel不能持有Activity/Fragment/View相关对象的引用,因为ViewModel生命周期和组件的生命周期不一致,这种情况下可能(少数情况,因为一般我们不允许配置更新导致Activity重建,但是即使仅从设计模式的角度考虑我们也应该这么做)会导致内存泄露。

2、 LiveData.observer方法注册观察者的时候,注意如果是Fragment和View的情况。Fragment建议传递Fragment自身this作为LifecycleOwner或者viewLifecycleOwner(和Activity的LifecycleOwner不是同一个,viewLifecycleOwner更建议使用)因为Fragment可能会先于 activity销毁,viewLifecycleOwner则是在onCreateView是创建,onDestroyView时销毁,LiveData.observer销毁时机也是一样的,使用viewLifecycleOwner可以保证和View的生命周期一致。

3、组件真正销毁时,会调用ViewModel的onCleared方法,注意销毁必要的数据,比如说数据层的监听回调等等(或者在数据层使用弱应用,不是必须,需要注意数据层生命周期很长的情况,例如数据层是个单例,一直持有ViewModel的引用会导致ViewModel无法回收)

4.2、数据保存

1、 Activity/Fragment 或者Fragment/Frament之间传递数据,注意传递的ViewModelStore需要时同一个,因为Fragment也会实现ViewModelStoreOwner接口,通过Fragment的getViewModelStore方法和Activity的getViewModelStrore方法获取的ViewModelStore并不是同一个,所以最终获取到的ViewModel对象并不是同一个。Fragment共享数据需要相同的Activity作为ViewModelStoreOwner,建议使用requireActivity方法获取Acitivity。

2、 ViewModel只能在配置更新导致的Activity重建的情况下保存,例如屏幕旋转。并不能在异常销毁Activity的情况保存数据,例如操作系统因为内存紧张杀掉进程的情况,这种情况数据并不能通过ViewModel恢复数据,可以考虑使用onSaveInstanceState方法或者其他方法保存数据。