Android Architecture Component之ViewModel源码分析

前言
知识准备
  • 重要知识介绍(后面用到)
    /**
         * Control whether a fragment instance is retained across Activity
         * re-creation (such as from a configuration change).  This can only
         * be used with fragments not in the back stack.  If set, the fragment
         * lifecycle will be slightly different when an activity is recreated:
         * <ul>
         * <li> {@link #onDestroy()} will not be called (but {@link #onDetach()} still
         * will be, because the fragment is being detached from its current activity).
         * <li> {@link #onCreate(Bundle)} will not be called since the fragment
         * is not being re-created.
         * <li> {@link #onAttach(Activity)} and {@link #onActivityCreated(Bundle)} <b>will</b>
         * still be called.
         * </ul>
         */
        public void setRetainInstance(boolean retain) {
            mRetainInstance = retain;
        }
    
    这个方法是fragment的方法,根据官方注释,我们知道一旦我们设置setRetainInstance(true),意味着当我们旋转屏幕的时候,activity重绘,fragment不会重绘,它将会保留。也就意味着Fragment的onCreate和onDestory方法都不会调用,这样的特性很多被用来用状态保存,数据恢复。具体是怎么样的过程,请看fragment源码分析
ViewModel是什么?
  • 定义

    ViewModel类旨在以一种有生命周期的方式存储和管理与UI相关的数据。 ViewModel类允许数据在屏幕旋转等配置变化后存活

  • 解决的问题

    Android框架控制着UI控制器(Activity或者Fragment)的生命周期,它就有可能决定销毁或者重建我们的UI控制,以响应完全不受控制的某些用户操作或设备事件。那么就会出现几个问题。

    1. 如果被销毁那么存储在其中的所有瞬态UI相关数据都将丢失.

      例如:您的应用可能会在其中一个活动中包含用户列表。 当针对配置更改重新创建活动时,新活动必须重新获取用户列表。 对于简单的数据,活动可以使用onSaveInstanceState()方法并从onCreate()的包中恢复其数据,但是这种方法仅适用于可以序列化然后反序列化的少量数据,而不适用于潜在的大量数据像用户或位图的列表。

    2. UI控制获取数据的时候经常进行异步调用,可能需要一些时间来返回。 UI控制器需要管理这些调用,并确保系统在销毁后清理它们以避免潜在的内存泄漏。

      这种管理需要大量的维护,并且在为配置更改而重新创建对象的情况下,由于对象可能不得不重新发出已经做出的调用(可能会重新请求数据),所以浪费资源.

    3. UI控制器(如活动和片段)主要用于显示UI数据,对用户操作做出反应,或处理操作系统通信(如权限请求)。

      如果要求UI控制器也负责从数据库或网络加载数据,从而增加了该类的膨胀。 为UI控制器分配过多的责任可能会导致一个类尝试单独处理应用程序的所有工作,而不是将工作委托给其他类。 通过这种方式给UI控制器分配过多的责任也使测试变得更加困难。

用法简介
  • 依赖

    compile "android.arch.lifecycle:runtime:1.0.3"
    compile "android.arch.lifecycle:extensions:1.0.0-rc1"
    annotationProcessor "android.arch.lifecycle:compiler:1.0.0-rc1"
    
  • api用法

    • 创建ViewModel
     public class MyViewModel extends ViewModel {
    private MutableLiveData<List<User>> users;
    public LiveData<List<User>> getUsers() {
        if (users == null) {
            users = new MutableLiveData<List<Users>>();
            loadUsers();
        }
        return users;
    }
    
    private void loadUsers() {
        // Do an asyncronous operation to fetch users.
    }
    }
    

    ViewModel对象在配置更改期间自动保留,以便它们保存的数据立即可用于下一个activity或fragment

    • acitivity或fragment获取数据
    public class MyActivity extends AppCompatActivity {
       public void onCreate(Bundle savedInstanceState) {
       // Create a ViewModel the first time the system calls an activity's onCreate() method.
       // Re-created activities receive the same MyViewModel instance created by the first activity.
    
       MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
       model.getUsers().observe(this, users -> {
           // update UI
       });
       }
       }
    

    如果Activity重新绘制,但是ViewModel实例并不会被销毁,而是重新从ViewModel中获取数据,等到Activity销毁(退出或者被系统杀死),框架会调用onCleared()方法,以便清理资源;

    警告:viewModel绝不能引用视图, Lifecycle或任何可能持有对活动上下文的引用的类。

    ViewModel可以包含lifeCycleObservers.例如liveData。但是ViewModel绝不能观察生命周期感知的可观察对象(LiveData对象的更改),如果ViewModel需要上下文引用,请用AndroidModelView,它含有Application的对象.

ViewModel的生命周期
image

从图中可以看出我们发现,当activity因屏幕旋转而销毁,但是ViewModel一直存在,也就是这个对象一直都在(框架核心,怎么实现源码分析会讲到),直到finished才调用clear清除内存。

Fragment之间共享数据

  • 一个Activity中两个Fragment进行通信是很常见的,想象一下,主 - 从Fragmetn的一种常见情况,其中有一个片段,用户从列表中选择一个项目,另一个片段显示所选项目的内容。这种情况从来都不是微不足道的,因为这两个片段都需要定义一些接口描述,而所有者活动必须将两者联系在一起。 此外,这两个片段必须处理其他片段尚未创建或可见的场景。

    对于这个常见的痛点,可以用ViewModel来解决,这些Fragment可以使用Activity的Scope来共享一个ViewModel,来处理通信。

     public class SharedViewModel extends ViewModel {
      private final MutableLiveData<Item> selected = new MutableLiveData<Item>();
    
      public void select(Item item) {
          selected.setValue(item);
      }
    
      public LiveData<Item> getSelected() {
          return selected;
      }
      }
    
      public class MasterFragment extends Fragment {
      private SharedViewModel model;
      public void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
          itemSelector.setOnClickListener(item -> {
              model.select(item);
          });
      }
      }
    
      public class DetailFragment extends Fragment {
      public void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
          model.getSelected().observe(this, { item ->
             // Update the UI.
          });
      }
      }
    

    注意:我们在获取ViewModelProvider时,这两个是使用的是getActivity(),而不是当前的Fragment(后面会分析源码)。所以两个Fragment用的是同一个对象。

    好处如下:

    • 对于Activity来说,他不知道这个交流,也不用管理
    • 对于Framgent,除了共同拥有这个SharedViewModel,他们之间不需要了解彼此。如果一个Framgnet消失不会影响另一个Framgent.
    • 每个Framgent都有自己独立的生命周期,不互相影响。
源码分析
  1. 我们先找到程序的入口
    ViewModelProviders.of(this).get(MyViewModel.class)
    
    1. 先看of方法

       return new ViewModelProvider(ViewModelStores.of(activity), sDefaultFactory)
      

      返回一个ViewModelProvider,需要两个参数ViewModelStore(存放ViewModel的仓库),Factory(用工厂模式创建我们的ViewModel对象)。假如说我们的Activity重新绘制了,那么这里的ViewModelProvider就是不同的实例。这个类主要的作用就是获取我们ViewModel对象。

    2. 看一下of()方法

      ViewModelStores.of(activity)
      

      ViewModelStores是干什么的呢?提供一些静态方法获取所传入activity的ViewModeStore.

       @MainThread
      public static ViewModelStore of(@NonNull FragmentActivity activity) {
          return holderFragmentFor(activity).getViewModelStore();
      }
      
    3. holderFragmentFor

      @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
      public static HolderFragment holderFragmentFor(FragmentActivity activity) {
          return sHolderFragmentManager.holderFragmentFor(activity);
      }
      
      • 我们看到是静态方法,这个方法不是ViewModelStore,而是
        HolderFragment的一个静态方法,返回一个HolderFragment对象.
      • 然后回到3处调用getViewModelStore,返回ViewModelStore。所以说HolderFragment中有一个ViewModelStore。对应关系是一个HolderFrament含有一个ViewModelStore(存放ViewModel的仓库),一个ViewModelStore存放着多个ViewModel。
      • HolderFragmentManager是属于HolderFramgent的静态内部类
    4. 在(iii)处调用了sHolderFragmentManager.holderFragmentFor

       HolderFragment holderFragmentFor(FragmentActivity activity) {
              FragmentManager fm = activity.getSupportFragmentManager();
              HolderFragment holder = findHolderFragment(fm);
              if (holder != null) {
                          return holder;
                    }
              holder = mNotCommittedActivityHolders.get(activity);
              if (holder != null) {
                          return holder;
                      }
              holder = createHolderFragment(fm);
              mNotCommittedActivityHolders.put(activity, holder);
              return holder;
          }
      
      • 首先在在Activity的supportFragmentManager中的查找,有的话就会返回

                private static HolderFragment findHolderFragment(FragmentManager manager) {
            if (manager.isDestroyed()) {
                throw new IllegalStateException("Can't access ViewModels from onDestroy");
            }
        
            Fragment fragmentByTag = manager.findFragmentByTag(HOLDER_TAG);
            if (fragmentByTag != null && !(fragmentByTag instanceof HolderFragment)) {
                throw new IllegalStateException("Unexpected "
                        + "fragment instance was returned by HOLDER_TAG");
            }
            return (HolderFragment) fragmentByTag;
        }
        
      • 没有的话在我们存放的集合中查找有的话就返回,在HolderFragmentManager集合如下

         private Map<Activity, HolderFragment> mNotCommittedActivityHolders = new HashMap<>();
        

        可以看出多个Activity可能对应一个HolderFrament

      • 没有的话就创建并放入我们的mNotCommittedActivityHolders。

        private static HolderFragment createHolderFragment(FragmentManager fragmentManager) {
                HolderFragment holder = new HolderFragment();
                fragmentManager.beginTransaction().add(holder, HOLDER_TAG).commitAllowingStateLoss();
                return holder;
            }
        

        创建Fragment对象,这是一个无ui的Fragment,==主要作用就是用ViewModelStore储存我们的ViewModel,并在Fragement的OnDestory调用ViewModelStore的clear释放所有内存==。

        有人要说,fragment对象还是要重建啊,那么ViewModel也要重建啊,因为它属于Fragment,这就用到了文章开篇讲到了setRetainInstance方法。看下源码

        public HolderFragment() {
             setRetainInstance(true);
        }
        

        在我们初始化的时候会调用setRetainInstance,这就解决这个问题了。

    5. 拿到HolderFragment对象,回到(ii)处调用getViewModelStore,拿到ViewModelStore对象。

  2. 我们拿到ViweModelStore回到(1)处,我们就去拿我们的ViewModel。看源码
    @NonNull
    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");
        }
        return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
    }
    
    • 看get(DEFAULT_KEY + ":" + canonicalName, modelClass)
        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;
    }
    
    • 首先是我们从mViewModelStore取出我们想要的ViewModel.
      有的话就返回
    • 没有的话就利用工厂模式反射生产我们所要的ViewModel对象,同时把我们的ViewModel对象放入mViewModelStore。同时返回我们的ViewModel.

总结:

核心思想就是HolderFragment调用setsetRetainInstance(true),使得HolderFragment在FragmentMannager调用FindFragmentBytag,找到的是同一个HolderFragment对象(无论Activity是否重绘),这也保证了HolderFragment中的ViewModelStore(存放我们ViewModel的地方)不被销毁,然后我们取出我们所要的ViewModel,进行数据读取。

参考

官方文档

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342