阅读 351

MVVM-ViewModel介绍与源码解析

一 概述

ViewModel 在Activity或者Fragment生命周期内只有一个的存储数据。ViewModel 里面的数据不会因为屏幕的旋转或者其他配置(比如切换到多窗口模式)而丢失。但是在正常的finish()或者按返回键的时候,在Activity或者Fragment走到onDestroy的时候回清除ViewModel里面的数据,避免内存泄漏。虽然屏幕旋转Activity也会走onDestroy,但是会判断是否是因为屏幕旋转而导致的。所以ViewModel是一个很合格的存储数据的类

二 ViewModel生命周期

ViewModel 对象存在的时间比Activity的生命周期要长,禁止在ViewModel的持有Activity、Fragment、View等对象,这样会引起内存泄漏。如果需要在ViewModel中引用Context的话,google为我们提供了AndroidViewModel 类来供我们使用。

三 ViewModel的好处

  1. 不会因为屏幕旋转或者其他配置改变(比如切换到多窗口模式)而销毁,避免数据重新创建,比如网络数据重新加载的情况
  2. 当Activity或者Fragment的正常销毁的时候,自动清除数据,也就是绑定了Activity或者Fragment的生命周期,避免了内存泄漏,
  3. 多个Fragment之间或者Activity和Fragment之间更好地传递数据,进行交互
  4. 减少Activityu或者Fragment的压力,Activityu或者Fragment的只是显示数据的界面,帮助Activity和Fragment处理一些数据逻辑

3.1 由于官网的例子有点老,所以写一个共享的例子

Activity 中的两个或更多 Fragment 需要相互通信是一种很常见的情况。Activity和Fragment的之间互相调用,下面是一个Activity和两个Fragment之间的互相调用

具体的demo地址github

3.1.1 构建一个Avtivity

class ViewModelDemoActivity : AppCompatActivity() {
    // 创建以Activity为维度的ViewModel
    private val viewModel: DemoViewModel by lazy { ViewModelProvider(this).get(DemoViewModel::class.java) }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
       var binding = DataBindingUtil.setContentView<ActivityViewmodelDemoBinding>(
            this,
            R.layout.activity_viewmodel_demo
        )
        viewModel.dataLive.observe(this, object : Observer<String> {
            override fun onChanged(s: String?) {
                // 当横竖屏变换时,会重新走这里,毕竟是View也都重新绘制了,只不过user里面立马有值
                tv_name.text = s
            }
        })

        tv_name.setOnClickListener{
            // activity 里面的点击去改变值
            viewModel.dataLive.value= "Activity触发的改变"
        }
        viewModel.getName()
    }
    companion object {
        fun startMe(activity: Activity) {
            activity.startActivity(Intent(activity, ViewModelDemoActivity::class.java))
        }
    }
}
复制代码

3.1.2 ViewModel的代码

class DemoViewModel : ViewModel() {
     val dataLive: MutableLiveData<String> =
         MutableLiveData<String>()


    fun getName(){
        viewModelScope.launch {
            delay(1000)
            dataLive.value = "王者荣耀"
        }
    }
}
复制代码

3.1.3 布局文件

两个Fragment是通过静态方式添加到Activity的

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">


    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:id="@+id/tv_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="#ED6E6E"
            android:textSize="30sp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <fragment
            android:id="@+id/one"
            android:name="com.nzy.mvvmsimple.viewmodel.ShareOneFragment"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="@+id/guidline"
            app:layout_constraintTop_toBottomOf="@+id/tv_name" />

        <fragment
            android:id="@+id/two"
            app:layout_constraintTop_toBottomOf="@+id/tv_name"
            android:name="com.nzy.mvvmsimple.viewmodel.ShareTwoFragment"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="@+id/guidline"
            app:layout_constraintRight_toRightOf="parent"
            />

        <androidx.constraintlayout.widget.Guideline
            android:id="@+id/guidline"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            app:layout_constraintGuide_percent="0.5" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
复制代码

3.1.4 两个 Fragment

注意: 两个Fragment获取ViewModel的维度,要以Activity为维度

class ShareOneFragment : Fragment() {
    // 创建以Activity为维度的ViewModel,注意 这里是 requireActivity() 不是 getActivity(),requireActivity()不可能为空,getActivity()有可能为空
    private val viewModel: DemoViewModel
            by lazy { ViewModelProvider(requireActivity()).get(DemoViewModel::class.java) }

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

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        // 这里传入是 viewLifecycleOwner(getViewLifecycleOwner()),而不会Fragment本身
        viewModel.dataLive.observe(viewLifecycleOwner, Observer {
            tv_name_one.text = it
        })

        // 在 FragmentOne 中去改变值
        tv_one.setOnClickListener {
            viewModel.dataLive.value = "Fragment-One,改变的值"
        }
    }

    companion object {
        @JvmStatic
        fun newInstance() =
            ShareOneFragment()
    }
}

复制代码

四 ViewModel 和 onSaveInstanceState 区别

4.1 ViewModel

  1. 缓存在内存中的,相当于本地缓存和网络缓存读写比较快
  2. 可以存较大的值,比如bitmap、大对象等等
  3. 数据绑定了Activity或者Fragment的生命周期,当Activity正常关闭的时候,都会清除ViewModel中的数据.比如
    • 调用finish()
    • 按返回键退出,
    • 用户直接杀进程或者是放后台内存不足,被回收了
  4. 在Activity旋转或者其他配置改变的时候,ViewModel里面是还有值得,不会销毁,
  5. ViewModel也可以使用本地存储,通过SavedStateHandle实现的,使用SavedStateViewModelFactory这个工厂,这里就不多介绍了,已经是正式版本了,引用是implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0

4.2 onSaveInstanceState

  1. 仅适合可以序列化再反序列化的少量数据
  2. 不适合数量可能较大的数据,如用数组或Bitmap。可以存一些简单的基本类型和简单的小对象、例如字符串,和一些id
  3. 会把数据存储到磁盘上

注意 对于复杂的大型数据,请使用 数据库 或者 SharedPreferences

五 ViewModel源码解析

下面的Fragment 和 Activity 都是androidx 包下面的,并且 lifecycle的版本是 2.2.0

5.1 源码

ViewModel的源码很少,基本上就是一个map 里面用来存储关于协程和SavedStateHandleController(暂时知道这个),onCleared() 方法是供我们自己调用,就是当Activity或者Fragmengt的关闭的时候,需要清理一些资源,比如Rxjava的流

//来存储 一些东西,当Activity 关闭的时候 会清除这里的数据
    private final Map<String, Object> mBagOfTags = new HashMap<>();
    private volatile boolean mCleared = false;

    //来存储 一些东西,当Activity 关闭的时候 自己实现去清楚一些数据,比如Rxjava中的流
    @SuppressWarnings("WeakerAccess")
    protected void onCleared() {
    }
复制代码

5.1.1 获得ViewModelProvider

private val viewModel: DemoViewModel by lazy { ViewModelProvider(this).get(DemoViewModel::class.java) }
复制代码

看ViewModelProvicder的构造方法

//创建一个ViewModelProvider , 用来创建ViewModels的,并将其保留在给定ViewModelStoreOwner的存储区中,如果owner是 HasDefaultViewModelProviderFactory 的子类,就用HasDefaultViewModelProviderFactory的工厂,像Fragment 和 ComponentActivity 都是实现了HasDefaultViewModelProviderFactory的接口 
 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;
    }
复制代码

上面默认的方法是; 用来创建ViewModels的,并将其保留在给定ViewModelStoreOwner的存储区中,如果owner是 HasDefaultViewModelProviderFactory 的子类,就用HasDefaultViewModelProviderFactory的工厂,像Fragment 和 ComponentActivity(是FragmentActivity的爹,AppCompatActivity的爷爷) 都是实现了HasDefaultViewModelProviderFactory的接口,获取到的Frctory是 SavedStateViewModelFactory。 否则就用NewInstanceFactory去创建ViewModel,比如需要我们在ViewModel中传递参数的时候,我们可以写自己的Factory继承NewInstanceFactory,走我们自己方法Create()

5.1.1.1 ComponentActivity中获取Factory的方法

public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
        // 看这个activity是否已经attache到application
        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 (mDefaultFactory == null) {
            mDefaultFactory = new SavedStateViewModelFactory(
                    getApplication(),
                    this,
                    getIntent() != null ? getIntent().getExtras() : null);
        }
        return mDefaultFactory;
    }
复制代码

5.1.1.2 Fragment中获取Factory的方法

    public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
        if (mFragmentManager == null) {
            throw new IllegalStateException("Can't access ViewModels from detached fragment");
        }
        if (mDefaultFactory == null) {
            mDefaultFactory = new SavedStateViewModelFactory(
                    requireActivity().getApplication(),
                    this,
                    getArguments());
        }
        return mDefaultFactory;
    }
复制代码

5.1.2 自己定义的Factory

使用

private val viewModel:UserViewModel by lazy { ViewModelProvider(this,UserViewModelFactory(repository)).get(UserViewModel::class.java) }
复制代码

自己定义的Factory

class UserViewModelFactory(
    private val repository: UserRepository
) : ViewModelProvider.NewInstanceFactory() {

    @Suppress("UNCHECKED_CAST")
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        // 传入一个 Repository 参数
        return UserViewModel(repository) as T
    }
}
复制代码

5.1.3 ViewModelStoreOwner: 存储ViewModel的拥有者

Fragment 和 ComponentActivity(是FragmentActivity的爹,AppCompatActivity的爷爷)都实现了 ViewModelStoreOwner 这个接口,

public interface ViewModelStoreOwner {
// ViewModelStore 是用来存储 ViewModel的实例的
    @NonNull
    ViewModelStore getViewModelStore();
}
复制代码

5.1.3.1 ComponentActivity中的获得 ViewModelStroe 的方法

 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实例,NonConfigurationInstances整个activity都装在里面了,NonConfigurationInstances是跟配置改变没有关系的一个实例。
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                // Restore the ViewModelStore from NonConfigurationInstances
                //从NonConfigurationInstances 恢复 ViewModelStore
                mViewModelStore = nc.viewModelStore;
            }
            // 如果为null,证明从来没有创建过,重新new 出来
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
        return mViewModelStore;
    }
复制代码

5.1.3.2 在看一下ComponentActivity:: onRetainNonConfigurationInstance

public final Object onRetainNonConfigurationInstance() {
        Object custom = onRetainCustomNonConfigurationInstance();

        ViewModelStore viewModelStore = mViewModelStore;
        
        if (viewModelStore == null) {
           // 如果是null,说明以前没有调用过 getViewModelStore()方法,也就是没有调用过ViewModelProvider(requireActivity()).get(DemoViewModel::class.java)的方法来获取  ViewModel。所以我们看一下最后一个的NonConfigurationInstance里面是否存在viewModelStore
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
            // 若果nc 不等于null,就证明以前存储过,所以从这里取出来
                viewModelStore = nc.viewModelStore;
            }
        }
        // custom这个返回null,子类也没重写他的方法,所以是必须是null
        if (viewModelStore == null && custom == null) {
            return null;
        }

        // 如果viewModelStore不是null,也就是说最后一个NonConfigurationInstance里面有值,直接new出来NonConfigurationInstances并赋值,返回出去
        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.custom = custom;
        nci.viewModelStore = viewModelStore;
        retu
复制代码

然后看看 NonConfigurationInstances类,是ComponentActivity的一个静态内部类,用来存储viewModelStore的,无论配置是否改变,这个类的实例不会改,里面的ViewModelStore不会消失

  static final class NonConfigurationInstances {
        Object custom;
        ViewModelStore viewModelStore;
    }
复制代码

5.1.3.3 retainNonConfigurationInstances 方法执行来存贮NonConfigurationInstances,返回结果就是NonConfigurationInstances,同时把NonConfigurationInstances存储到ActivityClientRecord中去,ActivityClientRecord是一个Activity的记录本。之后在Activity的Attach的时候,会把NonConfigurationInstances赋值给成员变量

在Activity调用onDestroy的之前会调用activity的retainNonConfigurationInstances

 NonConfigurationInstances retainNonConfigurationInstances() {
     ....
 }
复制代码

总结一下:

  • 先通过NonConfigurationInstances来拿ViewModelStore,NonConfigurationInstances是一个静态内部类,不会因为配置改变(比如屏幕旋转),而重新创建
  • 如果NonConfigurationInstances没有拿到,证明这就是个新的ViewModelStore,所以直接走创建ViewModelStore流程

5.1.4 ViewModelStore 用来存储ViewModel的容器,,里面有个HashMap,用来存储ViewModel的,存储的key下面会讲到

public class ViewModelStore {

//  这个就是存储ViewModel的Hashmap
    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());
    }

  // 清除viewmodel的里面的东西
    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.clear();
        }
        mMap.clear();
    }
}
复制代码

5.1.5 获得ViewModel

 public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
    // 获得 viewmodel这个类的全限定名
        String canonicalName = modelClass.getCanonicalName();
        if (canonicalName == null) {
            throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
        }
        // 用一个DEFAULT_KEY常量 + 一个这个类的全限定名当做key
        return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
    }
复制代码

继续看get(String,Class) 方法

 public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        // 你可以认为mViewModelStore就是个map,上面也讲了,其实它里面就是一个map来存储ViewModel的
        // 从map里面拿到 拿到这个 ViewModel
        ViewModel viewModel = mViewModelStore.get(key);
        // viewModel 这个对象是否可以转化成这个类,如果 viewModel 是null的话,这里也返回false
        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.
            }
        }
        // mFactory 如果不是自己实现的话,那就是SavedStateViewModelFactory 实现于 KeyedFactory,所以第一次拿的时候 ,走到这里
        if (mFactory instanceof KeyedFactory) {
            viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
        } else {
            viewModel = (mFactory).create(modelClass);
        }
        // 把获取到的ViewModel缓存到HashMap中
        mViewModelStore.put(key, viewModel);
        return (T) viewModel;
    }
复制代码

5.1.6 在什么时候清除ViewModel

当 ComponentActivity 的构造方法中有以下代码,当Activity走到destroy的时候清楚ViewModel,里面有个isChangingConfigurations方法,表示 是否是配置改变引起的(比如 Activity屏幕旋转),如果是就不清除。

 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();
                    }
                }
            }
        });
复制代码