理解Jetpack--ViewModel

433 阅读7分钟

ViewModel 是什么

ViewModel 类是一种业务逻辑或屏幕级状态容器。它用于将状态公开给界面,以及封装相关的业务逻辑。 它的主要优点是,它可以缓存状态,并可在配置更改后持久保留相应状态。这意味着在 activity 之间导航时或进行配置更改后(例如旋转屏幕时),界面将无需重新提取数据。

ViewModel优势

ViewModel 的替代方案是保存要在界面中显示的数据的普通类。在 activity 或 Navigation 目的地之间导航时,这可能会造成问题。此时,如果您不利用保存实例状态机制存储相应数据,系统便会销毁相应数据。ViewModel 提供了一个便捷的数据持久性 API,可以解决此问题。

ViewModel 类的主要优势实际上有两个方面:

  • 它允许您持久保留界面状态。
  • 它可以提供对业务逻辑的访问权限。

ViewModel生命周期

我们看一下ViewModel的生命周期,在系统首次调用 activity 对象的 onCreate() 方法时请求 ViewModel。系统可能会在 activity 的整个生命周期内多次调用 onCreate(),如在旋转设备屏幕时。ViewModel 存在的时间范围是从您首次请求 ViewModel 直到 activity 完成并销毁。下面采用官网提供的一张图说明。

viewmodel-lifecycle.png

ViewModel如何使用

我们如何使用创建ViewModel并使用呢?我们需要自定义ViewModel子类,然后再Activity中通过ViewModelProvider获取ViewModel实例。代码如下。

class MainViewModel:ViewModel() {
}


class MainActivity : AppCompatActivity() {

    private lateinit var viewModel:MainViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        viewModel= ViewModelProvider(this,ViewModelProvider.NewInstanceFactory())
            .get(MainViewModel::class.java)
    }

源码分析

我们的源码重点关注下面几个问题:

  • ViewModel如何创建的。
  • ViewModel可以在配置更改后持久保留相应状态,那是如何实现的。
  • ViewModel生命周期如何销毁的。

ViewModel如何创建的?

我们上面提到在Activity中获取ViewModel使用了下面这段代码,那我们重点看一下代码中做了什么。

override fun onCreate(savedInstanceState: Bundle?) {
       ...
        viewModel= ViewModelProvider(this,ViewModelProvider.NewInstanceFactory())
            .get(MainViewModel::class.java)
    }

调用了ViewModelProvider的构造方法,在其构造方法中传入ViewModelStoreFactory,代码如下:

public constructor(owner: ViewModelStoreOwner, factory: Factory) : this(
    owner.viewModelStore,
    factory,
    defaultCreationExtras(owner)
)

constructor(
    private val store: ViewModelStore,
    private val factory: Factory,
    private val defaultCreationExtras: CreationExtras = CreationExtras.Empty,
)

我们先看一下ViewModelStore是什么?

open class ViewModelStore {

    private val map = mutableMapOf<String, ViewModel>()

    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    fun put(key: String, viewModel: ViewModel) {
        val oldViewModel = map.put(key, viewModel)
        oldViewModel?.onCleared()
    }

    /**
     * Returns the `ViewModel` mapped to the given `key` or null if none exists.
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    operator fun get(key: String): ViewModel? {
        return map[key]
    }

    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    fun keys(): Set<String> {
        return HashSet(map.keys)
    }

    /**
     * Clears internal storage and notifies `ViewModel`s that they are no longer used.
     */
    fun clear() {
        for (vm in map.values) {
            vm.clear()
        }
        map.clear()
    }
}

原来ViewModelStore内部就是一个Map,通过Map保存了我们创建的ViewModel

我们再来看看ViewModelProvider的另外一个参数Factory做了什么呢?Factory是一个接口,NewInstanceFactory是它其中的一个实现类,我们看一下其代码实现。

public open class NewInstanceFactory : Factory {
    @Suppress("DocumentExceptions")
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        return try {
            modelClass.getDeclaredConstructor().newInstance()
        } catch (e: NoSuchMethodException) {
            throw RuntimeException("Cannot create an instance of $modelClass", e)
        } catch (e: InstantiationException) {
            throw RuntimeException("Cannot create an instance of $modelClass", e)
        } catch (e: IllegalAccessException) {
            throw RuntimeException("Cannot create an instance of $modelClass", e)
        }
    }

   ...
}

NewInstanceFactory类特别简单,它的create方法通过反射创建了ViewModel。我们知道了Factorycreate方法创建了ViewModel,那create方法何时调用的呢?

我们继续看ViewModelProvider类的get()方法中做了什么,代码如下:

@MainThread
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")
    return get("$DEFAULT_KEY:$canonicalName", modelClass)
}

@MainThread
public open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T {
    ...
    return try {
        factory.create(modelClass, extras)
    } catch (e: AbstractMethodError) {
        factory.create(modelClass)
    }.also { store.put(key, it) }
}

上面的代码可以看出get()方法中会调用Factorycreate方法创建一个ViewModel并把它存在了ViewModelStore

通过对上面ViewModel创建的代码分析我们知道了,ViewModel是在Factory的create方法中通过反射实例化的,并把实例化后的对象保存在ViewModelStore中。

配置更改后持久保留相应状态,那是如何实现的?

要保持ViewModel的状态持久就需要ViewModel对象不变,上面知道了创建的ViewModel实例被添加到了ViewModelStore中,那就需要在配置更改后保持ViewModelStore不变,我们下面就看一下ViewModelStore被如何创建和保持不变的?

在AndroidX的的环境下,我们的Activity都会继承ComponentActivity,该类实现了ViewModelStoreOwner接口,所以我们能在自己定义的Activity中通过getViewModelStore()获取到ViewModelStore,代码如下:

interface ViewModelStoreOwner {

    /**
     * The owned [ViewModelStore]
     */
    val viewModelStore: ViewModelStore
}

ViewModelStoreOwner的实现类中getViewModelStore()做了什么?代码如下:

@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() {
    //注释1
    if (mViewModelStore == null) {
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
         //注释2
        if (nc != null) {
            // Restore the ViewModelStore from NonConfigurationInstances
            mViewModelStore = nc.viewModelStore;
        }
        //注释3
        if (mViewModelStore == null) {
            mViewModelStore = new ViewModelStore();
        }
    }
}

上面的代码注释1处,先进行了mViewModelStore == null判定,只有为null才处理,是不是有点保持唯一性的感觉了。注释2处,从NonConfigurationInstancesViewModelStore对象,这里就是横竖屏改变后能保持ViewModelStore对象不变的关键,NonConfigurationInstances是什么我们一会再讲。注释3处,直接创建了一个ViewModelStore实例,首次activity会再这里创建该实例。

下面我们看一下上面代码中的getLastNonConfigurationInstance()方法,该方法是在Activity类中定义的,代码如下:

@Nullable
public Object getLastNonConfigurationInstance() {
    return mLastNonConfigurationInstances != null
            ? mLastNonConfigurationInstances.activity : null;
}

static final class NonConfigurationInstances {
    Object activity;
    HashMap<String, Object> children;
    FragmentManagerNonConfig fragments;
    ArrayMap<String, LoaderManager> loaders;
    VoiceInteractor voiceInteractor;
}

该方法返回了mLastNonConfigurationInstances对象中的activity,mLastNonConfigurationInstances字段所代表类的具体定义是NonConfigurationInstancesmLastNonConfigurationInstances字段是何时初始化的?该字段是在Activity类的attach方法中初始化,代码如下:

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;
   ...
}

上面我们知道了getLastNonConfigurationInstance()方法会返回NonConfigurationInstances的activity,那在什么地方给该NonConfigurationInstances的activity赋值的?会在Activity类的retainNonConfigurationInstances()方法中赋值,我们看一下具体实现:

NonConfigurationInstances retainNonConfigurationInstances() {
    Object activity = onRetainNonConfigurationInstance();
    HashMap<String, Object> children = onRetainNonConfigurationChildInstances();
    FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();

    // We're already stopped but we've been asked to retain.
    // Our fragments are taken care of but we need to mark the loaders for retention.
    // In order to do this correctly we need to restart the loaders first before
    // handing them off to the next activity.
    mFragments.doLoaderStart();
    mFragments.doLoaderStop(true);
    ArrayMap<String, LoaderManager> loaders = mFragments.retainLoaderNonConfig();

    if (activity == null && children == null && fragments == null && loaders == null
            && mVoiceInteractor == null) {
        return null;
    }

    NonConfigurationInstances nci = new NonConfigurationInstances();
    nci.activity = activity;
    nci.children = children;
    nci.fragments = fragments;
    nci.loaders = loaders;
    if (mVoiceInteractor != null) {
        mVoiceInteractor.retainInstance();
        nci.voiceInteractor = mVoiceInteractor;
    }
    return nci;
}

会调用onRetainNonConfigurationInstance()方法,并把它的返回值给NonConfigurationInstances的activity字段。我们看一下onRetainNonConfigurationInstance()方法

public Object onRetainNonConfigurationInstance() {
    return null;
}

该方法在Activity类中返回了null,所以具体的实现应该是在其子类中,我们去ComponentActivity类中看看该方法的实现

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

    ViewModelStore viewModelStore = mViewModelStore;
    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;
    }

    NonConfigurationInstances nci = new NonConfigurationInstances();
    nci.custom = custom;
    nci.viewModelStore = viewModelStore;
    return nci;
}

该方法中返回了NonConfigurationInstances,该类中包含了我们创建的ViewModelStore

注意ComponentActivity类中的NonConfigurationInstances与我们Activity类中的NonConfigurationInstances不是同一个。我们可以理解成在Activity类的retainNonConfigurationInstances()方法内,会把ComponentActivity类的NonConfigurationInstances对象,赋值给Activity类中的NonConfigurationInstances对象的activity字段。

这样ViewModelActivity类的NonConfigurationInstances就关联起来了。那retainNonConfigurationInstances()方法何时调用,由谁调用的?

该类在ActivityThread类中调用。我们在横竖屏切换时,会调用ActivityThread类的handleRelaunchActivity()方法,代码如下:

public void handleRelaunchActivity(ActivityClientRecord tmp,
        PendingTransactionActions pendingActions) {
  
    ...

    handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents,
            pendingActions, tmp.startsNotResumed, tmp.overrideConfig, "handleRelaunchActivity");
}

我们再看handleRelaunchActivityInner(...)方法,该方法会调用activity的onPause->onStop->onDestroy->attach->onCreate...,具体代码如下:

private void handleRelaunchActivityInner(ActivityClientRecord r, int configChanges,
        List<ResultInfo> pendingResults, List<ReferrerIntent> pendingIntents,
        PendingTransactionActions pendingActions, boolean startsNotResumed,
        Configuration overrideConfig, String reason) {
    // Preserve last used intent, it may be set from Activity#setIntent().
    final Intent customIntent = r.activity.mIntent;
    // Need to ensure state is saved.
    if (!r.paused) {
        performPauseActivity(r, false, reason, null /* pendingActions */);
    }
    if (!r.stopped) {
        callActivityOnStop(r, true /* saveState */, reason);
    }

    handleDestroyActivity(r, false, configChanges, true, reason);

    r.activity = null;
    r.window = null;
    r.hideForNow = false;
    // Merge any pending results and pending intents; don't just replace them
    if (pendingResults != null) {
        if (r.pendingResults == null) {
            r.pendingResults = pendingResults;
        } else {
            r.pendingResults.addAll(pendingResults);
        }
    }
    if (pendingIntents != null) {
        if (r.pendingIntents == null) {
            r.pendingIntents = pendingIntents;
        } else {
            r.pendingIntents.addAll(pendingIntents);
        }
    }
    r.startsNotResumed = startsNotResumed;
    r.overrideConfig = overrideConfig;

    handleLaunchActivity(r, pendingActions, mLastReportedDeviceId, customIntent);
}

这里我们重点分析handleDestroyActivity(...)handleLaunchActivity(...)

handleDestroyActivity(...)方法会调用performDestroyActivity(...),这里会调用Activity的retainNonConfigurationInstances()方法,并把返回值赋值给ActivityClientRecord

public void handleDestroyActivity(ActivityClientRecord r, boolean finishing, int configChanges,
        boolean getNonConfigInstance, String reason) {
    performDestroyActivity(r, finishing, configChanges, getNonConfigInstance, reason);
   ...
}

void performDestroyActivity(ActivityClientRecord r, boolean finishing,
        int configChanges, boolean getNonConfigInstance, String reason) {
   ...
    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);
            }
        }
    }
    ...
}

handleLaunchActivity(...)方法会调用performLaunchActivity(...),这里会调用Activity的attach(...)方法,会把lastNonConfigurationInstances传进去。

public Activity handleLaunchActivity(ActivityClientRecord r,
        PendingTransactionActions pendingActions, int deviceId, Intent customIntent) {
   ...

    final Activity a = performLaunchActivity(r, customIntent);

    ...
}


private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
           ...
            activity.attach(appContext, this, getInstrumentation(), r.token,
                    r.ident, app, r.intent, r.activityInfo, title, r.parent,
                    r.embeddedID, r.lastNonConfigurationInstances, config,
                    r.referrer, r.voiceInteractor, window, r.activityConfigCallback,
                    r.assistToken, r.shareableActivityToken);

          ...
}

总结:在配置更改后会通过retainNonConfigurationInstances()方法把ViewModelStore对象保存在ActivityThread中,在activity创建时通过attach(...)方法把对象回传,这样保证ViewModelStore对象的唯一。ViewModelStore对象通过map保存了ViewModel,这样就实现了viewMoel中保存的数据状态不变。

ViewModel如何销毁

ComponentActivity的构造方法中添加了LifecycleEventObserver观察者,在destroy时会调用ViewModelStore的clear方法清空ViewModel

public ComponentActivity() {
    ...
    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();
                }
                mReportFullyDrawnExecutor.activityDestroyed();
            }
        }
    });

    ...
}