美文网首页android使用
ViewModel源码探究

ViewModel源码探究

作者: 瑜小贤 | 来源:发表于2021-10-19 23:22 被阅读0次

    一. 什么是ViewModel

    官方对ViewModel的定义:

    1. 类职责:负责为界面准备数据(意味着一切处理数据逻辑的业务代码,应该写在ViewModel中)
    2. 在配置更改期间会自动保留ViewModel对象:因此可以作为跨页面(Fragment)通讯的基石

    二. ViewModel有什么优点

    1. Activity配置更改重建时(比如屏幕旋转)保留数据
    2. UI组件(Activity与Fragment、Fragment与Fragment)间实现数据共享

    对于第一条不用VM的情况下只能通过onSaveInstanceState保存数据,当activity重建后再通过onCreate或onRestoreInstanceState方法的bundle中取出,但如果数据量较大,数据的序列化和反序列化将产生一定的性能开销。

    对于第二条如果不用VM,各个UI组件都要持有共享数据的引用,这会带来两个麻烦:①. 如果新增了共享数据,各个UI组件需要再次声明并初始化新增的共享数据;②. 某个UI组件对共享数据修改,无法直接通知其他UI组件,需手动实现观察者模式。而VM结合LiveData就可以很轻松的实现这一点。

    知道了它的优化,下面我们来看看如果使用它

    三. 如何使用

    首先写个类,继承 ViewModel

      class UserViewModel : ViewModel() {
          private var users: MutableLiveData<String>? = null
          fun getUsers(): LiveData<String>? {
            if (users == null) {
                users = MutableLiveData<String>()
                users?.value = "hello world"
          }
          return users
      }
    }
    

    然后后Activity里面使用

    class ViewModelActivity : AppCompatActivity() {
        lateinit var viewmodel: MyViewModel
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_view_model)
             setTitle("ViewModel的使用")
            //通过ViewModelProvider获取实例
            viewModel =  ViewModelProvider(this).get(UserViewModel::class.java)
            viewModel.getUsers()?.observe(this, Observer {
                Log.d("ViewModel", it)
            })
        }
    }
    

    如果引入了依赖库

    implementation "androidx.fragment:fragment-ktx:1.2.4"
    

    那么上述实例化ViewModel可以通过by关键字方便实现

    private val viewModel by viewModels<MyViewModel>()
    // Activity中如果共享的ViewModel
    private val viewModel by activityViewModels<MyViewModel>()
    

    activityViewModels是什么呢?如果Activity中有好几个Fragment,那么这几个Fragment通过activityViewModels方法拿到的ViewModel都是同一样。这样就实现了数据的共享

    当然viewModelsactivityViewModels也是通过ViewModelProvider(this).get方法创建的。所以我们看源码,也是从ViewModelProvider(this).get方法看是如果生成对象的。通过源码就可以清楚的知道为什么它会有那两个优点了

    四、调用源码分析

    • 构造方法
    public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
        this(owner.getViewModelStore(), factory);
    }
    

    ViewModelProvider的构造方法传入的是一个ViewModelStoreOwner,获取的是owner.getViewModelStore()方法,getViewModelStore返回的是一个ViewModelStore对象。而ViewModelStore对象就比较简单,里面就主要有个Map<String,ViewModel>存储ViewModel

    看到这里可以猜测,我们获取的ViewModel存储应该就是在这个ViewModelStore里面,类似于缓存一样。

    ViewModelProvider(this)方法传的是当前Activity对象,那么肯定是哪个父类继承了ViewModelStoreOwner接口。往上找发现FragmentActivity实现了这个接口

    在FragmentActivity里面有个全局变量mViewModelStore

    public class FragmentActivity extends ComponentActivity implements
            ViewModelStoreOwner,
            ActivityCompat.OnRequestPermissionsResultCallback,
            ActivityCompat.RequestPermissionsRequestCodeValidator {
        ...
        
        private ViewModelStore mViewModelStore;
        
        ...
    
        @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.");
            }
            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;
        }
        ...
    }
    

    FragmentActivity里面实现了getViewModelStore方法,这里是个关键点,可以看出来,如果mViewModelStore 为null,则去NonConfigurationInstances 里面拿,那这个getLastNonConfigurationInstance从哪里拿的?这里我们暂且不跟,先看它是如何创建ViewModel的,如果前面获取都为null,最后是直接new了一个出来。

    继续看ViewModelProvider#get方法

    public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
        String canonicalName = modelClass.getCanonicalName();
        return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
    }
    

    通过类名和前缀DEFAULT_KEY 生成一个字符串key,继续调用重载方法,从前面mViewModelStore里面拿出来一个ViewModel,如果不为空,则直接返回,可见mViewModelStore确实就是缓存用的,如果为空。则通过mFactory 创建一个。并且放入mViewModelStore

     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.
            }
        }
     //通过factory创建viewModel
        if (mFactory instanceof KeyedFactory) {
            viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
        } else {
            viewModel = (mFactory).create(modelClass);
        }
      //存入mViewModelStore
        mViewModelStore.put(key, viewModel);
        return (T) viewModel;
    }
    

    这个代码也好理解,就是从mViewModelStore里面拿对象,拿不到就让factory去生成一个。由于我们没有传入factory,这里的factory就是默认的NewInstanceFactory。而NewInstanceFactory逻辑也简单,也就是直接反射生成一个对象。

    分析到这我们发现,ViewModelProvider.get方法其实逻辑就是反射生成一个ViewMode,并存储到当前Activity的mViewModelStore中,
    那么问题来了,既然旋转屏幕,Activity会重新创建,那么它里面的变量mViewModelStore怎么还能保持是同一个?

    答案就在刚刚的FragmentActivity#getViewModelStore方法中,继续看这个方法,这里的mViewModelStore并不是重新new,而是从一个NonConfigurationInstances 对象中去拿,而这个getLastNonConfigurationInstance方法那就是这个问题的关键所在了。

      public ViewModelStore getViewModelStore() {
        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;
    }
    

    我们继续看这个
    getLastNonConfigurationInstance方法
    按ctrl键点进去,进了Activity.java里面了

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

    我们关心的是这个对象是在哪里赋值的,在attach方法里面发现了赋值的地方

     final void attach(...) {
        attachBaseContext(context);
        mFragments.attachHost(null /*parent*/);
        ....
        mLastNonConfigurationInstances = lastNonConfigurationInstances;
    }
    

    那么attach方法里面的这个lastNonConfigurationInstances又是从哪里来的呢?
    ActivityThread里面的performLaunchActivity方法,创建时会调用这个方法绑定一些重要的数据到Activity,
    现在我们知道lastNonConfigurationInstances是存在ActivityClientRecord中的。

       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.configCallback);
    
               ...
    
        return activity;
    }
    

    那么r.lastNonConfigurationInstances又是在哪里赋值的呢?
    同样的
    ActivityThread#performDestroyActivity方法中我们看到了赋值操作。如果发生了配置变化,会调用activity的retainNonConfigurationInstances方法,存入ActivityClientRecord

       ActivityClientRecord performDestroyActivity(IBinder token, 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);
                    }
                }
            }
           ...
        return r;
    }
    

    似乎离真相越来越近了,我们继续看retainNonConfigurationInstances方法

    //Activity#retainNonConfigurationInstances
    NonConfigurationInstances retainNonConfigurationInstances() {
        Object activity = onRetainNonConfigurationInstance();
        ...
        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方法,而FragmentActivity复写了这个方法,把viewModelStore 存入了NonConfigurationInstances

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

    总结

    事情的起因是我们通过ViewModelProvider去获取存放在mViewModelStore中的ViewModel。然后就在发现ViewModelProvider的构造方法要先去取ViewModelStore
    1. 如果mViewModelStore不为空,就当场返回
    2. 如果mViewModelStore为空,就去通过getLastNonConfigurationInstance()NonConfigurationInstances,再从NonConfigurationInstances中取。
    2.1 NonConfigurationInstances如果不为空,就直接返回nc.viewModelStore
    2.2 NonConfigurationInstances如果为空,再判断一遍mViewModelStore也为空【上面已经刚判断过一次】,就new一个mViewModelStore出来并返回

    那么,getLastNonConfigurationInstance()返回的NonConfigurationInstances怎么就能取出来mViewModelStore了,啥时候放进去的?
    答:是Activity#attach方法时赋值的

    那么,attach方法中拿来给mLastNonConfigurationInstances赋值的lastNonConfigurationInstances又是哪里来的?
    答:是ActivityClientRecord中保存的,在执行ActivityThread#performLaunchActivity方法时传入的ActivityClientRecord,然后方法内调用activity.attach方法,传入r.lastNonConfigurationInstances

    那么,这个r.lastNonConfigurationInstances又是什么时候赋值的?
    答:执行performDestroyActivity的时候将r.activity.retainNonConfigurationInstances()赋值给了r.lastNonConfigurationInstances

    那么,这个retainNonConfigurationInstances方法内部做了什么?
    答:它调用了onRetainNonConfigurationInstance,而FragmentActivity复写了这个方法,把viewModelStore 存入了NonConfigurationInstances

    相当于是当Activity销毁【执行performDestroyActivity】时,
    ①会把ViewModelStore存储在-->NonConfigurationInstances
    ②然后把NonConfigurationInstances 存储在-->ActivityClientRecord
    然后重启Activity【执行performLaunchActivity】时,会调用attach方法,在其中把ActivityClientRecordNonConfigurationInstances给Activity

    相关文章

      网友评论

        本文标题:ViewModel源码探究

        本文链接:https://www.haomeiwen.com/subject/vusewltx.html