ViewModel简介

ViewModel综述
ViewModel是Google Jetpack组件中的一员,注重以生命周期的方式存储和管理UI界面相关的数据,防止内存泄漏。
另外可以让数据在发生配置更改(如屏幕旋转后)仍然存在,优于onSaveInstanceState()+onCreate()方式,因为后者只适合可以序列化的
少量数据,不适合大的数据,如列表等。
ViewModel相关的类
  1. ViewModel.java类:类,实现数据保持相关功能,并且在Activity销毁(非配置更改(如屏幕旋转))的时候随之销毁。
  2. AndroidViewModel.java类:类,ViewModel的子类,持有application对象,可以调用系统服务等。
  3. ViewModelProvider.java类:Provider类,通过get方法获取相关ViewModel对象。
  4. ViewModelStore.java类:管理页面(Activity、Fragment相关的ViewModel),内部HashMap实现。
  5. ViewModelStoreOwner.java类:接口,内部只有一个方法getViewModelStore,类似于LifecycleOwner,ComponentActivity实现了该接口。
  6. ViewModelProviders.java类:类,对外提供的工具类,通过of方法传入Fragment或者FragmentActivity等获取ViewModelProvider对象。

ViewModel使用

依赖引入:
implementation 'androidx.appcompat:appcompat:1.1.0'
	 implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'

不引入第二行则ViewModelProviders类找不到

简单使用
  1. 新建MyViewModel继承自ViewModel或者AndroidViewModel,新建MutableLiveData保持数据,数据为Student对象,并且启动线程休眠1s模拟网络数据加载,加载完成之后通过MutableLiveData的post方法传输数据,代码如下:
class MyViewModel:ViewModel() {

   private val student: MutableLiveData<Student> by lazy {
       MutableLiveData<Student>().also{
           loadStudent()
       }
   }

   private fun loadStudent(){
       //加载数据
       Thread(
           Runnable {
               Thread.sleep(1000)
               student.postValue(Student("小美女",5))
           }
       ).start()
   }

   fun getStudent():LiveData<Student>{
       return student
   }

}
  1. 通过ViewModelProviders的of方法以及ViewModelProvider的get方法获取MyViewModel的对象。
  2. 通过MyViewModel的对象获取LiveData对象并进行observe监听注册。
    代码如下:
class TestActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_test_view_model)

        val model = ViewModelProviders.of(this)[MyViewModel::class.java]
        model.getStudent().observe(this, Observer{ student ->
            Log.d(Common.TAG,"name:${student.name}")
            Log.d(Common.TAG,"age:${student.age}")
        })
    }

}

1、运行日志如下,表示在网络加载完成的时候收到数据,在此处更新UI即可:

android ViewModel中更新Ui android viewmodel入门_jetpack


2、屏幕转成横屏,再转回来,日志如下,表示实现了数据保持:

1)进入页面的时候延迟1s收到数据

2)转成横屏马上收到数据

3)转回到竖屏马上收到数据

android ViewModel中更新Ui android viewmodel入门_android_02


3、进入页面,马上退出,则不会收到数据,表示监听到了生命周期,实现了以生命周期的方式存储和管理UI界面相关的数据,防止内存泄漏。

实现同一个FragmentActivity之间的多个Fragment与Activity的数据共享与交互

1、创建MyFragment和MyFragment2如下:

class MyFragment : Fragment() {
    
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_test_view_model, container, false)
    }
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        val model = ViewModelProviders.of(activity!!)[MyViewModel::class.java]
        model.getStudent().observe(this, Observer { student ->
            Log.d(Common.TAG, "MyFragment->name:${student.name}")
            Log.d(Common.TAG, "MyFragment->age:${student.age}")
            tv_name.text = student.name
            val age = Integer.toString(student.age)
            tv_age.text = age
        })
        tv_action.setOnClickListener {
            val liveData: MutableLiveData<Student> = model.getStudent() as MutableLiveData<Student>
            liveData.value = Student("小可爱", 6)
        }
    }
}

class MyFragment2 :Fragment() {
    
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment2_test_view_model,container,false)
    }

   
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val model = ViewModelProviders.of(activity!!)[MyViewModel::class.java]
        model.getStudent().observe(this, Observer{ student ->
            Log.d(Common.TAG,"MyFragment2->name:${student.name}")
            Log.d(Common.TAG,"MyFragment2->age:${student.age}")
            tv_name.text = student.name
            val age = Integer.toString(student.age)
            tv_age.text= age
        })
        tv_action.setOnClickListener {
            val liveData: MutableLiveData<Student> = model.getStudent() as MutableLiveData<Student>
            liveData.value = Student("小奶狗", 22)
        }
    }

}
  1. 添加fragment到activity中,并在activity中添加代码如下:
class TestActivity : AppCompatActivity() {


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_test_view_model)

        val model = ViewModelProviders.of(this)[MyViewModel::class.java]
        model.getStudent().observe(this, Observer{ student ->
            Log.d(Common.TAG,"name:${student.name}")
            Log.d(Common.TAG,"age:${student.age}")
            tv_name.text = student.name
            val age = Integer.toString(student.age)
            tv_age.text= age
        })
        tv_action.setOnClickListener {
            val liveData:MutableLiveData<Student> = model.getStudent() as MutableLiveData<Student>
            liveData.value = Student("小帅哥",8)
        }
    }

}
  1. 运行日志如下:
    1)直接启动 在ViewModel中延迟加载,fragment和Activity都收到了消息并更新了UI

    2)点击Activity中的按钮,fragment和Activity都收到了消息并更新了UI

    3)分别点击两个fragment中的按钮,fragment和Activity都收到了消息并更新了UI

注意

  1. ViewModel必须有无参构造器,如果没有则会抛出异常,代码如下:
/**
 *  @author Simple
 *  @date 2020/1/7
 *  @description :
 **/
class MyViewModel constructor(name:String): ViewModel() {

    private val student: MutableLiveData<Student> by lazy {
        MutableLiveData<Student>().also{
            loadStudent()
        }
    }

    private fun loadStudent(){
        //加载数据
        Thread(
            Runnable {
                Thread.sleep(1000)
                student.postValue(Student("小美女",5))
            }
        ).start()
    }

    fun getStudent():LiveData<Student>{
        return student
    }

}

运行则会报错如下,孵化器报错了,这跟启动过程有关,这里不做深入:

android ViewModel中更新Ui android viewmodel入门_viewmodel_03


android ViewModel中更新Ui android viewmodel入门_jetpack_04


继续深入找到报错的地方1,往上一步步追错误2,一直到ViewModelProvider中,可以看到代码如下,在此处直接调用了ViewModel的无参构造器,而ViewModel并没有无参构造器:

/**
     * Simple factory, which calls empty constructor on the give class.
     */
    public static class NewInstanceFactory implements Factory {

        @SuppressWarnings("ClassNewInstance")
        @NonNull
        @Override
        public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
            //noinspection TryWithIdenticalCatches
            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);
            }
        }
    }

至于为什么newInstance会抛出InstantiationException ,newInstance为native方法,但在newInstance的注释中就能找到答案如下,很明确了吧。

android ViewModel中更新Ui android viewmodel入门_fragment数据共享_05


2、继承自AndroidViewModel,则必须有一个构造器只有application一个参数,否则也会报错,和1类似(内部调用了此构造器,构造器不匹配),这一点在AndroidViewModel的注释中已有说明,如下:

android ViewModel中更新Ui android viewmodel入门_fragment数据共享_06


3、在实现数据交互与共享时候注意fragment中的代码,of方法传入的必须是activity对象,如果传this则各管各的,不能实现数据的共享

val model = ViewModelProviders.of(activity!!)[MyViewModel::class.java]

ViewModel的回收

FragmentActivity中的回收
ViewModel实现了根据生命周期保存和管理UI数据,那究竟是如何回收的呢?
从AppCompatActivity继承关系一路追进入到ComponentActivity,可以看到ComponentActivity的构造函数有如下代码实现监听,在
Activity销毁(非配置更改(如屏幕旋转))的时候调用ViewModelStore的clear方法
public ComponentActivity() {
        ...
        	省略部分代码
        ...
        }
        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的clear方法,代码如下,遍历了该ViewModelStore持有的所有的ViewModel ,调用ViewModel 的clear方法并且清空Map。

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

ViewModel的clear方法如下,可以看出进行了协程相关的清除操作,以及调用了一个空方法onCleared,我们可以在这个方法里面实现一些资源的释放工作。

@MainThread
    final void clear() {
        mCleared = true;
        // Since clear() is final, this method is still called on mock objects
        // and in those cases, mBagOfTags is null. It'll always be empty though
        // because setTagIfAbsent and getTag are not final so we can skip
        // clearing it
        if (mBagOfTags != null) {
            synchronized (mBagOfTags) {
                for (Object value : mBagOfTags.values()) {
                    // see comment for the similar call in setTagIfAbsent
                    closeWithRuntimeException(value);
                }
            }
        }
        onCleared();
    }
Fragment中的回收

从Fragment的getViewModelStore方法一路追进去,最终到FragmentManagerViewModel的getViewModelStore方法
代码如下:

Fragment.java
	 @NonNull
    @Override
    public ViewModelStore getViewModelStore() {
        if (mFragmentManager == null) {
            throw new IllegalStateException("Can't access ViewModels from detached fragment");
        }
        return mFragmentManager.getViewModelStore(this);
    }
 FragmentManagerImp.java
 	    @NonNull
    ViewModelStore getViewModelStore(@NonNull Fragment f) {
        return mNonConfig.getViewModelStore(f);
    }
FragmentManagerViewModel.java
    @NonNull
    ViewModelStore getViewModelStore(@NonNull Fragment f) {
        ViewModelStore viewModelStore = mViewModelStores.get(f.mWho);
        if (viewModelStore == null) {
            viewModelStore = new ViewModelStore();
            mViewModelStores.put(f.mWho, viewModelStore);
        }
        return viewModelStore;
    }

已知销毁的时候调用了ViewModelStore 的clear方法,所以在FragmentManagerViewModel中找到方法
clearNonConfigState调用了ViewModelStore.clear方法,FragmentManagerViewModel#clearNonConfigState方法只有在
FragmentManagerImp中调用了,如下:

FragmentManagerViewModel#clearNonConfigState
	    void clearNonConfigState(@NonNull Fragment f) {
        if (FragmentManagerImpl.DEBUG) {
            Log.d(FragmentManagerImpl.TAG, "Clearing non-config state for " + f);
        }
        // Clear and remove the Fragment's child non config state
        FragmentManagerViewModel childNonConfig = mChildNonConfigs.get(f.mWho);
        if (childNonConfig != null) {
            childNonConfig.onCleared();
            mChildNonConfigs.remove(f.mWho);
        }
        // Clear and remove the Fragment's ViewModelStore
        ViewModelStore viewModelStore = mViewModelStores.get(f.mWho);
        if (viewModelStore != null) {
        	//清除并且移除管理
            viewModelStore.clear();
            mViewModelStores.remove(f.mWho);
        }
    }
FragmentManagerImp中的调用
	...
		省略
	...
	if (beingRemoved || mNonConfig.shouldDestroy(f)) {//是否销毁 shouldDestroy
        boolean shouldClear;
         if (mHost instanceof ViewModelStoreOwner) {
             shouldClear = mNonConfig.isCleared();
         } else if (mHost.getContext() instanceof Activity) {
              Activity activity = (Activity) mHost.getContext();
              shouldClear = !activity.isChangingConfigurations();
         } else {
              shouldClear = true;
          }
          if (beingRemoved || shouldClear) {
            mNonConfig.clearNonConfigState(f);//清除
        }
        f.performDestroy();
        dispatchOnFragmentDestroyed(f, false);                                                                                                                                                                                                                                                                                                                         
    }

源码

总结

1、关于Jetpack中的ViewModel就介绍到此处。
2、自勉,要多思考多解决问题,想到问题不要忽略直接跳过 ,多思考多查源码,收获是不一样的:
	如上述的ViewHolder是怎么回收的。
	如fragment间的数据共享等,不要只停留在简单的使用上。
3、自勉,看源码的时候也要注意注释,很多的问题已经在注释中说的很清楚了:
	如上述注意事项中的1和2
4、自勉,问题多思考多验证,遇到问题找根源:
	如上述注意事项中的3