【Jetpack】学穿:ViewModel → 视图模型(上)

简介: 本节带来组件 → ViewModel 视图模型的解读!叫 视图数据 可能更贴切,有人也叫 视图状态

0x1、引言


来来来,继续学穿Jetpack,本节带来组件 → ViewModel 视图模型的解读!叫 视图数据 可能更贴切,有人也叫 视图状态,都一个意思,怎么称呼看你自己喜欢~


ViewModel 一言以蔽之


ViewModel 将 视图数据视图控制器 中分离,并实现了 数据管理 的:一致性数据共享(跨页面通信)作用域可控


① 视图数据与控制器分离


视图控制器


一般代指Activity和Fragment,它们通过在屏幕上绘制View,捕获用户事件,处理用户与互动界面相关的操作来 控制界面


视图数据


就是你用来对控件setXxx()的数据源,它和与它相关的决策逻辑 (或者说管理) 不应该放到视图控制器中。


ViewModel所做的事,就是用 模版方法模式 进行封装,隐藏一些具体细节,提供简洁的API供我们使用。给了我们一种它们好像真的分离了的错觉,实际上还是与视图控制器紧密相连,ViewModel依旧被对应的Activity、Fragment所持有


最直观的体现::页面配置变更,引起页面销毁重建,ViewModel中的数据不会因此而丢失


写个简单的例子更直观,先不用ViewModel:


class TestActivity : AppCompatActivity() {
    private var mCount: Int = 0
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_test)
        tv_content.text = "${mCount}" 
    }
    bt_test.setOnClickListener { tv_content.text = "${++mCount}" }
}


操作:点击按钮mCount会自增1,旋转手机触发屏幕翻转,发现数字又从0开始了:


网络异常,图片无法展示
|


接着用上ViewModel,定义一个类继承ViewModel,把mCount丢到里面:


class TestViewModel: ViewModel() { var mCount = 0 }


改动下原代码:


class TestActivity : AppCompatActivity() {
    private lateinit var testViewModel: TestViewModel
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_test)
        testViewModel = ViewModelProvider(this).get(TestViewModel::class.java)
        tv_content.text = "${testViewModel.mCount}"
    }
    bt_test.setOnClickListener { tv_content.text = "${++testViewModel.mCount}" }
}


执行同样的操作,无论怎么翻转,数字都不会因页面重建而丢失(表现为从0开始)。


网络异常,图片无法展示
|


结论:把视图数据放到ViewModel里,就不会受页面配置变化销毁重建的影响。


② 一致性


对了,上面说到的配置变更,除了横竖屏切换外,还有这些:


分辨率调整、权限变更、系统字体样式变更、系统语言切换、多窗口设定、系统导航方式变更等。


在以前,为了避免这种 页面配置变更引起的页面销毁重建 导致的 视图数据丢失 问题,需要我们在 onSaveInstanceState()onRestoreInstanceState() 手动编写数据保存和恢复的语句。


页面一多、要保存恢复的数据一多、加之多人协作,就很容易出现 结果不一致的问题,比如:某人在编写存数据相关的代码,漏掉了某个数据,导致拿时没拿到正确的数据。

结论:使用ViewModel,你只管把数据丢里面就行,无需关心具体如何存取,间接保证了结果一致性。


扩展一下:有了ViewModel就不需要onSaveInstanceState()了?


答:非也非也,具体用哪个还得权衡数据复杂度、访问速度及生命周期,建议 混合使用,分而治之


怎么说?除了这两种存储恢复数据的方式外,还有一种 持久化存储,官方文档 《保存界面状态》 提供了一个维度参考表:


网络异常,图片无法展示
|


简要说下笔者的看法:


ViewModel


  • 数据存内存中,读取更快,和Activity(或其他生命周期所有者)关联,配置更改期间保留在内存中,系统会自动将ViewModel与发生配置更改后产生的新Activity实例关联;
  • 关联组件 (Activity或Fragment) 退出时,系统会自动销毁ViewModel,进程终止也会销毁;
  • 适用于:配置更改后数据需要继续存在的场景,支持复杂对象


onSaveInstanceState()


  • 用Bundle存储数据以便于跨进程传递,存储上限受限于Binder(1M),请勿用于存储大量数据(如Bitmap),也不要存需要冗长序列化和反序列化操作的复杂数据结构;
  • onSaveInstanceState()会将数据序列化到磁盘,如果序列化对象很复制,序列化时会占用大量内存,可能丢帧和视觉卡顿;
  • 适用于:配置更改后少量数据、Activity异常关闭,进程被终止后重新打开 需要恢复的场景。


持久性存储


如果 数据的恢复非常重要、存储数据非常大、数据需要长期存储 的场景,可以考虑持久化存储,比如存数据库中。建议策略:间歇性提前自动把临时数据从内存中备份到硬盘中。当然,持久性存储不局限于本地,网络亦可。


③ 数据共享 (跨页面通信)


日常开发中,Activity和Fragment通信,Fragment与Fragment通信的场景非常常见,常见的做法下述几种:


  1. 依托于宿主Activity,定义一堆公共的访问Fragment的方法,setArguments() 或者 目标Fragment()预留回调;
  2. Fragment中调 getActivity() 获得宿主Activity,强转后获取FragmentManager实例,通过findFragmentById()或者ByTag()拿到目标Fragment实例,调用setArguments()传参;
  3. 利用Fragment Result的API,使用公共FragmentManager,充当传递数据的中心存储,setFragmentResult() 和 setFragmentResultListener();
  4. EventBus、广播等


各有利弊,而采用ViewModel,只需 指定作用域,即可轻松实现跨页面通信。写个烂大街的经典例子:点击列表Fragment,更新右侧内容Fragment,预期效果如下:


网络异常,图片无法展示
|


接着写代码实现一波,先是左侧列表项的布局 (item_list.xml):


<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/tv_choose"
    android:layout_width="match_parent"
    android:layout_height="60dp"
    android:background="@android:color/holo_green_light"
    android:gravity="center" />


接着到列表适配器类 (ListAdapter.kt)


class ListAdapter(data: ArrayList<String>): RecyclerView.Adapter<ListAdapter.ViewHolder>() {
    private var mData = data
    private var mClickListener: ItemClickListener? = null
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        return ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_list, parent ,false))
    }
    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.chooseTv?.let {
            it.text = mData[position]
            it.setOnClickListener { mClickListener?.onItemClick(mData[position]) }
        }
    }
    override fun getItemCount() = mData.size
    fun setOnItemClickListener(listener: ItemClickListener) {
        this.mClickListener = listener
    }
    inner class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
        var chooseTv: TextView? = null
        init { chooseTv = itemView.findViewById(R.id.tv_choose) }
    }
    interface ItemClickListener { fun onItemClick(choose: String) }
}


相关文章
|
3月前
|
前端开发 测试技术 API
Jetpack MVVM 七宗罪之六:ViewModel 接口暴露不合理
Jetpack MVVM 七宗罪之六:ViewModel 接口暴露不合理
45 0
|
8月前
|
Android开发
Android JetPack组件之ViewModel状态的保存(程序在后台被系统杀死数据也存活)
Android JetPack组件之ViewModel状态的保存(程序在后台被系统杀死数据也存活)
100 0
|
8月前
|
存储 Android开发
Android JetPack组件之ViewModel的使用详解
Android JetPack组件之ViewModel的使用详解
83 0
|
10月前
|
编解码
Jetpack 之 ViewModel 组件介绍
ViewModel 是介于 View(视图)和 Model(数据模型)之间的一个东西。它起到了桥梁的作用,使视图和数据既能够分离开,也能够保持通信。
56 0
Jetpack 之 ViewModel 组件介绍
|
存储 XML 前端开发
Android Jetpack系列之ViewModel
ViewModel的定义:**ViewModel旨在以注重生命周期的方式存储和管理界面相关的数据**。ViewModel本质上是视图(View)与数据(Model)之间的桥梁,想想以前的MVC模式,视图和数据都会写在Activity/Fragment中,导致Activity/Fragment过重,后续难以维护,而ViewModel将视图和数据进行了分离解耦,为视图层提供数据。
170 0
|
XML 存储 Android开发
大放光彩的安卓Jetpack组件-ViewModel(终)
前面我们已经说过Jetpack中ViewModel的作用、用法以及使用要点,但还缺少在Activity中的实例展示,所以本节我们将结合结果展示与代码进行解读,希望能更好的展示出ViewModel的风采。
107 0
|
Android开发
大放光彩的安卓Jetpack组件-ViewModel(二)
上回我们说到使用方法,但没有具体去说明使用的要点,其实ViewMode还是挺容易上手的,这节我们来具体说明一些使用要点与运用方式。
|
前端开发 Android开发
大放光彩的安卓Jetpack组件-ViewModel(一)
在项目中,我遇到了一个问题,起因则是无法实时去获取信息来更新UI界面,因为我需要知道我是否获取到了实时信息
|
Android开发 Windows 容器
浅析 JetPack Compose 是如何安装到View视图上
为什么 Compose 无需在意 view 层级问题,怎样嵌套都行? (最简单10s就能明白);
207 0
浅析 JetPack Compose 是如何安装到View视图上