Jetpack源码解析(三):ViewModel解析

Jetpack源码解析之ViewModel

ViewModel 最早是在 2017 年的 Google I/O 大会上被提出,同时间问世的还有 Lifecycle 和 LiveData。

Google开发团队希望我们用ViewModel来管理页面状态相关信息,并建议我们搭配LiveData一起食用。

基本使用

依赖(可选),activity-ktx artifact。

implementation "androidx.fragment:fragment-ktx:1.2.5"//by viewModels()

ViewModel和Activity/Fragment相绑定,被视图持有。ViewModel存在的时间范围是从首次请求ViewModel直到 Activity 完成并销毁,所以ViewModel类让数据可在发生屏幕旋转等配置更改后继续留存。

    class MyViewModel : ViewModel() {
        private val users: MutableLiveData<List<User>> by lazy {
            MutableLiveData().also {
                loadUsers()
            }
        }

        fun getUsers(): LiveData<List<User>> {
            return users
        }

        private fun loadUsers() {
            // Do an asynchronous operation to fetch users.
        }
    }
    

然后,您可以从 Activity 访问该列表,如下所示:

    class MyActivity : AppCompatActivity() {

        override fun onCreate(savedInstanceState: Bundle?) {
            // Create a ViewModel the first time the system calls an activity's onCreate() method.
            // Re-created activities receive the same MyViewModel instance created by the first activity.

            // Use the 'by viewModels()' Kotlin property delegate
            // from the activity-ktx artifact
            val model: MyViewModel by viewModels()
            //等同于 val model = ViewModelProvider(this).get(MyViewModel::class.java)
            model.getUsers().observe(this, Observer<List<User>>{ users ->
                // update UI
            })
        }
    }
    

注意ViewModel 绝不能引用视图、Lifecycle 或可能存储对 Activity 上下文的引用的任何类。

by viewModels()为Kotlin语法糖,Fragment中用by viewModels获取到的是属于该Fragment的model,如果在Fragment中想要获取activity的ViewModel,则要使用 by activityViewModels()

原理解析

ViewModel创建

by viewModels()是ktx包的便便用法,实际内部调用到的还是ViewModelProvider(store, factory).get(viewModelClass.java)。所以我们直接看ViewModelProvider。

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

public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
    mFactory = factory;
    mViewModelStore = store;
}

传入了store和factory。

store

getViewModelStore()获取的。ComponentActivity实现了ViewModelStoreOwner接口,所以getViewModelStore的实现在ComponentActivity中,每个Activity中会持有一个viewModel实例。

compomentActivity.getViewModelStore()

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

factory

public interface Factory {
    /**
    * Creates a new instance of the given {@code Class}.
    * <p>
    *
    * @param modelClass a {@code Class} whose instance is requested
    * @param <T>        The type parameter for the ViewModel.
    * @return a newly created ViewModel
    */
    @NonNull
    <T extends ViewModel> T create(@NonNull Class<T> modelClass);
}

可以看到Factory就一个create方法,注释有说用来创建ViewModel实例的。

HasDefaultViewModelProviderFactory

如果activity实现了HasDefaultViewModelProviderFactory接口,则factory在getDefaultViewModelProviderFactory中:

@Override
public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
    if (getApplication() == null) {
        throw new IllegalStateException("Your activity is not yet attached to the "
                + "Application instance. You can't request ViewModel before onCreate call.");
    }
    if (mDefaultFactory == null) {
        mDefaultFactory = new SavedStateViewModelFactory(
                getApplication(),
                this,
                getIntent() != null ? getIntent().getExtras() : null);
    }
    return mDefaultFactory;
}
public final class SavedStateViewModelFactory extends ViewModelProvider.KeyedFactory  { ... }
abstract static class KeyedFactory extends OnRequeryFactory implements Factory { ... }

返回的是个SavedStateViewModelFactory,其create方法:

@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
    // ViewModelProvider calls correct create that support same modelClass with different keys
    // If a developer manually calls this method, there is no "key" in picture, so factory
    // simply uses classname internally as as key.
    String canonicalName = modelClass.getCanonicalName();
    if (canonicalName == null) {
        throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
    }
    return create(canonicalName, modelClass);
}
private static final Class<?>[] ANDROID_VIEWMODEL_SIGNATURE = new Class[]{Application.class,SavedStateHandle.class};
private static final Class<?>[] VIEWMODEL_SIGNATURE = new Class[]{SavedStateHandle.class};

@Override
public <T extends ViewModel> T create(@NonNull String key, @NonNull Class<T> modelClass) {
    //AndroidViewModel 继承ViewModel,内部持有一个application
    boolean isAndroidViewModel = AndroidViewModel.class.isAssignableFrom(modelClass);
    //判断是不是AndroidViewModel的子类,用不同的构造器
    Constructor<T> constructor;
    if (isAndroidViewModel) {
        constructor = findMatchingConstructor(modelClass, ANDROID_VIEWMODEL_SIGNATURE);
    } else {
        constructor = findMatchingConstructor(modelClass, VIEWMODEL_SIGNATURE);
    }
    // doesn't need SavedStateHandle
    if (constructor == null) {
        //到这里表示是无参数的构造函数,会调到NewInstanceFactory中的create,和第二种情况一样
        return mFactory.create(modelClass);
    }

    SavedStateHandleController controller = SavedStateHandleController.create(
            mSavedStateRegistry, mLifecycle, key, mDefaultArgs);
    try {
        T viewmodel;
        if (isAndroidViewModel) {
            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());
    }
}

先判断传入的类是不是AndroidViewModel的子类,然后调用findMatchingConstructor,传入的signature不同。

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

private static <T> Constructor<T> findMatchingConstructor(Class<T> modelClass,
        Class<?>[] signature) {
    for (Constructor<?> constructor : modelClass.getConstructors()) {
        Class<?>[] parameterTypes = constructor.getParameterTypes();
        if (Arrays.equals(signature, parameterTypes)) {
            return (Constructor<T>) constructor;
        }
    }
    return null;
}

这堆代码说白了就是:要么返回一个形参为SavedStateHandle.class的构造函数,要么返回一个形参为(Application.class,SavedStateHandle.class)的构造函数。没有这两样构造函数的中的任何一种的话,返回null。如果为null,则处理方式和第二种情况一样了。

如果不为null,则证明构造函数中传入了SavedStateHandle,即需要保存状态。后续代码暂且不表。

!HasDefaultViewModelProviderFactory

如果activity没有实现HasDefaultViewModelProviderFactory接口,返回的直接是个NewInstanceFactory。且NewInstanceFactory中,其create方法如下:

@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);
    }
}

直接拿到类后通过反射就new出了Instance。

get(VM.class)

@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);
}

@MainThread
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
    ViewModel viewModel = mViewModelStore.get(key);

    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.
        }
    }
    if (mFactory instanceof KeyedFactory) {
        viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
    } else {
        viewModel = (mFactory).create(modelClass);
    }
    mViewModelStore.put(key, viewModel);
    return (T) viewModel;
}

如果能在mViewModelStore中通过“包名”为Key获取到viewModel则将其返回;否则用上一步创造出来的Factory来创建viewModel,然后存入mViewModelStore中。

ViewModelStore

通过以上的描述,猜想ViewModelStore应该是个类似map的东西。所以看下源码:

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();
    }
}

果然,内部维护了一个map,Key就是我们class的完整包名,在同一个activity下重复获取获取到的都是是同一个viewModel。

ViewModel销毁

ViewModel销毁的相关代码在ComponentActivity的初始化方法中:

public ComponentActivity() {
    ...
    getLifecycle().addObserver(new LifecycleEventObserver() {
        @Override
        public void onStateChanged(@NonNull LifecycleOwner source,@NonNull Lifecycle.Event event) {
            if (event == Lifecycle.Event.ON_DESTROY) {
                if (!isChangingConfigurations()) {
                    getViewModelStore().clear();
                }
            }
        }
    });
}

利用LifecycleObserver,当执行ON_DESTROY方法时,调用到viewModelStore的clear()。

保存状态

ViewModel 对象存在的时间范围是获取 ViewModel 时传递给 ViewModelProvider 的 Lifecycle。

ViewModel 将一直留在内存中,直到限定其存在时间范围的 Lifecycle 永久消失:对于 Activity,是在 Activity Finish时;而对于 Fragment,是在 Fragment 分离时。

说明 ViewModel 随着 Activity 状态的改变而经历的生命周期。

有一种情况比较特殊,屏幕旋转(不配置configChanges)的时候,activity生命周期是会经过销毁重建的,即:

onPause--onSaveInstanceState--onStop--onRetainCustomNonConfigurationInstance--onDestroy--onCreate--onStart--onRestoreInstanceState--onResume

而ViewModel不会再onDestroy过程中销毁,原因有二:

  1. 在onDestroy判断中,有一个判断:
if (!isChangingConfigurations()) {
    getViewModelStore().clear();
}

isChangingConfigurations表示此次销毁是否是为了改变配置而重建的一次操作,如果是的,就先不销毁了。

2.NonConfigurationInstances

我们在onStop和onDestroy中会调用一个onRetainCustomNonConfigurationInstance【为了便于区分简写onRCustomNC】方法,这个方法原是为了保留自定义非配置实例,它的前身是onRetainNonConfigurationInstance【简写为onRNC】,但是现在onRNC不能被重写【加了final】,onRCustomNC即将被弃用,官方推荐我们用ViewModel来代替。

因为在onRetainNonConfigurationInstance【onRNC】中,源码中将mViewModelStore保存了起来。

public final Object onRetainNonConfigurationInstance() {
    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;
}

这里面用来保存viewModelStore的载体是NonConfigurationInstances。NonConfigurationInstances是ComponentActivity中的一个静态内部类,里面存了viewModelStore;

static final class NonConfigurationInstances {
    Object custom;
    ViewModelStore viewModelStore;
}

也就是在destory之前,viewmodelStory就已被保存了。

再回顾getViewModelStore方法,在获取viewModelStore时,新建之前,从NonConfigurationInstances的中试图获取viewmodelStory;

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

这里的nc便是上面保存的nci。

面试问题

viewModelStore何时初始化

调试过程中无意发现,就算你新建一个Activity什么都不做,也会调用到getViewModelStore方法,其原因在于viewModelStory在onCreate中就被初始化了。在FragmentActivity中:

final FragmentController mFragments = FragmentController.createController(new HostCallbacks());

protected void onCreate(@Nullable Bundle savedInstanceState) {
    mFragments.attachHost(null /*parent*/);
    ...
}

FragmentControl:

final FragmentManager mFragmentManager = new FragmentManagerImpl();

public void attachHost(@Nullable Fragment parent) {
    mHost.mFragmentManager.attachController(
            mHost, mHost /*container*/, parent);
}

FragmentManager:

void attachController(@NonNull FragmentHostCallback<?> host,
        @NonNull FragmentContainer container, @Nullable final Fragment parent) {
    if (mHost != null) throw new IllegalStateException("Already attached");
    mHost = host;
    mContainer = container;
    mParent = parent;
    if (mParent != null) {
        // Since the callback depends on us being the primary navigation fragment,
        // update our callback now that we have a parent so that we have the correct
        // state by default
        updateOnBackPressedCallbackEnabled();
    }
    // Set up the OnBackPressedCallback
    if (host instanceof OnBackPressedDispatcherOwner) {
        OnBackPressedDispatcherOwner dispatcherOwner = ((OnBackPressedDispatcherOwner) host);
        mOnBackPressedDispatcher = dispatcherOwner.getOnBackPressedDispatcher();
        LifecycleOwner owner = parent != null ? parent : dispatcherOwner;
        mOnBackPressedDispatcher.addCallback(owner, mOnBackPressedCallback);
    }

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

推荐阅读更多精彩内容