文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Android Jetpack库剖析之ViewModel组件篇

2024-04-02 19:55

关注

前言

今天让我们一起去探究一下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 );
        }
        if (!r.stopped) {
            callActivityOnStop(r, true , 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 );
        }
        if (!r.stopped) {
            callActivityOnStop(r, true , 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 );
        //赋值给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内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     807人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     351人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     314人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     433人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     221人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-移动开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯