Jetpack系列 之 ViewModel

113 阅读8分钟

本文目标

  • ViewModel是什么?
  • ViewModel有何作用?
  • ViewModel原理?
  • ViewModel没有之前是怎样的?对比有何优势?

ViewModel基本概念

ViewModel 类旨在以注重生命周期的方式存储和管理界面相关的数据。 ViewModel 类让数据可在发生屏幕旋转等配置更改后继续留存。

viewmodel-lifecycle.png

从图中可见,ViewModel的生命周期是从Activity的onCreate中到Activity正常销毁为止。也就是说当应用因为旋转导致界面重绘,这种非正常的界面销毁,是不会让ViewModel销毁的。

ViewModel原理解析

一、ViewModel的使用

// 声明
class GuideViewModel : ViewModel() {
}


// 使用
private val viewModel: GuideActivityViewModel by viewModels()

使用上也是异常简单,首先声明一个继承至ViewModel类,然后“创建”声明的ViewModel类对象就可以使用了

二、ViewModel初始化

上述的使用中,我们使用的是 by viewModels方式

其中viewModels方法是ComponentActivity的一个扩展方法

@MainThread
public inline fun <reified VM : ViewModel> ComponentActivity.viewModels(
    noinline factoryProducer: (() -> Factory)? = null
): Lazy<VM> {
    val factoryPromise = factoryProducer ?: {
        defaultViewModelProviderFactory
    }

    return ViewModelLazy(VM::class, { viewModelStore }, factoryPromise)
}

viewModels参数中声明了一个创建构建ViewModel的工厂方法。如果不传就使用默认的构建方式构建ViewModel。

其中默认的工厂方法为SavedStateViewModelFactory,后面再详细的介绍

public class ViewModelLazy<VM : ViewModel> (
    private val viewModelClass: KClass<VM>,
    private val storeProducer: () -> ViewModelStore,
    private val factoryProducer: () -> ViewModelProvider.Factory
) : Lazy<VM> {
    private var cached: VM? = null

    override val value: VM
        get() {
            val viewModel = cached
            return if (viewModel == null) {
                val factory = factoryProducer()
                val store = storeProducer()
                // 创建ViewModel
                ViewModelProvider(store, factory).get(viewModelClass.java).also {
                    cached = it
                }
            } else {
                viewModel
            }
        }
}

ViewModelLazy是继承了Lazy子类,可以缓存生成的ViewModel类,避免重复的创建。ViewModelLazy类只在第一次的时候通过传入的工厂方法创建ViewModel,并缓存,供之后使用

接下来再看下ViewModelProvider中的get方法

public open operator fun <T : ViewModel> get(modelClass: Class<T>): T {
    val canonicalName = modelClass.canonicalName
        ?: throw IllegalArgumentException("Local and anonymous classes can not be ViewModels")
        // 通过一定的算法组装成一个key,用来标识创建的ViewModel,以便查询
    return get("$DEFAULT_KEY:$canonicalName", modelClass)
}


@Suppress("UNCHECKED_CAST")
@MainThread
public open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T {

    // 1、从ViewModelStore中查询key所对应的ViewModel,如果有且是ViewModel则返回
    var viewModel = store[key]
    if (modelClass.isInstance(viewModel)) {
        (factory as? OnRequeryFactory)?.onRequery(viewModel)
        return viewModel as T
    } else {
        @Suppress("ControlFlowWithEmptyBody")
        if (viewModel != null) {
            // TODO: log a warning.
        }
    }
    // 在没有缓存的情况下,使用factory生成新的ViewModel并缓存
    viewModel = if (factory is KeyedFactory) {
        factory.create(key, modelClass)
    } else {
        factory.create(modelClass)
    }
    store.put(key, viewModel)
    return viewModel
}

可见最终的生成ViewModel逻辑还是在Factory,而默认的工厂是SavedStateViewModelFactory

@NonNull
@Override
public <T extends ViewModel> T create(@NonNull String key, @NonNull Class<T> modelClass) {
    boolean isAndroidViewModel = AndroidViewModel.class.isAssignableFrom(modelClass);
    Constructor<T> constructor;
    if (isAndroidViewModel && mApplication != null) {
        constructor = findMatchingConstructor(modelClass, ANDROID_VIEWMODEL_SIGNATURE);
    } else {
        constructor = findMatchingConstructor(modelClass, VIEWMODEL_SIGNATURE);
    }
    // doesn't need SavedStateHandle
    // 如果不需要SavedStateHandle方式,即构造方法中没有ANDROID_VIEWMODEL_SIGNATURE签名的,则使用默认的方式构造
    if (constructor == null) {
        return mFactory.create(modelClass);
    }

    SavedStateHandleController controller = SavedStateHandleController.create(
            mSavedStateRegistry, mLifecycle, key, mDefaultArgs);
    try {
        T viewmodel;
        if (isAndroidViewModel && mApplication != null) {
            viewmodel = constructor.newInstance(mApplication, controller.getHandle());
        } else {
            viewmodel = constructor.newInstance(controller.getHandle());
        }
        viewmodel.setTagIfAbsent(TAG_SAVED_STATE_HANDLE_CONTROLLER, controller);
        return viewmodel;
    } catch (IllegalAccessException e) {
        throw new RuntimeException("Failed to access " + modelClass, e);
    } catch (InstantiationException e) {
        throw new RuntimeException("A " + modelClass + " cannot be instantiated.", e);
    } catch (InvocationTargetException e) {
        throw new RuntimeException("An exception happened in constructor of "
                + modelClass, e.getCause());
    }
}

create方法,简单说就是找到符合的条件的ViewModel构造方法

首先,这里是Android设备,isAndroidViewModel为true

再查找ANDROID_VIEWMODEL_SIGNATURE签名的构造方法

private static final Class<?>[] ANDROID_VIEWMODEL_SIGNATURE = new Class[]{Application.class,
        SavedStateHandle.class};

没有SavedStateHandle构造方法,则使用mFactory创建ViewModel。如果没有合适的ViewModel构造方法,则抛出异常。

小结:

  • 所有的ViewModel是保存在ViewModelStore中的,ViewModelStore中使用Map形式存放。key为String类型。查询的时候,通过KEY来获取缓存ViewModel
public class ViewModelStore {

    private final HashMap<String, ViewModel> mMap = new HashMap<>();
  • 如果缓存中有ViewModel的实例,则返回缓存。没有则通过提供的Factory工厂创建新的ViewModel。默认的是SavedStateViewModelFactory,如果ViewModel有对应SavedStateHandle类型的构造方法,则会创建,没有则创建默认的ViewModel,都没有就抛出异常。或者可以自定义Factory创建特定ViewModel

三、ViewModelStore恢复和保存

从上一节可知,ViewModel是存储在ViewModelStore中的,且在因配置重新创建Activity的时候,ViewModel并不会销毁。所以可知其实是ViewModelStore没有被销毁。

ViewModelStore是如何在被保存的?

先看下ComponentActivity类

    // Lazily recreated from NonConfigurationInstances by getViewModelStore()
    private ViewModelStore mViewModelStore;
    private ViewModelProvider.Factory mDefaultFactory;

ComponentActivity中存在mViewModelStore实例,和默认的ViewModel工厂实例。接下来看下mViewModelStore的初始化

    void ensureViewModelStore() {
        if (mViewModelStore == null) {
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                // Restore the ViewModelStore from NonConfigurationInstances
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
    }

在每次使用ViewModelStore之前,都要确保ViewModelStore已经生成了,否则需要创建。

在ensureViewModelStore中,从NonConfigurationInstances恢复ViewModelStore,如果没有则生成个新ViewModelStore对象。

这里有两个问题 1、ViewModelStore是怎么保存的? 2、ViewModelStore是怎么恢复的?

先看下第一个问题,沿着上面内容,看下NonConfigurationInstances结构


    public Object getLastNonConfigurationInstance() {
        return mLastNonConfigurationInstances != null
                ? mLastNonConfigurationInstances.activity : null;
    }
	
	// ComponentActivity类中声明
	static final class NonConfigurationInstances {
        Object custom;
        ViewModelStore viewModelStore;
    }

	// Activity类中声明
    static final class NonConfigurationInstances {
        Object activity;
        HashMap<String, Object> children;
        FragmentManagerNonConfig fragments;
        ArrayMap<String, LoaderManager> loaders;
        VoiceInteractor voiceInteractor;
    }

getLastNonConfigurationInstance()就是返回其中activity对象就是ComponentActivity类中声明的对象。 custom留给我们自定义使用,viewModelStore就是保存的值。

目前可知,ViewModelStore等对象都是从mLastNonConfigurationInstances来的,那么mLastNonConfigurationInstances是怎么生成的?

    final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken,
            IBinder shareableActivityToken) {
        
			...
			// 赋值
			mLastNonConfigurationInstances = lastNonConfigurationInstances;
			...
	
	}

在Activity的attach方法中,这里可以简单的理解为生产Activity时候回调attach,attach是由系统调用的。lastNonConfigurationInstances也是由系统保存的,在生成Activity时候传递而来。

这里我们知道了ViewModelStore是怎么恢复的。现在在看下ViewModelStore是怎么保存的。

onRetainNonConfigurationInstance由系统调用,作为由于配置更改而销毁活动的一部分,当已知将立即为新配置创建新实例时。可以在此处返回您喜欢的任何对象,包括活动实例本身,稍后可以通过在新活动实例中调用getLastNonConfigurationInstance()来检索这些对象

    public final Object onRetainNonConfigurationInstance() {
        // Maintain backward compatibility.
        Object custom = onRetainCustomNonConfigurationInstance();

        ViewModelStore viewModelStore = mViewModelStore;
		
		// 如果mViewModelStore,则获取上一次保存的ViewModelStore
        if (viewModelStore == null) {
            // No one called getViewModelStore(), so see if there was an existing
            // ViewModelStore from our last NonConfigurationInstance
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                viewModelStore = nc.viewModelStore;
            }
        }

		// 如果没有要保存的数据,则返回空
        if (viewModelStore == null && custom == null) {
            return null;
        }

		// 保存自定义内容和ViewModelStore
        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.custom = custom;
        nci.viewModelStore = viewModelStore;
        return nci;
    }
	
	
	// 可以在此处返回您喜欢的任何对象,包括活动实例本身
	public Object onRetainCustomNonConfigurationInstance() {
        return null;
    }

调用onRetainNonConfigurationInstance对象的在Activity中

NonConfigurationInstances retainNonConfigurationInstances() {
        Object activity = onRetainNonConfigurationInstance();
}		

retainNonConfigurationInstances则是在ActivityThread中执行,这里在要销毁Activity时,非正常销毁的情况下保存配置信息

    void performDestroyActivity(ActivityClientRecord r, boolean finishing,
            int configChanges, boolean getNonConfigInstance, String reason) {
        Class<? extends Activity> activityClass = null;
        if (localLOGV) Slog.v(TAG, "Performing finish of " + r);
        activityClass = r.activity.getClass();
        r.activity.mConfigChangeFlags |= configChanges;
        if (finishing) {
            r.activity.mFinished = true;
        }

        performPauseActivityIfNeeded(r, "destroy");

        if (!r.stopped) {
            callActivityOnStop(r, false /* saveState */, "destroy");
        }
        if (getNonConfigInstance) {
            try {
                r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances();
            } catch (Exception e) {
                if (!mInstrumentation.onException(r.activity, e)) {
                    throw new RuntimeException("Unable to retain activity "
                            + r.intent.getComponent().toShortString() + ": " + e.toString(), e);
                }
            }
        }

小结:

这里我们分析了由配置导致Activity重建情况下,ViewModelStore的保存和恢复流程了。异常销毁情况下,会在onStop和onDestroy之间保存ViewModelStore,以及在重新创建Activity时候,恢复ViewModelStore

四、ViewModelStore正常流程

上一节中,讲解了异常情况下的对ViewModelStore的保存和恢复,那正常的情况下是如何的?以及流程是怎么样的?

还是在ComponentActivity类中,截取了ComponentActivity构造方法

public ComponentActivity() {
        Lifecycle lifecycle = getLifecycle();
        ....
        getLifecycle().addObserver(new LifecycleEventObserver() {
            @Override
            public void onStateChanged(@NonNull LifecycleOwner source,
                    @NonNull Lifecycle.Event event) {
                if (event == Lifecycle.Event.ON_DESTROY) {
                    // Clear out the available context
                    mContextAwareHelper.clearAvailableContext();
                    // And clear the ViewModelStore
                    if (!isChangingConfigurations()) {
                        getViewModelStore().clear();
                    }
                }
            }
        });
		....
      
}

在构造方法中,我们监听了Activity的生命周期,并在ON_DESTROY事件回调时候,如果活动不是因为新配置而被销毁,那么就会走到getViewModelStore().clear();

public final void clear() {
    for (ViewModel vm : mMap.values()) {
        vm.clear();
    }
    mMap.clear();
}

ViewModelStore中,它会依次的调用ViewModel中的Clear方法,并清空ViewModelStore中,保存的ViewModel实例。这样的话,下次重新进入时候便会重新的创建新的ViewModel。

五、ViewModel作用

1、两种情况下的异常销毁

  • 因为配置改变,屏幕旋转等原因导致的屏幕重绘。Activity会被销毁
  • 由于系统资源不足导致Activity被销毁

解决:

  • 对于第一种,ViewModel可以解决
  • 对于第二种,SavedStateHandle可以解决。采用SavedStateHandle,将原本在Activity中存取数据逻辑放置到ViewModel中。

2、为什么需要ViewModel

  1. 系统销毁或重新创建界面控制器,则存储在其中的任何瞬态界面相关数据都会丢失。例如,应用可能会在它的某个 Activity 中包含用户列表。为配置更改重新创建 Activity 后,新 Activity 必须重新提取用户列表。对于简单的数据,Activity 可以使用 onSaveInstanceState() 方法从 onCreate() 中的捆绑包恢复其数据,但此方法仅适合可以序列化再反序列化的少量数据,而不适合数量可能较大的数据,如用户列表或位图。

  2. 界面控制器经常需要进行可能需要一些时间才能返回的异步调用。界面控制器需要管理这些调用,并确保系统在其销毁后清理这些调用以避免潜在的内存泄漏。

  3. ViewModel中可以保存大量数据,便于共享数据。并且可以在不同的作用域中创建属于自己的ViewModelStore

3、ViewModel出现的背景初衷和要达到什么目标或者解决什么问题

  • ViewModel是要实现关注点分离,解放Activity中数据处理,由ViewModel作为数据的载体,来实现数据驱动。

  • ViewModel可以解决Config改变导致的屏幕销毁重建后,数据丢失问题

  • 可以解决多Fragment的数据共享

4、ViewModel的优势和劣势

  • 优势
    • 系统自带,Jetpack中携带的,官方维护

    • 可以解决数据共享、数据保存和模型驱动UI

5、:ViewModel的使用场景

  • 需要Config变化,重建UI后,数据可以不销毁
  • 方便数据共享,同Activity中的多Fragment间很方便共享数据,可以细粒度的区分业务
  • 方便模型驱动View渲染,关注点分离,可以单独测试ViewModel,View和ViewModel可以区分
  • View可以持有ViewModel,ViewModel不能持有View,ViewModel的生命周期更长

6、ViewModel和其他模式对比

  • MVC
    • 用户输入在C层控制
    • C即Controller,大多数的控制逻辑都在C层,对于已经有处理Event能力的View没由用到
    • C和V是一一对应的,没办法复用View
  • MVP
    • M和V不直接访问,而是通过P层,这样V就可以复用
    • M和V都依赖P层,P层会臃肿,且P层改动都需要改动
  • MVVM
    • M和V都依赖P层,对MVP增加自动绑定过程,但是ViewModel层双向绑定V和M层,V层变化,系统自动修改M层数据,反之亦然
    • 在 MVVM 的架构下,View 层(DOM展示层)和 Model 层(数据对象层)并没有直接联系,而是通过 ViewModel 层进行交互。ViewModel 层通过双向数据绑定将 View 层和 Model 层连接了起来,使得 View 层和 Model 层的同步工作完全是自动的。
    • 数据绑定,可以理解为模板引擎,框架体积大,不轻巧