7. Android jetpack全家桶, 三剑客之ViewModel轻松掌握

目录:
1. ViewModel是解决什么问题的?
2. ViewModel的特点
3. ViewModel的特点1生命周期
4. ViewModel的特点2不持有UI层引用
5. ViewModel的特点3.Fragment间数据共享
6. ViewModel的使用
7. ViewModel原理源码分析(ViewModel为什么在旋转屏幕后不会丢失状态)
8. ViewModel对比onSaveInstanceState()
9. ViewModel内存泄露

viewmodel.jpg

ViewModel相关的问 题:

1.ViewModel为什么在旋转屏幕后不会丢失状态

2.ViewModel的原理,为什么可以在Activity销毁后保存数据

3.ViewModel怎么实现自动处理生命周期?ViewModel的生命周期是怎么样的?

4.为什么不同的Fragment使用相同的Activity对象来获取ViewModel,可以轻易的实现ViewModel共享?

5.ViewModel在Activity初始化与在Fragment中初始化,有什么区别?

6.ViewModel是如何创建出来的?

7.ViewModel是怎么实现双向数据绑定的。这个vm指的是mvvm里面的?

1. ViewModel是解决什么问题的?

在详细介绍ViewModel前,先来看下背景和问题点。

1). 数据持久保持: Activity可能会在某些场景(例如屏幕旋转)销毁和重新创建界面,那么存储在其中的界面相关数据都会丢失。例如,界面含用户信息列表,因配置更改而重新创建 Activity 后,新 Activity 必须重新请求用户列表,这会造成资源的浪费。能否直接恢复之前的数据呢?对于简单的数据,Activity 可以使用 onSaveInstanceState() 方法保存 然后从 onCreate() 中的Bundle恢复数据,但此方法仅适合可以序列化再反序列化的少量数据(IPC对Bundle有1M的限制),而不适合数量可能较大的数据,如用户信息列表或位图。 那么如何做到 因配置更改而新建Activity后的数据恢复呢?

2). 内存泄露: UI层(如 Activity 和 Fragment)经常需要通过逻辑层(如MVP中的Presenter)进行异步请求,可能需要一些时间才能返回结果,如果逻辑层持有UI层应用(如context),那么UI层需要管理这些请求,确保界面销毁后清理这些调用以避免潜在的内存泄露,但此项管理需要大量的维护工作。 那么如何更好的避免因异步请求带来的内存泄漏呢?

这时候ViewModel就闪亮出场了——ViewModel用于代替MVP中的Presenter,为UI层准备数据,用于解决上面两个问题。

  • ViewModel : 从界面控制中分离出视图数据所有权的操作更容易且更高效。
  • Activity 的 onSaveInstanceState() 方法从 onCreate() 捆绑包恢复其数据,仅限支持序列化的少量数据。
  • ViewModel 非常适合在用户正活跃地使用应用时存储和管理界面相关数据。它支持快速访问界面数据,并且有助于避免在发生旋转、窗口大小调整和其他常见的配置变更后从网络或磁盘中重新获取数据。

2. ViewModel的特点

ViewModel可以这么理解: 它是介于View(视图)和Model(数据模型)之间的一个东西。它起到了桥梁的作用,使视图和数据既能分离,也能保持通信

  1). 保存数据 ! ViewModel 主要用于存储 UI 数据

  2). 自动管理: 生命周期感知的数据。

  3). **共享 ViewModel 定义, **Fragment 共享数据

3.生命周期长于Activity

Android中的ViewModel是一个可以用来存储UI相关的数据的类。ViewModel的生命周期会比创建它的Activity、Fragment的生命周期长

看到在因屏幕旋转而重新创建Activity后,ViewModel对象依然会保留。 只有Activity真正Finish的时ViewModel才会被清除。

也就是说,因系统配置变更Activity销毁重建,ViewModel对象会保留并关联到新的Activity。而Activity的正常销毁(系统不会重建Activity)时,ViewModel对象是会清除的。

生命周期.jpg

4. 不持有UI层引用

问题: viewModel怎么实现自动处理生命周期?

我们知道,在MVP的Presenter中需要持有IView接口来回调结果给界面。

而ViewModel是不需要持有UI层引用的,那结果怎么给到UI层呢?答案就是使用上一篇中介绍的基于观察者模式的LiveData。 并且,ViewModel也不能持有UI层引用,因为ViewModel的生命周期更长。

所以,ViewModel不需要也不能 持有UI层引用,那么就避免了可能的内存泄漏,同时实现了解耦。这就解决了第二个问题。

5. ViewModel的特点3之Fragment间数据共享

Activity 中的多个Fragment需要相互通信是一种很常见的情况。假设有一个ListFragment,用户从列表中选择一项,会有另一个DetailFragment显示选定项的详情内容。在之前 你可能会定义接口或者使用EventBus来实现数据的传递共享。

在上面ViewMoel的生命周期也提到了 ViewModel只会在Activity存活时,只会创建一次,因此在同一个Activity中可以在多个Fragment中共享ViewModel中数据。

现在就可以使用 ViewModel 来实现。这两个 Fragment 可以使用其 Activity 范围共享 ViewModel 来处理此类通信,如以下示例代码所示:

//ViewModel
public class SharedViewModel extends ViewModel {
//被选中的Item
    private final MutableLiveData<UserContent.UserItem> selected = new MutableLiveData<UserContent.UserItem>();

    public void select(UserContent.UserItem user) {
        selected.setValue(user);
    }
    public LiveData<UserContent.UserItem> getSelected() {
        return selected;
    }
}

//ListFragment
public class MyListFragment extends Fragment {
   ...
    private SharedViewModel model;
   ...
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        //获取ViewModel,注意ViewModelProvider实例传入的是宿主Activity
        model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
        adapter.setListner(new MyItemRecyclerViewAdapter.ItemCLickListner(){
            @Override
            public void onClickItem(UserContent.UserItem userItem) {
                model.select(userItem);
            }
        });
    }
}

//DetailFragment
public class DetailFragment extends Fragment {

    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        TextView detail = view.findViewById(R.id.tv_detail);
        //获取ViewModel,观察被选中的Item
        SharedViewModel model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
        model.getSelected().observe(getViewLifecycleOwner(), new Observer<UserContent.UserItem>() {
            @Override
            public void onChanged(UserContent.UserItem userItem) {
                //展示详情
                detail.setText(userItem.toString());
            }
        });
    }
}

问题: ViewModel在Activity初始化与在Fragment中初始化,有什么区别?

共享的基础:

Activity和Fragment创建ViewModel时的区别在于:

分别会创建一个ViewModelProvider对象,这个不同的ViewModelProvider对象中又封装了相同的ViewModelStore对象和factory对象,

通过相同的ViewModelStore,又会获取到相同的ViewModel对象,这也是Activity和Fragment通过ViewModel通讯的基础。

生命周期不一样,ViewModel和Activity绑定过程:

                        ViewModel和Fragment绑定过程:

问题: ViewModel 如何实现不同的作用域

ViewModel 内部会为不同的 ViewModel 宿主分配不同的 ViewModelStore 映射表,不同宿主是从不同的数据源来获取 ViewModel 的实例,因而得以区分作用域。

6. ViewModel的使用

6.1 ViewModel 的创建三种方式

方法 1: ViewModelProvider 是创建 ViewModel 的工具类

方法 2: 使用 Kotlin by 委托属性,本质上是间接使用了 ViewModelProvider:

方法 3: Hilt 提供了注入部分 Jetpack 架构组件的支持

** ViewModel 定义,组合了 LiveData**

class NameViewModel : ViewModel() {
   val currentName: MutableLiveData<String> by lazy {
       MutableLiveData<String>()
   }
}

class MainActivity : AppCompatActivity() {

   private val model: NameViewModel by viewModels()

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

       // LiveData 观察者
       val nameObserver = Observer<String> { newName ->
           // 更新视图
           nameTextView.text = newName
       }

       // 注册 LiveData 观察者,this 为生命周期宿主
       model.currentName.observe(this, nameObserver)

       // 修改 LiveData 数据
       button.setOnClickListener {
           val anotherName = "John Doe"
           model.currentName.value = anotherName
       }
   }
}

问题: 4.viewModel是怎么实现双向数据绑定的?

一般是databing实现双向数据

ViewModel里面存放MutableLiveData(model)

然后更新view:post。然后Acrtivity通过观察者模式监听!!!

7. ViewModel源码分析

7.1 ViewModel 结构

  • ViewModel:抽象类,有 onClear() 方法以供重写,自定义数据清空。
  • ViewModelStore: 持有管理 ViewModel 的 HashMap。
  • ViewModelStoreOwner: ComponentActivityFragment 实现此接口,约束ViewModelStore 的作用域。
  • ViewModelProvider: 创建 ViewModel

获取ViewModel:

ActivityThread

   ActivityClientRecord

       ViewModelProvider

           NonConfigurationInstance

                ViewModelStore 

                              HashMap

7.2 Activity 流程

7.2.1  Activity 关联 ViewModel 类图

viewmodel架构.jpg

Activity 重建 ViewModel 依然存在

深度的原理:

ActivityThread 中的 ActivityClientRecord 的属性 lastNonConfigurationInstances 不受重建影响,在新建 activity 时,将 lastNonConfigurationInstances 入参到 attach() 方法中,所以 activity 重建后,依然能获取到之前的配置缓存 lastNonConfigurationInstances ,拿到其中的缓存 ViewModelStore

总结: ActivityThread 中的 ActivityClientRecord 是不受 activity 重建的影响,那么ActivityClientRecord中lastNonConfigurationInstances也不受影响,那么其中的Object activity也不受影响,那么ComponentActivity中的NonConfigurationInstances的viewModelStore不受影响,那么viewModel也就不受影响了。

问题: 修改系统语言会发生什么?

进程会被杀死**

问题: 为什么说旋转屏幕ViewModel 不会丢数据。因为虽然走了onDestroy 但是内部判断了是否旋转屏幕

在Android中,当设备的屏幕方向(横屏与竖屏)发生变化时,当前的Activity会经历一系列的生命周期回调。这主要是因为屏幕旋转通常会导致当前的Activity被销毁并重新创建,以适应新的屏幕配置

  • 为啥旋转还能保存数据?
判断了是配置变化,如旋转屏幕等,等到真正销毁才清空

答案:这里我们又学到了Activity的两个跟生命周期相关的函数调用:onRetainNonConfigurationInstance和getLastNonConfigurationInstance。

  1. Activity实现了ViewModelStoreOwner接口,创建了ViewModelStore对象。
  2. 当Activity意外销毁时,onRetainNonConfigurationInstance函数被回调,在此函数中对ViewModelStore对象进行了保存。
  3. 当Activity重建时,onCreate方法中会先获取getLastNonConfigurationInstance,如果其中的ViewModelStore对象不为空,就直接引用,不再重新创****建ViewModelStore对象了。

横竖平:能够保存数据的原理:ViewModelStore,里面用的hashmap


viewmodel原理.png

=============================================================

问题: 旋转屏幕会执行ondestory方法吗?会执行

根本原因:AMS有关,因为在执行onDestroy时,ActivityClientRecord的lastNonConfigurationInstances从哪里来,答案是在onDestroy时,会保存Activity的retainNonConfigurationInstances()方法返回值。

从ActivityClientRecord持有一条到ViewModelStore引用链****,所以当Activity被销毁时,ViewModelStore不会被销毁,

而Activity的reLaunch并不会销毁对应的ActivityClientRecord,下次仍然会复用ActivityClientRecord,进而复用保存的ViewModelStore,这样就解释通了;

Activity是不同的实例。但是获取到的ViewModelStore是同一个。


答案总结:1).进来创建了ViewModel,如果ViewModelStore还在,用同一个

2).onstop的时候,viewModelStore保存了数据,通过一个新方法

3).destory的时候,viewModelstore是保存在ActivityClientRecord里面,viewModelStore在横竖平的时候不会销毁,但是在正常destory的时候会清除

和上面一样,AMS的ActivityClientRecord

setRetainInstance (true)

那我有个问题,Actiivty销毁之后,一直用的是第一次的VIewModel。那么它们的数据岂不是用旧数据.

只要在切换横竖屏才保持,否则ondestory的时候clear。把VIewModerStore清除了。

FragmentManager类的destroy()方法里面清除

Activity在onDestroy会尝试对ViewModelStore清空。如果是由于ConfigurationChanged带来的Destroy则不进行清空


当 Activity 处于前台的时候被销毁了,那么得到的 ViewModel 是之前实例过的 ViewModel;

如果 Activity 处于后台时被销毁了,那么得到的 ViewModel 不是同一个。举例说,

如果 Activity 因为配置发生变化而被重建了,那么当重建的时候,ViewModel 是之前的实例;如果因为长期处于后台而被销毁了,那么重建的时候,ViewModel 就不是之前的实例了。

viewmodel整体存在.jpg

问题: 怎么自动管理的?

  • ComponentActivity 监听onDestroy() ,清理
  • FragmentFragmentActivityonDestroy() 会清理。

Fragment能拿 ActivityViewModel么?

  • 能 ,毕竟FragmentManger 那向上管理,其实取的就是上层FragmentActivityViewModelStore

问题: ViewModelStore是个啥?

  • ViewModelStore 知道么?

    • 知道 ,一个map 就是干。
  • 知道怎么创建的么?

    • 内部 factory 反射就是干。

8. ViewModel对比onSaveInstanceState()

对比.jpg

9.ViewModel 的内存泄漏问题

ViewModel 的内存泄漏是指 Activity 已经销毁,但是 ViewModel 却被其他组件引用。这往往是因为数据层是通过回调监听器的方式返回数据,并且数据层是单例对象或者属于全局生命周期,所以导致 Activity 销毁了,但是数据层依然间接持有 ViewModel 的引用。

如果 ViewModel 是轻量级的或者可以保证数据层操作快速完成,这个泄漏影响不大可以忽略。但如果数据层操作并不能快速完成,或者 ViewModel 存储了重量级数据,就有必要采取措施。例如:

  • 方法 1: 在 ViewModel#onCleared() 中通知数据层丢弃对 ViewModel 回调监听器的引用;
  • 方法 2: 在数据层使用对 ViewModel 回调监听器的弱引用(这要求 ViewModel 必须持有回调监听器的强引用,而不能使用匿名内部类,这会带来编码复杂性);
  • 方法 3: 使用 EventBus 代替回调监听器(这会带来编码复杂性);
  • 方法 4: 使用 LiveData 的 Transformations.switchMap() API 包装数据层的请求方法,这相当于在 ViewModel 和数据层中间使用 LiveData 进行通信

第二种泄露的情况:

 使用ViewModel的时候,需要注意的是ViewModel不能够持有View、Lifecycle、Acitivity引用,而且不能够包含任何包含前面内容的类。因为这样很有可能会造成内存泄漏。

这张图也解释了为什么ViewModel中不能持有Activity、Fragment、view的引用。因为Activity在重建后是一个新的对象,如果ViewModel中持有旧对象的引用,这个旧对象可能就等不到释放,造成泄漏。

如果确实需要,应该使用applicationcontext,或者使用含有上下文的AndroidViewModel。

产生的原因:数据永久保存,shareperfece用的话要context.所以产生了AndroidViewModel

那如果需要使用Context对象改怎么办。这时候我们可以给ViewModel一个Application。Application是一个Context,而且一个应用也只会有Application。

我们自己添加Application?其实没必要Google还有一个AndroidViewModel。这是一个包含Application的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

推荐阅读更多精彩内容