美文网首页
Andorid-JetPack-ViewModel组件用法和源码

Andorid-JetPack-ViewModel组件用法和源码

作者: 信仰年輕 | 来源:发表于2021-08-11 15:06 被阅读0次

    本文目标

    理解并掌握ViewModel组件用法和原理

    1.什么是ViewModel

    • 具备宿主生命周期感知能力的数据存储组件
    • ViewModel保存的数据,在页面因配置变更导致页面销毁重建之后依然是存在的

    配置变更:横竖屏切换、分辨率调整、权限变更、系统字体样式变更...

    ViewModel是如何做到页面销毁了还能恢复期数据呢?
    其实就是ViewModel实例被保存了下来,页面重建之后获取的ViewModel是同一个

    2.基本用法

    常规用法:存储的数据,仅仅只能当页面因为配置变更导致的销毁再重建时可复用,复用的是ViewModel的实例对象整体

    public class MyGirlViewModel extends ViewModel {
    
        //定义一个对象,相当于一个用来存放数据的仓库
        private static MutableLiveData<List<Girl>> mLiveData;
    
        /**
         * 用于获取数据
         */
        public LiveData<List<Girl>> getDataList() {
            if (mLiveData == null) {
                mLiveData = new MutableLiveData<>();
                loadData();
            }
            return mLiveData;
        }
    
        private void loadData() {
            List<Girl> data = new ArrayList<>();
            data.add(new Girl(R.drawable.f1, "一星", "****"));
            data.add(new Girl(R.drawable.f2, "一星", "****"));
            data.add(new Girl(R.drawable.f3, "一星", "****"));
            data.add(new Girl(R.drawable.f4, "一星", "****"));
            data.add(new Girl(R.drawable.f5, "一星", "****"));
            data.add(new Girl(R.drawable.f6, "一星", "****"));
            data.add(new Girl(R.drawable.f7, "一星", "****"));
            data.add(new Girl(R.drawable.f8, "一星", "****"));
            data.add(new Girl(R.drawable.f9, "一星", "****"));
            data.add(new Girl(R.drawable.f10, "一星", "****"));
            //把这些数据存到仓库里
            mLiveData.setValue(data);
        }
    
        //提供一个方法来改变数据
        public void changeValue(int position, String value) {
            //把数据取出来,然后更改其中某个条目的值,在把数据塞会仓库里
            List<Girl> list = mLiveData.getValue();
            Girl girl = list.get(position);
            girl.setLike(value);
    
            mLiveData.setValue(list);
        }
    
    }
    
    public class MainActivity extends AppCompatActivity {
    
        private ListView mListView;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mListView = findViewById(R.id.listview);
            initListView();
        }
    
        private void initListView() {
            MyGirlViewModel myGirlViewModel = new ViewModelProvider(this).get(MyGirlViewModel.class);
            LiveData<List<Girl>> liveData = myGirlViewModel.getDataList();
    
            //监听数据的改变,数据改变就会调用onChanged方法
            liveData.observe(this, new Observer<List<Girl>>() {
                /**
                 * 当我们的数据发生变化的时候,我们可以在这个onChanged中进行处理
                 */
                @Override
                public void onChanged(List<Girl> girls) {
                    Log.i("yd","onChanged刷新一次");
                    mListView.setAdapter(new GirlAdapter(MainActivity.this, girls));
                }
            });
    
            mListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
                @Override
                public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
                    myGirlViewModel.changeValue(position,"哈哈哈哈哈哈哈哈哈");
                    return false;
                }
            });
    
            mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                @Override
                public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                    startActivity(new Intent(MainActivity.this,SecondActivity.class));
                }
            });
        }
    }
    

    跨页面数据共享

    public class SecondActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_second);
        }
    
        public void sendData(View view) {
            //一定要在主线程中调用
            MyGirlViewModel myGirlViewModel = new ViewModelProvider(this).get(MyGirlViewModel.class);
            myGirlViewModel.changeValue(0,"我要改变的是第1条数据呀呀呀呀呀呀呀");
    
            //必须是MainActivity 可见才能收到信息,如果不可见哪怕发送了信息也是收不到的
            finish();
        }
    }
    

    3.配置变更ViewModel复用实现原理

    准确点来说,应该是ViewModel如何做到在宿主销毁了,还能继续存在.以至于页面恢复重建后,还能接着复用
    肯定是前后获取到的是同一个ViewModel实例对象

    我们先来看下获取ViewModel实例的过程

    MyGirlViewModel myGirlViewModel = new ViewModelProvider(MainActivity.this).get(MyGirlViewModel.class);
    

    ViewModelProvider本质是从传递进去的ViewModelStore来获取实例,如果没有,则利用factory去创建

    3.1 ViewModelProvider的创建

    public class ViewModelProvider {
    
        private final Factory mFactory;
        private final ViewModelStore mViewModelStore;
    
        public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
            this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
                    ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
                    : NewInstanceFactory.getInstance());
        }
        //把ViewModelStore 和 Factory 都保持到成员变量中
        public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
            mFactory = factory;
            mViewModelStore = store;
        }
    }
    

    在创建ViewModelProvider的时候需要传参数ViewModelStoreOwner,
    我们的MainActivity是继承自顶层ComponentActivity然后实现了ViewModelStoreOwner接口的,所以我们可以直接传MainActivity,可以发现创建ViewModelProvider的时候会把ViewModelStoreFactory 都保存到成员变量中,那我们来看下ViewModelStore

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

    其实ViewModelStore中就是封装了个HashMap,用来存储ViewModel
    然后我们来看下 Factory ,该工厂就是创造ViewModel用的

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

    就是说,当我们来获取ViewModel实例的时候,如果在ViewModelStore中获取不到,就会用Factory去创造一个实例,
    ok,到这里ViewModelProvider对象已经创建完毕,接下来看ViewModelProviderget()方法

    3.2 ViewModelProvider调用get()方法

    public class ViewModelProvider {
        private static final String DEFAULT_KEY = "androidx.lifecycle.ViewModelProvider.DefaultKey";
    
        @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) {
             //1.从mViewModelStore中根据key去找ViewModel
            ViewModel viewModel = mViewModelStore.get(key);
    
             //2.判断viewModel该实例是不是我们传入的modelClass类型的
            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.
                }
            }
            //3.如果不是就通过工厂来创建一个viewModel实例
            if (mFactory instanceof KeyedFactory) {
                viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
            } else {
                viewModel = (mFactory).create(modelClass);
            }
            //4.创建完成之后存到mViewModelStore 的 map中
            mViewModelStore.put(key, viewModel);
            return (T) viewModel;
        }
    }
    

    当调用get()方法的时候,我们只是传递了一个ViewModel.class对象,就会把我们ViewModelclassName名字拼接上DEFAULT_KEY作为Key,
    这就是在ViewModelStore中存储的Key,Value是我们的ViewModel.class对象
    我们知道了ViewModel是从ViewModelStore中获取的,那既然想做到ViewModel实例的复用,那就是说ViewModelStore也要复用,关键点就在这里

    3.3 ViewModelStore是在哪里获取的?

    通过看ViewModelProvider的构造方法,我们可以发现是在owner.getViewModelStore()中获取的

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

    追进去发现,owner.getViewModelStore()这行代码是在ComponentActivity中实现的,
    也就是说ViewModelStore是在ComponentActivity中获取的

    public class ComponentActivity extends androidx.core.app.ComponentActivity implements
            LifecycleOwner,
            ViewModelStoreOwner,
            SavedStateRegistryOwner,
            OnBackPressedDispatcherOwner {
    
        //保存用来配置变更后还想保存的数据
        static final class NonConfigurationInstances {
            Object custom;
            ViewModelStore viewModelStore;
        }
    
        @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) {
                //1.viewModelStore是根据getLastNonConfigurationInstance()获取的
                NonConfigurationInstances nc =(NonConfigurationInstances) getLastNonConfigurationInstance();
                if (nc != null) {
                  //2.从NonConfigurationInstances中把viewModelStore提出来变成成员变量
                    mViewModelStore = nc.viewModelStore;
                }
                //3.如果mViewModelStore为空,则就创建一个
                if (mViewModelStore == null) {
                    mViewModelStore = new ViewModelStore();
                }
            }
            return mViewModelStore;
        }
    }
    

    ComponentActivity中的getViewModelStore方法中我们发现

    • 1.viewModelStore是根据getLastNonConfigurationInstance()获取的
    • 2.从NonConfigurationInstances中把viewModelStore提出来变成成员变量
    • 3.如果mViewModelStore为空,则就创建一个

    那这个只是ViewModelStore的获取,那我们想知道ViewModelStore是在什么地方存的呢?

    3.3 ViewModelStore是在哪里存储的?

    public class ComponentActivity extends androidx.core.app.ComponentActivity implements
            LifecycleOwner,
            ViewModelStoreOwner,
            SavedStateRegistryOwner,
            OnBackPressedDispatcherOwner {
    
        //保存用来配置变更后还想保存的数据
        static final class NonConfigurationInstances {
            Object custom;
            ViewModelStore viewModelStore;
        }
    
        //什么时候被触发呢?
        //是在`Activity`的`retainNonConfigurationInstances()`方法调用的
        @Override
        @Nullable
        public final Object onRetainNonConfigurationInstance() {
           //如果我们想在activity保存一下数据,就是说因配置变更页面被销毁了,重建的时候继续复用
           //我们就可以重写onRetainCustomNonConfigurationInstance()这个方法,然后获取的时候可以用getLastNonConfigurationInstance()
           //不过这种方式已经被废弃掉了,了解即可,现在都推荐使用ViewModel
            Object custom = onRetainCustomNonConfigurationInstance();
    
            ViewModelStore viewModelStore = mViewModelStore;
            if (viewModelStore == null) {
                NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance();
                if (nc != null) {
                    viewModelStore = nc.viewModelStore;
                }
            }
            //如果viewModelStore 为空则就直接 return 空了
            if (viewModelStore == null && custom == null) {
                return null;
            }
    
            //如果viewModelStore 不为空,就创建NonConfigurationInstances对象并把viewModelStore存进去
            NonConfigurationInstances nci = new NonConfigurationInstances();
            nci.custom = custom;
            nci.viewModelStore = viewModelStore;
            return nci;
        }
    }
    

    ViewModelStore是在ComponentActivityonRetainNonConfigurationInstance()方法中存储的.
    该方法中的onRetainCustomNonConfigurationInstance()这行代码,
    如果我们想在activity保存一下数据,就是说因配置变更页面被销毁了,重建的时候继续复用
    我们就可以重写onRetainCustomNonConfigurationInstance()这个方法,然后获取的时候可以用getLastNonConfigurationInstance()
    不过这种方式已经被废弃掉了,了解即可,现在都推荐使用ViewModel
    小总结一下:
    我们已经知道了ViewModelStore是在onRetainNonConfigurationInstance()方法中存储的,
    但是我们要知道onRetainNonConfigurationInstance()在什么情况下才会被调用,
    只有这个方法调用了,我们的ViewModelStore才能存到NonConfigurationInstances中,这才能实现复用

    3.4 onRetainNonConfigurationInstance()方法又是在哪被调用的呢?

    是在ComponentActivity的父类ActivityretainNonConfigurationInstances()方法调用的

    public class Activity extends ContextThemeWrapper
            implements LayoutInflater.Factory2,
            Window.Callback, KeyEvent.Callback,
            OnCreateContextMenuListener, ComponentCallbacks2,
            Window.OnWindowDismissedCallback,
            AutofillManager.AutofillClient, ContentCaptureManager.ContentCaptureClient {
    
        //该方法又是在哪里调用的呢?是在ActivityTread中调用的
        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;
        }
    
        public Object onRetainNonConfigurationInstance() {
            return null;
        }
    }
    

    retainNonConfigurationInstances()方法又是在哪里调用的呢?答案是在ActivityThread中被调用的,

    3.4 因配置变更重新启动一个activity,会执行ActivityThreadhandleRelaunchActivity()方法,继而调用retainNonConfigurationInstances()

    我们都知道程序启动的时候首先要调用ActivityThreadmain方法,
    如果我们正常启动一个activity的时候会调用ActivityThreadhandleLaunchActivity()方法,
    如果是因为配置变更重新启动一个activity的时候,是走的handleRelaunchActivity()方法,
    这里先告诉大家,retainNonConfigurationInstances()方法是在handleRelaunchActivity()中的handleDestroyActivity()方法中的performDestroyActivity()方法中调用的,具体源码如下

    public final class ActivityThread extends ClientTransactionHandler {
    
        final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>();
    
        public static final class ActivityClientRecord {
            //这个字段存储的就是因配置变更导致的被销毁的那个activity存留下来的数据  
           Activity.NonConfigurationInstances lastNonConfigurationInstances;
        }
    
        //正常启动一个activity会走这个方法
        @Override
        public Activity handleLaunchActivity(ActivityClientRecord r,PendingTransactionActions pendingActions, Intent customIntent) {
          ..........
        }
    
        //1.因为配置变更重新启动一个`activity`的时候会调用这个方法
        @Override
        public void handleRelaunchActivity(ActivityClientRecord tmp,PendingTransactionActions pendingActions) {
          .....
            //mActivities这个集合存储的是当前app已经打开的所有activity
            ActivityClientRecord r = mActivities.get(tmp.token);
            handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents,
                    pendingActions, tmp.startsNotResumed, tmp.overrideConfig, "handleRelaunchActivity");
          .....
       }
        //2.因为配置变更重新启动一个`activity`的时候会调用这个方法
        private void handleRelaunchActivityInner(ActivityClientRecord r, int configChanges,
                List<ResultInfo> pendingResults, List<ReferrerIntent> pendingIntents,
                PendingTransactionActions pendingActions, boolean startsNotResumed,
                Configuration overrideConfig, String reason) {
           
            final Intent customIntent = r.activity.mIntent;
      
            //重新创建一个activity之前要把发生配置变更的activity销毁
            if (!r.paused) {
                performPauseActivity(r, false, reason, null /* pendingActions */);
            }
            if (!r.stopped) {
                callActivityOnStop(r, true /* saveState */, reason);
            }
            //该方法的第4个参数getNonConfigInstance,这里面传的是true
            //经过这个方法后,ActivityClientRecord 这个r对象就包含了那个被销毁的activity所存留下来的对象
            handleDestroyActivity(r.token, false, configChanges, true, reason);
            ......
            //重新打开一个新的activity
            handleLaunchActivity(r, pendingActions, customIntent);
        }
    
       //重要方法,经过这个方法后,ActivityClientRecord 这个r对象就包含了那个被销毁的activity所存留下来的对象
        @Override
        public void handleDestroyActivity(IBinder token, boolean finishing, int configChanges,boolean getNonConfigInstance, String reason) {
            ActivityClientRecord r = performDestroyActivity(token, finishing,configChanges, getNonConfigInstance, reason);
        }
    
        //该方法是真正执行Activity的destroy方法的
        ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,int configChanges, boolean getNonConfigInstance, String reason) {
            ActivityClientRecord r = mActivities.get(token);
                 if (getNonConfigInstance) {//此处为true
                   //该行代码就会调用activity的retainNonConfigurationInstances方法,
                   //从而把Activity受到配置变更而不想丢失的数据给保存起来,那我们的viewModelStore对象也就被保存起来了
                   r.lastNonConfigurationInstances= r.activity.retainNonConfigurationInstances();
                }
            return r;
        }
    }
    
    • 1.当一个activity重建的时候就会执行ActivityThread中的handleRelaunchActivity(),然后会经过一系列调用,
      handleRelaunchActivity()--->handleDestroyActivity()--->performDestroyActivity()--->r.activity.retainNonConfigurationInstances();

    • 2.retainNonConfigurationInstances()方法,作用就是把Activity受到配置变更而不想丢失的数据给保存起来,那我们的viewModelStore对象也就被保存起来了,数据都会存在ActivityClientRecord中,其中有个字段Activity.NonConfigurationInstances lastNonConfigurationInstances,这个字段存储的就是因配置变更导致的被销毁的那个activity存留下来的数据

    • 3.当handleDestroyActivity()这个方法被调用完,我们的ActivityClientRecord 这个r对象就包含了那个被销毁的activity所存留下来的数据,最后会调用handleLaunchActivity()重新打开一个activity,接下来handleLaunchActivity()中就调用performLaunchActivity()方法

    3.5handleLaunchActivity方法创建新的Activity并启动

    public final class ActivityThread extends ClientTransactionHandler {
         //正常启动一个activity会走这个方法
        @Override
        public Activity handleLaunchActivity(ActivityClientRecord r,PendingTransactionActions pendingActions, Intent customIntent) {
          final Activity a = performLaunchActivity(r, customIntent);
        }
    
         private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
            Activity activity = null;
            try {
                java.lang.ClassLoader cl = appContext.getClassLoader();
                //1.新建一个activity
                activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
                StrictMode.incrementExpectedActivityCount(activity.getClass());
                r.intent.setExtrasClassLoader(cl);
                r.intent.prepareToEnterProcess();
                if (r.state != null) {
                    r.state.setClassLoader(cl);
                }
            } catch (Exception e) {
            }
    
           //2.执行attach方法把r.lastNonConfigurationInstances传进去了
           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);
          }
    }
    
    • 1.不难发现,在performLaunchActivity()方法中会通过newActivity()的形式创建activity
    • 2.执行attach方法把r.lastNonConfigurationInstances传进去了

    3.6Activity最终把lastNonConfigurationInstances对象保存了起来

    public class Activity{
    
       @UnsupportedAppUsage
       NonConfigurationInstances mLastNonConfigurationInstances;
    
       @UnsupportedAppUsage
        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) {
            
            //保存了起来
            mLastNonConfigurationInstances = lastNonConfigurationInstances;
        }
    
        @Nullable
        public Object getLastNonConfigurationInstance() {
            return mLastNonConfigurationInstances != null ? mLastNonConfigurationInstances.activity : null;
        }
    }
    

    所以在getLastNonConfigurationInstance()方法中能拿到mLastNonConfigurationInstances对象,那我们就能拿到我们的ViewModelStore,从而完成我们实例对象的复用

    总结

    问题1:ViewModel是如何实现因配置变更还能还原数据的呢?

    准确点来说,应该是ViewModel如何做到在宿主销毁了,还能继续存在.以至于页面恢复重建后,还能接着复用

    • 1.首先我们的ViewModel是存到ViewModelStore中的HashMap中,然后我们这个ViewModelStore是存到ComponentActivityNonConfigurationInstances静态内部类中
    • 2.当我们的Activity因为配置变更发生改变的时候,会调用ActivityThread中的handleRelaunchActivity,这里面有个 handleDestroyActivity()
      执行完这个方法后,就把Activity受到配置变更而不想丢失的数据给保存起来,那我们的viewModelStore对象也就被保存起来了
    • 3.继而继续调用handleLaunchActivity方法创建新的Activity并启动

    问题2:ViewModelonSaveIntanceState方法有什么区别呢?

    • 1.onSaveIntanceState只能存储轻量级的key-value键值对数据,非配置变更导致的页面被回收时才会触发,此时数据存储在ActivityRecord
    • 2.ViewModel可以存放任意Object数据,因配置变更导致的页面被回收才有效,此时存在ActivityThread#ActivityClientRecord

    相关文章

      网友评论

          本文标题:Andorid-JetPack-ViewModel组件用法和源码

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