Android Jetpack系列之ViewModel

Posted 冬天的毛毛雨

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android Jetpack系列之ViewModel相关的知识,希望对你有一定的参考价值。

ViewModel介绍

ViewModel的定义:ViewModel旨在以注重生命周期的方式存储和管理界面相关的数据。ViewModel本质上是视图(View)与数据(Model)之间的桥梁,想想以前的MVC模式,视图和数据都会写在Activity/Fragment中,导致Activity/Fragment过重,后续难以维护,而ViewModel将视图和数据进行了分离解耦,为视图层提供数据。

ViewModel的特点:

ViewModel生命周期比Activity长

数据可在屏幕发生旋转等配置更改后继续留存。下面是ViewModel生命周期图:

可以看到即使是发生屏幕旋转,旋转之后拿到的ViewModel跟之前的是同一个实例,即发生屏幕旋转时,ViewModel并不会消失重建;而如果Activity是正常finish(),ViewModel则会调用onClear()销毁。

ViewModel中不能持有Activity引用

不要将Activity传入ViewModel中,因为ViewModel的生命周期比Activity长,所以如果ViewModel持有了Activity引用,很容易造成内存泄漏。如果想在ViewModel中使用Application,可以使用ViewModel的子类androidViewModel,其在构造方法中需要传入了Application实例:

public class AndroidViewModel extends ViewModel {
    private Application mApplication;

    public AndroidViewModel(@NonNull Application application) {
        mApplication = application;
    }

    public <T extends Application> T getApplication() {
        return (T) mApplication;
    }
}

ViewModel使用举例

引入ViewModel,在介绍Jetpack系列文章Lifecycle时已经提过一次,这里再写一下:

implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"

先看运行效果图:

页面中有两个Fragment,左侧为列表,右侧为详情,当点击左侧某一个Item时,右侧会展示相应的数据,即两个Fragment可以通过ViewModel进行通信;同时可以看到,当屏幕发生旋转的时候,右侧详情页的数据并没有丢失,而是直接进行了展示。

//ViewModelActivity.kt
class ViewModelActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.e(JConsts.VIEW_MODEL, "onCreate")
        setContentView(R.layout.activity_view_model)
    }
}

其中的XML文件:

//activity_view_model.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".viewmodel.ViewModelActivity">

    <fragment
        android:id="@+id/fragment_item"
        android:name="com.example.jetpackstudy.viewmodel.ItemFragment"
        android:layout_width="150dp"
        android:layout_height="match_parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="@+id/fragment_detail"
        app:layout_constraintTop_toTopOf="parent" />

    <fragment
        android:id="@+id/fragment_detail"
        android:name="com.example.jetpackstudy.viewmodel.DetailFragment"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        app:layout_constraintLeft_toRightOf="@+id/fragment_item"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

直接将Fragment以布局方式写入我们的Activity中,继续看两个Fragment:

//左侧列表Fragment
class ItemFragment : Fragment() {
    lateinit var mRvView: RecyclerView

    //Fragment之间通过传入同一个Activity来共享ViewModel
    private val mShareModel by lazy {
        ViewModelProvider(activity!!).get(ShareViewModel::class.java)
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val view = LayoutInflater.from(activity)
            .inflate(R.layout.layout_fragment_item, container, false)
        mRvView = view.findViewById(R.id.rv_view)
        mRvView.layoutManager = LinearLayoutManager(activity)
        mRvView.adapter = MyAdapter(mShareModel)

        return view
    }

    //构造RecyclerView的Adapter
    class MyAdapter(private val sViewModel: ShareViewModel) :
        RecyclerView.Adapter<MyAdapter.MyViewHolder>() {

        class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
            val mTvText: TextView = itemView.findViewById(R.id.tv_text)
        }

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
            val itemView = LayoutInflater.from(parent.context)
                .inflate(R.layout.item_fragment_left, parent, false)
            return MyViewHolder(itemView)
        }

        override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
            val itemStr = "item pos:$position"
            holder.mTvText.text = itemStr
            //点击发送数据
            holder.itemView.setOnClickListener {
                sViewModel.clickItem(itemStr)
            }
        }

        override fun getItemCount(): Int {
            return 50
        }
    }
}

//右侧详情页Fragment
class DetailFragment : Fragment() {
    lateinit var mTvDetail: TextView

    //Fragment之间通过传入同一个Activity来共享ViewModel
    private val mShareModel by lazy {
        ViewModelProvider(activity!!).get(ShareViewModel::class.java)
    }

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

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        mTvDetail = view.findViewById(R.id.tv_detail)
        //注册Observer并监听数据变化
        mShareModel.itemLiveData.observe(activity!!, { itemStr ->
            mTvDetail.text = itemStr
        })
    }
}

最后贴一下我们的ViewModel:

//ShareViewModel.kt
class ShareViewModel : ViewModel() {
    val itemLiveData: MutableLiveData<String> by lazy { MutableLiveData<String>() }

    //点击左侧Fragment中的Item发送数据
    fun clickItem(infoStr: String) {
        itemLiveData.value = infoStr
    }
}

这里再强调一下,两个Fragment中ViewModelProvider(activity).get()传入的是同一个Activity,从而得到的ViewModel是同一个实例,进而可以进行互相通信。

上面使用ViewModel的写法有两个好处:

屏幕切换时保存数据

  • 屏幕发生变化时,不需要重新请求数据,直接从ViewModel中再次拿数据即可。

Fragment之间共享数据

  • Activity 不需要执行任何操作,也不需要对此通信有任何了解。
  • 除了 SharedViewModel 约定之外,Fragment 不需要相互了解。如果其中一个 Fragment 消失,另一个 Fragment 将继续照常工作。
  • 每个 Fragment 都有自己的生命周期,而不受另一个 Fragment 的生命周期的影响。如果一个 Fragment 替换另一个 Fragment,界面将继续工作而没有任何问题。

ViewModel与onSaveInstance(Bundle)对比

  • ViewModel是将数据存到内存中,而onSaveInstance()是通过Bundle将序列化数据存在磁盘中
  • ViewModel可以存储任何形式的数据,且大小不限制(不超过App分配的内存即可),onSaveInstance()中只能存储可序列化的数据,且大小一般不超过1M(IPC通信数据限制)

源码解析

ViewModel的存取

我们在获取ViewModel实例时,并不是直接new出来的,而是通过ViewModelProvider.get()获取的,顾名思义,ViewModelProvider意为ViewModel提供者,那么先来看它的构造方法:

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

public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
    this(owner.getViewModelStore(), factory);
}

public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
    mFactory = factory;
    mViewModelStore = store;
}

ViewModelProvider构造函数中的几个参数:

  • ViewModelStoreOwner:ViewModel存储器拥有者,用来提供ViewModelStore
  • ViewModelStore:ViewModel存储器,用来存储ViewModel
  • Factory:创建ViewModel的工厂
private final ViewModelStore mViewModelStore;

private static final String DEFAULT_KEY =
        "androidx.lifecycle.ViewModelProvider.DefaultKey";

@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");
    }
    //首先构造了一个key,直接调用下面的get(key,modelClass)
    return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}

@MainThread
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
    //尝试从ViewModelStore中获取ViewModel
    ViewModel viewModel = mViewModelStore.get(key);

    if (modelClass.isInstance(viewModel)) {
        if (mFactory instanceof OnRequeryFactory) {
            ((OnRequeryFactory) mFactory).onRequery(viewModel);
        }
        //viewModel不为空直接返回该实例
        return (T) viewModel;
    } else {
        //noinspection StatementWithEmptyBody
        if (viewModel != null) {
            // TODO: log a warning.
        }
    }
    if (mFactory instanceof KeyedFactory) {
        viewModel = ((KeyedFactory) mFactory).create(key, modelClass);
    } else {
        //viewModel为空,通过Factory创建
        viewModel = mFactory.create(modelClass);
    }
    //将ViewModel保存到ViewModelStore中
    mViewModelStore.put(key, viewModel);
    return (T) viewModel;
}

逻辑很简单,首先尝试通过ViewModelStore.get(key)获取ViewModel,如果不为空直接返回该实例;如果为空,通过Factory.create创建ViewModel并保存到ViewModelStore中。先来看Factory是如何创建ViewModel的,ViewModelProvider构造函数中,如果没有传入Factory,那么会使用NewInstanceFactory:

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

//默认Factory的实现类NewInstanceFactory
public static class NewInstanceFactory implements Factory {
   private static NewInstanceFactory sInstance;

   //获取单例
   static NewInstanceFactory getInstance() {
       if (sInstance == null) {
          sInstance = new NewInstanceFactory();
         }
        return sInstance;
       }

      @Override
   public <T extends ViewModel> T create(@NonNull 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);
       }
   }
}

可以看到NewInstanceFactory的实现很简单,直接通过传入的class反射创建实例,泛型中限制必须是ViewModel的子类,所以最终创建的是ViewModel实例。

看完Factory,接着来看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);
        //如果oldViewModel不为空,调用oldViewModel的onCleared释放资源
        if (oldViewModel != null) {
            oldViewModel.onCleared();
        }
    }

    final ViewModel get(String key) {
        return mMap.get(key);
    }

    Set<String> keys() {
        return new HashSet<>(mMap.keySet());
    }

    //遍历存储的ViewModel并调用其clear()方法,然后清除所有ViewModel
    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.clear();
        }
        mMap.clear();
    }
}

ViewModelStore类也很简单,内部就是通过Map进行存储ViewModel的。到这里,我们基本看完了ViewModel的存取,流程如下:

ViewModelStore的存取

上面聊了ViewModel的存取,有一个重要的点没有说到,既然ViewModel的生命周期比Activity长,而ViewModel又是通过ViewModelStore存取的,那么ViewModelStore又是如何存取的呢?在上面的流程图中我们知道ViewModelStore是通过ViewModelStoreOwner提供的:

//接口ViewModelStoreOwner.java
public interface ViewModelStoreOwner {
    ViewModelStore getViewModelStore();
}

ViewModelStoreOwner中接口方法getViewModelStore()返回的既是ViewModelStore。我们上面例子获取ViewModel时是ViewModelProvider(activity).get(ShareViewModel::class.java),其中的activity其实就是ViewModelStoreOwner,也就是Activity中实现了这个接口:

//ComponentActivity.java
public class ComponentActivity extends androidx.core.app.ComponentActivity implements
        LifecycleOwner,
        ViewModelStoreOwner,.... {

    @Override
    public ViewModelStore getViewModelStore() {
      //Activity还未关联到Application时,会抛异常,此时不能使用ViewModel
      if (getApplication() == null) {
        throw new IllegalStateException("Your activity is not yet attached to the "
                    + "Application instance. You can't request ViewModel before onCreate call.");
        }
        ensureViewModelStore();
        return mViewModelStore;
    }

    void ensureViewModelStore() {
        if (mViewModelStore == null) {
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                //从NonConfigurationInstances中恢复ViewModelStore
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
    }

    @Override
    //覆写了Activity的onRetainNonConfigurationInstance方法,在Activity#retainNonConfigurationInstances()方法中被调用,即配置发生变化时调用。
    public final Object onRetainNonConfigurationInstance() {
        Object custom = onRetainCustomNonConfigurationInstance();

        ViewModelStore viewModelStore = mViewModelStore;
        if (viewModelStore == null) {
        //尝试从之前的存储中获取NonConfigurationInstance
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
          if (nc != null) {
              //从NonConfigurationInstances中恢复ViewModelStore
              viewModelStore = nc.viewModelStore;
          }
      }

     if (viewModelStore == null && custom == null) {
         return null;
     }
     //如果viewModelStore不为空,当配置变化时将ViewModelStore保存到NonConfigurationInstances中
     NonConfigurationInstances nci = new NonConfigurationInstances();
     nci.custom = custom;
     nci.viewModelStore = viewModelStore;
     return nci;
 }

    //内部类NonConfigurationInstances
    static final class NonConfigurationInstances {
  

以上是关于Android Jetpack系列之ViewModel的主要内容,如果未能解决你的问题,请参考以下文章

Android Jetpack系列之ViewModel

Android Jetpack系列之DataStore

Android Jetpack系列之MVI架构

Android Jetpack系列 之 WorkManager

Android Jetpack之AppCompat - Actionbar篇

《Android Jetpack从入门到精通+高级Jetpack强化实战》,最新Jetpack系列开发笔记开源