ViewModel简单使用与解析

作者: 炸山哥 | 来源:发表于2019-03-30 22:41 被阅读5次

    在开始说ViewModel之前我们先来一些我们经常要考虑问题:
    1.Activity屏幕旋转怎么处理保留和处理数据?通过onSaveInstanceState?那如果数据比较大呢?
    2.如果Acntivity/Fragment持有的后台线程在未结束之前我们按了返回,任务结束后操作UI是不是各种NullPointerException?
    3.如果我们正在执行一个很耗时的任务,我们怎样能做到在旋转屏幕的时候不中断这个任务而后能返回正解的结果呢?

    以上种种跟生命周期相关的问题都是比较常见又不得不面对的,那有没有很优雅的处理方式呢?
    有,就是ViewModel

    ViewModel官方介绍:

    ViewModel类旨在以生命周期意识的方式存储和管理与UI相关的数据。
    ViewModel类允许数据在配置更改(例如屏幕旋转)后继续存在。

    我们先来看下应用场景

    我们在竖屏状态下启动了一个耗时任务,然后翻转手机到横屏状态

    横竖屏切换

    通过图片我们看到在切换到横屏时耗时任务并没有中断,在Activity重新创建后还能收到它返回的数据。我们来看下这过程Activity生命周期的变化:


    image.png

    通过图片我们看到Activity确实已经Destroy然后又重新Cread了。然后如果我们直接按返回键退出这个Activity


    image.png
    你会看到Activity被Destroy的同时,ViewModel也跟着Clear了。
    说明我们的ViewModel不仅仅能保存数据,能还保留任务,而且对生命周期还是可感知的。

    那ViewModel是怎么做到这些的呢?我们先从简单的使用开始:

    导入ViewModel:

    //androidx的版本,包含ViewModel和LiveData
    implementation "androidx.lifecycle:lifecycle-extensions:2.0.0"
    //非androidx版本
    implementation "android.arch.lifecycle:extensions:1.1.1"
    

    创建一个继承自ViewModel的类:

    public class MViewModel extends ViewModel
    

    获取一个ViewModel实例:

    //在Activity中
    mViewModel = ViewModelProviders.of(this).get(MViewModel.class);
    //在Fragment中,传this是获取和此Fragment绑定的ViewModel,
    //传getActivity则获取到的是跟activity中的是同一个ViewModel
    mViewModel = ViewModelProviders.of(this).get(MViewModel.class);
    mViewModel = ViewModelProviders.of(getActivity).get(MViewModel.class);
    

    我们看下完整的代码

    自定义MViewModel(提前用到了LiveData可以先不用管,后面会讲到)

    public class MViewModel extends ViewModel {
    
        MutableLiveData<String> mString;
        MutableLiveData<String> msgString;
    
    
        public MutableLiveData<String> getString(){
            if(mString==null){
                mString=new MutableLiveData<>();
            }
            return mString;
        }
    
        public MutableLiveData<String> getMsgString(){
            if(msgString==null){
                msgString=new MutableLiveData<>();
            }
            return msgString;
        }
    
        public void startTask(){
            new Thread(){
                @Override
                public void run() {
                    //请求网络数据、数据库、加载大图等。
                    //如果在Activity转屏的时候取消这些任务,那恢复的时候就要重新加载,势必浪费资源
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //此处用的是LiveData,如果我们不用LiveData可能是通过EventBus之类的把数据传递出去的
                    mString.postValue("我是来自3秒后的数据");
                    super.run();
                }
            }.start();
        }
    
    
        @SuppressWarnings("WeakerAccess")
        protected void onCleared() {
            //做一些数据清理工作
            Log.e("MViewModel","onCleared");
        }
    
    }
    

    MainActivity中获取了一个ViewModel

    public class MainActivity extends AppCompatActivity {
    
        private MViewModel mViewModel;
        private TextView mTextView;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mTextView=findViewById(R.id.desc);
            mViewModel = ViewModelProviders.of(this).get(MViewModel.class);
            
           //订阅数据变化,可以暂时理解为EventBus的消息订阅
            mViewModel.getString().observe(this, new Observer<String>() {
                @Override
                public void onChanged(String s) {
                    Log.e("MainActivity", "耗时任务结束返回数据");
                    mTextView.setText(s);
                }
            });
    
            mViewModel.getMsgString().observe(this, new Observer<String>() {
                @Override
                public void onChanged(String s) {
                    Log.e("MainActivity", "Fragment1发过来的数据");
                    mTextView.setText(s);
                }
            });
    
            getSupportFragmentManager().beginTransaction().replace(R.id.container, new TestFragment(),
                    TestFragment.class.getName()).commit();
            getSupportFragmentManager().beginTransaction().replace(R.id.container2, new TestFragment2(),
                    TestFragment.class.getName()).commit();
    
            findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Log.e("MainActivity", "开始耗时任务");
                    mViewModel.startTask();
                }
            });
    
    
        }
    }
    
    

    为了说明ViewModel的作用域,我们再建一个TestFragment

    public class TestFragment extends Fragment {
    
        private static final String TAG = "TestFragment";
    
        private View view;
        private MViewModel mMViewModel;
        private Button mBtn;
        private TextView mDesc;
    
        @Nullable
        @Override
        public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
            view = inflater.inflate(R.layout.fragment_test, container, false);
            mBtn = view.findViewById(R.id.btn);
            mDesc = view.findViewById(R.id.desc);
            return view;
    
        }
    
        @Override
        public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
            super.onViewCreated(view, savedInstanceState);
            //这里传入了getActivity(),返回的是跟Activity同一个ViewModel.
            mMViewModel = ViewModelProviders.of(getActivity()).get(MViewModel.class);
            mMViewModel.getString().observe(this, new Observer<String>() {
                @Override
                public void onChanged(String s) {
                    mDesc.setText(s);
                }
            });
    
            mBtn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    mMViewModel.getMsgString().setValue("Fragment1 发送的数据");
                }
            });
        }
    
    }
    

    通过上面的代码你会发现ViewModel使用起来非常的简单,只需要继承ViewModel即可,其余的生命周期之类的就完全不用我们考虑,它自己就能处理。除此之外同一Activity内的Fragment还能不通过Activity直接进行数据交互和共享,实现了真正意义上的解耦。

    原理解析

    1创建过程解析

    1.1 ViewModelProviders.of()

    ViewModelProviders 类提供了4个静态工厂方法 of() 创建新的 ViewModelProvider 对象。

        public static ViewModelProvider of(@NonNull Fragment fragment) {
            return of(fragment, null);
        }    
    
        public static ViewModelProvider of(@NonNull FragmentActivity activity) {
            return of(activity, null);
        }
    
        public static ViewModelProvider of(@NonNull Fragment fragment, @Nullable Factory factory) {
            Application application = checkApplication(activity);
            if (factory == null) {
                factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
            }
            return new ViewModelProvider(fragment.getViewModelStore(), factory);
        }
    
        public static ViewModelProvider of(@NonNull FragmentActivity activity,
                @Nullable Factory factory) {
            Application application = checkApplication(activity);
            if (factory == null) {
                //默认的factory
                factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
            }
            return new ViewModelProvider(activity.getViewModelStore(), factory);
        }
    
    

    这里主要是ViewModelProvider、Fragemtn/Activity下的ViewModelStrore、Factory(从上你可以看出默认都是ViewModelProvider.AndroidViewModelFactory)

    1.2 activity.getViewModelStore()和fragment.getViewModelStore()

          //ViewModelStoreOwner
          public interface ViewModelStoreOwner {
              ViewModelStore getViewModelStore();
          }
    
          //activity实现ViewModelStoreOwner
         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) {
                    //需要注意的是这里,优先从NonConfigurationInstances中取出,这就是为什么Activity在转屏重建之后还能拿到原来的ViewModel的原因,后面我们看是怎么存的
                    // Restore the ViewModelStore from NonConfigurationInstances
                    mViewModelStore = nc.viewModelStore;
                }
                if (mViewModelStore == null) {
                    mViewModelStore = new ViewModelStore();
                }
            }
            return mViewModelStore;
        }
        //fragment实现ViewModelStoreOwner
        public ViewModelStore getViewModelStore() {
            if (getContext() == null) {
                throw new IllegalStateException("Can't access ViewModels from detached fragment");
            }
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
            return mViewModelStore;
        }
    

    1.3 ViewModelStrore

    我们看到上面的方法返回的都是一个ViewModelStore 对象,并且是Fragemnt/Activity下的一个成员变量。ViewModelStore 类中维护一个 Map<String, ViewModel> 对象存储已创建的 ViewModel 对象,说明一个Activity或者Fragment下可以保存多个不同的ViewModel,并提供 put() 和 get() 方法。

    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);
        }
    
        /**
         *  Clears internal storage and notifies ViewModels that they are no longer used.
         */
        public final void clear() {
            for (ViewModel vm : mMap.values()) {
                vm.onCleared();
            }
            mMap.clear();
        }
    }
    

    1.4 ViewModelProvider.Factory

    Factory 接口定义了一个创建 ViewModel 的接口 create(),ViewModelProvider 在需要时调用该方法新建 ViewModel 对象。

        public interface Factory {
            <T extends ViewModel> T create(@NonNull Class<T> modelClass);
        }
    

    Android 已经内置了2个 Factory 实现类,分别是:

    • AndroidViewModelFactory 实现类,可以创建 ViewModel 和 AndroidViewModel 子类对象。
    • NewInstanceFactory 类,只可以创建 ViewModel 子类对象。
      它们的实现都是通过反射机制调用 ViewModel 子类的构造方法创建对象。
    public static class NewInstanceFactory implements Factory {
        @Override
        public <T extends ViewModel> T create(Class<T> modelClass) {
            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);
            }
        }
    }
    

    AndroidViewModelFactory 继承 NewInstanceFactory 类,是个单例,支持创建 AndroidViewModel 子类对象。

    public static class AndroidViewModelFactory extends ViewModelProvider.NewInstanceFactory {
    
        private static AndroidViewModelFactory sInstance;
    
        public static AndroidViewModelFactory getInstance(Application application) {
            if (sInstance == null) {
                sInstance = new AndroidViewModelFactory(application);
            }
            return sInstance;
        }
    
        private Application mApplication;
    
        public AndroidViewModelFactory(Application application) {
            mApplication = application;
        }
    
        @Override
        public <T extends ViewModel> T create(Class<T> modelClass) {
            if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
                try {
                    return modelClass.getConstructor(Application.class).newInstance(mApplication);
                } catch (NoSuchMethodException e) {
                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
                } catch (IllegalAccessException e) {
                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
                } catch (InstantiationException e) {
                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
                } catch (InvocationTargetException e) {
                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
                }
            }
            return super.create(modelClass);
        }
    }
    

    1.5 ViewModelProvider

    ViewModelProviders.of()返回的是一个ViewModelProvider对象,这个类下面就两个字段

        private final Factory mFactory;
        private final ViewModelStore mViewModelStore;
    

    通过上面的解析我们知道Factory是创建ViewModel的工厂,ViewModelStore是用来存储ViewModel的,所以我们来看下其中的get方法

        public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
            ViewModel viewModel = mViewModelStore.get(key);
            if (modelClass.isInstance(viewModel)) {
                //noinspection unchecked
                return (T) viewModel;
            } else {
                //noinspection StatementWithEmptyBody
                if (viewModel != null) {
                    // TODO: log a warning.
                }
            }
            viewModel = mFactory.create(modelClass);
            mViewModelStore.put(key, viewModel);
            //noinspection unchecked
            return (T) viewModel;
        }
    

    至此,一个ViewModel便通过ViewModelProviders.of(activity/fragment).get()创建完成。

    2生命周期和保存活过程解析

    ViewModel生命周期.png

    从上图可知ViewModel跟实例化时传入的Activity/Fragment的生命周期是保持一致的,但是从文章一开始我们就说到在屏幕旋转的时候Acticity已经被Destroy掉的情况下ViewModel却依然存活并正常执行其内部的任务。原因就在于Activity的Configuration Changes。

    2.1 Configuration Change概述

    Configuration 这个类描述了设备的所有配置信息,这些配置信息会影响到应用程序检索的资源。包括了用户指定的选项(locale和scaling)也包括设备本身配置(例如input modes,screen size and screen orientation).可以在该类里查看所有影响Configuration Change 的属性。

    横竖屏切换是我们最常见的影响配置变化的因素,还有很多其他影响配置的因素有语言的更改(例如中英文切换)、键盘的可用性(这个没理解)等

    常见的引发Configuration Change的属性:
    横竖屏切换:android:configChanges="orientation"
    键盘可用性:android:configChanges="keyboardHidden"
    屏幕大小变化:android:configChanges="screenSize"
    语言的更改:android:configChanges="locale"

    在程序运行时,如果发生Configuration Change会导致当前的Activity被销毁并重新创建 ,即先调用onDestroy紧接着调用onCreate()方法。 重建的目的是为了让应用程序通过自动加载可替代资源来适应新的配置。

    2.2 ViewModelStore的存取

    当设备发生Configuration Change之后,Activity在Destroy之前会调用onRetainNonConfigurationInstance方法,FragmentActivity#onRetainNonConfigurationInstance()源码如下

       public final Object onRetainNonConfigurationInstance() {
            Object custom = onRetainCustomNonConfigurationInstance();
            //这地方是调用Fragment的处理方法,进去之后也是类似的处理方式
            FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();
    
            if (fragments == null && mViewModelStore == null && custom == null) {
                return null;
            }
    
            NonConfigurationInstances nci = new NonConfigurationInstances();
            nci.custom = custom;
            //mViewModelStore将ViewModelStrore保存
            nci.viewModelStore = mViewModelStore;
            nci.fragments = fragments;
            return nci;
        }
    

    我们可以看到此方法将ViewModelStore对象保存了下来,然后在onCreate的时候取回

    protected void onCreate(Bundle savedInstanceState) {
            //...
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null && nc.viewModelStore != null && mViewModelStore == null) {
                mViewModelStore = nc.viewModelStore;
            }
            //...
    }
    

    此外回到我们之前说的FragmentActivity#getViewModelStore你会发现也做了相应的取出操作

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

    2.3 销毁过程

    Configuration Change的过程不销毁,但是从文章开始我们发现当我们按返回键主动结束这个Activity的时候ViewModel也跟着执行onClear()了。我们来看下系统是如何区分的:
    FragmentActivity#onDestroy

        protected void onDestroy() {
            super.onDestroy();
            if (mViewModelStore != null && !isChangingConfigurations()) {
                mViewModelStore.clear();
            }
            mFragments.dispatchDestroy();
        }
    

    FragmentActivity#onDestroy

    public void onDestroy() {
            mCalled = true;
            FragmentActivity activity = getActivity();
            boolean isChangingConfigurations = activity != null && activity.isChangingConfigurations();
            if (mViewModelStore != null && !isChangingConfigurations) {
                mViewModelStore.clear();
            }
        }
    

    由此我们可以看出FragmentActivity跟Fragment的onDestroy方法中都对Configuration Change做了相应的判断。

    最后的总结

    • 通过 ViewModelProviders 创建 ViewModelProvider 对象,调用该对象的 get() 方法获取 ViewModel 对象。 当 ViewModelStore 里不存在想要的对象,ViewModelProvider 会使用 Factory 新建一个对象并存放到 ViewModelStore 里。
    • 当发生 发生 Configuration Changes 时,FragmentActivity 利用 getLastNonConfigurationInstance()、onRetainNonConfigurationInstance() 方法实现 -ViewModelStore 的保留与恢复,进而实现 ViewModel 对象的保活。
    • 当 FragmentActivity 和 Fragment 被销毁时,会根据是否发生 Configuration Changes 来决定是否销毁 ViewModel。
    • 最重要的一点是在发生 Configuration Changes时,ViewModel的存活时间是会比Activity/Fragment的存活时间要长,所以我们不能在ViewModel中持有Activity/Fragment引用,否则会产生泄漏。所以我们不能通过接口的形式(MVP)将数据返回给Activity/Fragment使用,为些官方给出的方案便是LiveData,我们将在下一篇文章中讲到。
    • 如果ViewModel中要使用到Application可以通过继承AndroidViewModel,也是通过ViewModelProviders.of(this).get(MViewModel.class)方式实例化

    示例源码地址

    相关文章

      网友评论

        本文标题:ViewModel简单使用与解析

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