Android Jetpack库剖析之ViewModel组件篇

 更新时间:2022年07月22日 11:46:53   作者:猪飞啦~  
这篇文章主要介绍了Android Jetpack架构组件 ViewModel详解,ViewModel类让数据可在发生屏幕旋转等配置更改后继续存在,ViewModel类旨在以注重生命周期的方式存储和管理界面相关的数据。感兴趣可以来学习一下

前言

今天让我们一起去探究一下ViewModel的实现原理,描述的不对或不足还请海涵,仅作为参考

ViewModel简介

ViewModel是一个可感知Activity或Fragment生命周期的一个架构组件,当视图销毁,数据也会被清除,所以它的本质就是用来存储与视图相关的数据,让视图显示控制与数据分离,即使界面配置发生改变数据也不会被销毁,通常配合LiveData使用

ViewModel用法

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding = ActivityMainBinding.inflate(LayoutInflater.from(baseContext))
        setContentView(binding.root)
        //获取ViewModel实例
        val viewModel: TextViewModel = ViewModelProvider(this).get(TextViewModel::class.java)
        //订阅数据
        viewModel.liveData.observe(this, { println(it) })
        //调用函数
        viewModel.test()
    }
    class TextViewModel : ViewModel() {
        val liveData = MediatorLiveData<String>()
        fun test() {
            liveData.postValue("Hello")
        }
    }
}

1,使用ViewModelProvider获取ViewModel实例

2,订阅VIewModel的LiveData

3,调用ViewModel的方法

构造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;
    

1,当我们创建ViewModelProvider的时候需要传入一个ViewModelStoreOwner对象,ViewModelStoreOwner是负责提供ViewModelStore对象的, 而ComponentActivity实现了这个接口,所以我们默认传当前的Activity即可

2,首先判断是否有默认的ViewModel工厂,如果没有就创建一个Factory

3,Factory是用来创建ViewModel的,ViewModelStore是用来存储ViewModel的

调用get()方法是如何构建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) {
        //通过key到ViewModelStore中取
        ViewModel viewModel = mViewModelStore.get(key);
        
        //如果取到ViewModel 代表已经创建过这个ViewModel 直接返回
        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.
            }
        }
        //通过Factor利用反射创建一个实例
        if (mFactory instanceof KeyedFactory) {
            viewModel = ((KeyedFactory) mFactory).create(key, modelClass);
        } else {
            viewModel = mFactory.create(modelClass);
        }
        //把ViewModel实例存储到ViewModelStore中
        mViewModelStore.put(key, viewModel);
        return (T) viewModel;
    }

1,当我们调用get()方法时会先获取类名,然后生成一个唯一的key

2,先通过key到ViewModelStore中取ViewModel,如果不为空代表已经创建过这个ViewModel的实例,直接返回这个实例

3,如果为空就通过工厂利用java反射机制创建一个实例,通过键值对形式保存到ViewModelStore中,返回实例,看到这里是不是对为什么多个fragment可以共享同一个ViewModel的疑问豁然开朗了,因为Fragment是依附于Activity之上的,在我们构建ViewModelProvider的时候传入同一个Activity,那么ViewModelProvider得到的ViewModelStore是同一个,在我们调用get()方法时就能通过key到ViewModelStore中取到同一个ViewModel实例,说白了就是共用Activity的ViewModel

Activity配置发生改变如何缓存ViewModelStore

ActivityThread{
    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);
        }
        //销毁Activity
        handleDestroyActivity(r.token, false, configChanges, true, reason);
        //启动Activity
        handleLaunchActivity(r, pendingActions, customIntent);
    }
}

1,当我们切换横竖屏的时候,ActivityThread会接收到RELAUNCH_ACTIVITY消息,会调用自身的handleRelaunchActivityInner(),这个方法里面有一个参数r,类型是ActivityClientRecord,我们每打开一个Activity,ActivityThread就会生成这么个对象来记录我们打开的Activity并保存起来

2,handleRelaunchActivityInner()这个方法里调用了handleDestroyActivity()方法去销毁我们的Activity

ActivityThread{
        @Override
    public void handleDestroyActivity(IBinder token, boolean finishing, int configChanges,
            boolean getNonConfigInstance, String reason) {
        //执行销毁Activity
        ActivityClientRecord r = performDestroyActivity(token, finishing,
                configChanges, getNonConfigInstance, reason);
    }
    ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
            int configChanges, boolean getNonConfigInstance, String reason) {
        //通过token获取Activity的记录
        ActivityClientRecord r = mActivities.get(token);
        if (r != null) {
            //是否需要获取配置实例
            if (getNonConfigInstance) {
                try {
                    //调用Activity的retainNonConfigurationInstances方法获取配置实例
                    r.lastNonConfigurationInstances
                            = r.activity.retainNonConfigurationInstances();
                } catch (Exception e) {
                }
            }
            r.setState(ON_DESTROY);
        }
        //通过token移除这条Activity的记录
        synchronized (mResourcesManager) {
            mActivities.remove(token);
        }
        return r;
    }
}

3,handleDestroyActivity()则直接调用了performDestroyActivity()方法去销毁Activity,核心部分就是调用了Activity的retainNonConfigurationInstances()方法获取了配置实例并复制给了ActivityClientRecord,把NonConfigurationInstances实例保存起来

Activity{
    NonConfigurationInstances retainNonConfigurationInstances() {
        //获取NonConfigurationInstances对象
        Object activity = onRetainNonConfigurationInstance();
        HashMap<String, Object> children = onRetainNonConfigurationChildInstances();
        FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();
        mFragments.doLoaderStart();
        mFragments.doLoaderStop(true);
        ArrayMap<String, LoaderManager> loaders = mFragments.retainLoaderNonConfig();
        //创建NonConfigurationInstances实例
        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;
    }
}
ComponentActivity{
    @Override
    @Nullable
    public final Object onRetainNonConfigurationInstance() {
        Object custom = onRetainCustomNonConfigurationInstance();
        //获取ViewModelStore
        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实例
        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.custom = custom;
        nci.viewModelStore = viewModelStore;
        return nci;
    }
}

4, 通过查阅源码,一层一层的往下剖析,ActivityThread是通过这样的调用链来获取我们的ViewModelStore实例并保存在ActivityClientRecord中的

Activity重建后如何恢复ViewModelStore

ActivityThread{
    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);
        }
        //销毁Activity
        handleDestroyActivity(r.token, false, configChanges, true, reason);
        //启动Activity
        handleLaunchActivity(r, pendingActions, customIntent);
    }
}

1,当handleDestroyActivity执行完毕就已经把ViewModelStore的实例获取到并存放到ActivityClientRecord中,此时就开始执行handleLaunchActivity()方法来启动activity

2,handleLaunchActivity()这个方法也需要ActivityClientRecord这个参数,而此时ActivityClientRecord这个对象正是经过handleDestroyActivity()这个方法获取并保存了ViewModelStore 实例的对象

3,handleLaunchActivity()则调用了performLaunchActivity()方法来启动Activity

ActivityThread{
    @Override
    public Activity handleLaunchActivity(ActivityClientRecord r,
            PendingTransactionActions pendingActions, Intent customIntent) {
        // Make sure we are running with the most recent config.
        handleConfigurationChanged(null, null);
        //去启动Activity
        final Activity a = performLaunchActivity(r, customIntent);
        return a;
    }
    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        try {
            Application app = r.packageInfo.makeApplication(false, mInstrumentation);
            if (activity != null) {
                CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
                Configuration config = new Configuration(mCompatConfiguration);
                if (r.overrideConfig != null) {
                    config.updateFrom(r.overrideConfig);
                }
                if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
                        + r.activityInfo.name + " with config " + config);
                Window window = null;
                if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
                    window = r.mPendingRemoveWindow;
                    r.mPendingRemoveWindow = null;
                    r.mPendingRemoveWindowManager = null;
                }
                appContext.setOuterContext(activity);
                //调用当前打开的Activity的attach()方法
                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.configCallback,
                        r.assistToken);
                r.activity = activity;
            }
            r.setState(ON_CREATE);
            //保存这条Activity记录
            synchronized (mResourcesManager) {
                mActivities.put(r.token, r);
            }
        } catch (SuperNotCalledException e) {
            throw e;
        } catch (Exception e) {
        }
        return activity;
    }
}

4,通过代码发现performLaunchActivity调用了当前正在打开的Activity的attach方法,而这个方法需要一个NonConfigurationInstances类型的参数,这个参数里面就有我们的ViewModelStore实例

Activity{
     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) {
        attachBaseContext(context);
        mFragments.attachHost(null /*parent*/);
        //赋值给mLastNonConfigurationInstances
        mLastNonConfigurationInstances = lastNonConfigurationInstances;
        setAutofillOptions(application.getAutofillOptions());
        setContentCaptureOptions(application.getContentCaptureOptions());
    }
    public Object getLastNonConfigurationInstance() {
        return mLastNonConfigurationInstances != null
                ? mLastNonConfigurationInstances.activity : null;
    }
}
ComponentActivity{
    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) {
            //先去lastNonConfigurationInstance中取,没有再创建
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                // Restore the ViewModelStore from NonConfigurationInstances
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
        return mViewModelStore;
    }
}

5,在attach方法里就把这个参数赋值给mLastNonConfigurationInstances,当我们获取ViewModelStore实例的时候,就会先去mLastNonConfigurationInstances中取,如果没有再自己创建一个ViewModelStore实例,到这里整个调用链就搞明白了

生命周期绑定

ComponentActivity{
    public ComponentActivity(){
        //订阅生命周期,当生命周期==ON_DESTROY,清除ViewModel数据
        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();
                    }
                }
            }
        });
    }
}
ViewModelStore{
    public final void clear() {
        //遍历所有ViewModel并调用其clear()方法清空数据
        for (ViewModel vm : mMap.values()) {
            vm.clear();
        }
        //清空所有ViewModel
        mMap.clear();
    }
}

总结

1,ComponentActivity实现了ViewModelStoreOwner接口并实现了其抽象方法getViewModelStore()

2,我们通过ViewModelProvider使用默认工厂创建了ViewModel,通过唯一key值进行标识并保存到ViewModelStore中

3,当切换横竖屏的时候ActivityThread接收到RELAUNCH_ACTIVITY消息,就会调用Activity的retainNonConfigurationInstances()方法获取我们的ViewModelStore实例并保存起来

4,当Activity启动并调用attach()方法时就将切换横竖屏前的ViewModel恢复过来

5,当我们获取ViewModelStore实例的时候会调用先getLastNonConfigurationInstance()方法去取ViewModelStore,如果为null就会重新创建ViewModelStore并存储在全局中

6,当生命周期发生改变,并且状态为ON_DESTROY,清除ViewModel数据以及实例

到此这篇关于Android Jetpack库剖析之ViewModel组件篇的文章就介绍到这了,更多相关Android ViewModel内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Android开发案例手册Application跳出dialog

    Android开发案例手册Application跳出dialog

    这篇文章主要为大家介绍了Android开发案例手册Application跳出dialog,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-06-06
  • 详解MVP模式在Android开发中的应用

    详解MVP模式在Android开发中的应用

    MVP是MVC衍生而来的,很早以前就由某软公司提出,近年来在Android应用开发中越来越多的被提及,越来越重要了。这篇文章主要介绍了详解MVP模式在Android开发中的应用,有兴趣的可以了解一下。
    2016-11-11
  • Android实现文字动态高亮读取进度效果

    Android实现文字动态高亮读取进度效果

    这篇文章主要为大家详细介绍了Android实现文字动态高亮读取进度效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-05-05
  • Android UI实现SlidingMenu侧滑菜单效果

    Android UI实现SlidingMenu侧滑菜单效果

    这篇文章主要为大家详细介绍了Android UI实现SlidingMenu侧滑菜单效果,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-12-12
  • AndroidStudio实现能在图片上涂鸦程序

    AndroidStudio实现能在图片上涂鸦程序

    这篇文章主要为大家详细介绍了AndroidStudio实现能在图片上涂鸦程序,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-05-05
  • RecyclerView实现仿支付宝应用管理

    RecyclerView实现仿支付宝应用管理

    这篇文章主要为大家详细介绍了RecyclerView实现仿支付宝应用管理的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-04-04
  • Android 进度条使用详解及示例代码

    Android 进度条使用详解及示例代码

    本文主要介绍Android 进度条的知识,这里整理了相关资料及实现示例代码,有需要的小伙伴可以参考下
    2016-09-09
  • Android利用软引用和弱引用避免OOM的方法

    Android利用软引用和弱引用避免OOM的方法

    Java从JDK1.2版本开始,就把对象的引用分为四种级别,从而使程序能更加灵活的控制对象的生命周期。这四种级别由高到低依次为:强引用、软引用、弱引用和虚引用。本文给大家介绍Android利用软引用和弱引用避免OOM,需要的朋友一起学习吧
    2016-04-04
  • Android仿今日头条滑动页面导航效果

    Android仿今日头条滑动页面导航效果

    这篇文章主要为大家详细介绍了Android仿今日头条滑动页面导航效果的相关资料,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-01-01
  • Android如何实现年月选择器功能

    Android如何实现年月选择器功能

    这篇文章主要介绍了Android如何实现年月选择器功能,帮助大家更好的理解和学习使用Android,感兴趣的朋友可以了解下
    2021-03-03

最新评论