美文网首页
【Android Jetpack教程】ViewModel原理分析

【Android Jetpack教程】ViewModel原理分析

作者: 笨笨11 | 来源:发表于2021-10-13 16:11 被阅读0次

    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 {
           Object custom;
           ViewModelStore viewModelStore;
       }
    
    }
    复制代码
    

    NonConfigurationInstances是ComponentActivity的静态内部类,里面包含了ViewModelStore。getViewModelStore()内部首先尝试通过getLastNonConfigurationInstance()来获取NonConfigurationInstances,不为空直接能拿到对应的ViewModelStore;否则直接new一个新的ViewModelStore

    跟进去getLastNonConfigurationInstance()这个方法:

    //Activity.java
    public class Activity extends ContextThemeWrapper {
    
      NonConfigurationInstances mLastNonConfigurationInstances;
    
      public Object getLastNonConfigurationInstance() {
          return mLastNonConfigurationInstances != null
                ? mLastNonConfigurationInstances.activity : null;
      }
    
      NonConfigurationInstances retainNonConfigurationInstances() {
         //onRetainNonConfigurationInstance实现在子类ComponentActivity中实现
         Object activity = onRetainNonConfigurationInstance();
         .......
    
         if (activity == null && children == null && fragments == null && loaders == null
                && mVoiceInteractor == null) {
             return null;
         }
         NonConfigurationInstances nci = new NonConfigurationInstances();
         nci.activity = activity;
         ......
         return nci;
     }
    
      final void attach(Context context,
            .......,
            NonConfigurationInstances lastNonConfigurationInstances) {
            mLastNonConfigurationInstances = lastNonConfigurationInstances;
       }
    
      static final class NonConfigurationInstances {
          Object activity;
          ......
      }
    }
    复制代码
    

    可以看到getLastNonConfigurationInstance()中是通过mLastNonConfigurationInstances是否为空来判断的,搜索一下该变量赋值的地方,就找到了Activity#attach()方法。我们知道Activity#attach()是在创建Activity的时候调用的,顺藤摸瓜就可以找到了ActivityThread:

    //ActivityThread.java
    final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>();
    
    Activity.NonConfigurationInstances lastNonConfigurationInstances;
    
    //1、将NonConfigurationInstances存储到ActivityClientRecord
    ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
            int configChanges, boolean getNonConfigInstance, String reason) {
        ActivityClientRecord r = mActivities.get(token);
        Class<? extends Activity> activityClass = null;
        if (r != null) {
            ......
            if (getNonConfigInstance) {
                try {
                    //retainNonConfigurationInstances
                    r.lastNonConfigurationInstances
                            = r.activity.retainNonConfigurationInstances();
                } catch (Exception e) {
                    ......
                }
            }
        }
        synchronized (mResourcesManager) {
            mActivities.remove(token);
        }
        return r;
    }
    
    //2、从ActivityClientRecord中获取NonConfigurationInstances
    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
      ......
      activity.attach(...., r.lastNonConfigurationInstances,....);
    }
    复制代码
    
    • 在执行performDestroyActivity()的时候,会调用Activity#retainNonConfigurationInstances()方法将生成的NonConfigurationInstances赋值给lastNonConfigurationInstances。
    • 在performLaunchActivity()中又会通过Activity#attach()将lastNonConfigurationInstances赋值给Activity.mLastNonConfigurationInstances,进而取到ViewModelStore。

    ViewModelStore的存取都是间接在ActivityThread中进行并保存在ActivityClientRecord中。在Activity配置变化时,ViewModelStore可以在Activity销毁时得以保存并在重建时重新从lastNonConfigurationInstances中获取,又因为ViewModelStore提供了ViewModel,所以ViewModel也可以在Activity配置变化时得以保存,这也是为什么ViewModel的生命周期比Activity生命周期长的原因了。

    作者:小马快跑
    链接:https://juejin.cn/post/7018003137692696606
    来源:稀土掘金
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

    相关文章

      网友评论

          本文标题:【Android Jetpack教程】ViewModel原理分析

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