首先,Jetpack 下的ViewModel经常是和 Lifecycle 和 LiveData,databind 一起使用的。 但是我们这里就单讲讲 ViewModel 。
另外你要知道,不同的ViewModel库的使用方式是不大一样的,大概分为2.1.0之前,以及之后。
Jetpack官网 developer.android.google.cn/jetpack/
ViewModel 官方文档 developer.android.google.cn/topic/libra…
一句话来说:
ViewModel用在屏幕旋转后保存依然保存数据,Fragment中共享数据
一、ViewModel能干嘛
作用1:屏幕旋转后ViewModel依然可以帮你保存数据。- ViewModel 类旨在以注重
生命周期的方式存储和管理界面相关的数据。ViewModel 类让数据可在发生屏幕旋转等配置更改后继续留存。
- ViewModel 类旨在以注重
注意:这里说的 ViewModel 生命周期,跟Activity的生命周期不是一个东西。
作用2:避免内存泄漏。- 原有的Activity自己管理页面销毁恢复数据工作时,我们需要确保系统在其销毁后清理这些调用,避免潜在的内存泄漏。你一旦管理得不好,隐患就出现了。所以Viewmodel,从一定程度上来说,就是谷歌在给你打工,帮你避免内存泄露。
咔咔把两个作用记心里,然后这篇文章不用可以看也行了。看太多会累,赶紧点个赞,然后去吃猪脚饭吧。
看到了吧,了解学习这个玩意能干嘛,人家官方说了,这是这个东西最大的作用,就是可以屏幕发生旋转的时候数据继续保留,以前没有他的时候,屏幕旋转时,数据保存工作还是有点麻烦的。(在 onSaveInstanceState() 方法从 onCreate() 中的进行一顿操作,捆绑包恢复其数据)
别到时候,连这个东西自身能用来干嘛都不知道。
这个时候,你可以先看下官方文档了:ViewModel
developer.android.google.cn/topic/libra…
如果不想看,就听我接着胡说。
二、这玩意为啥能在屏幕旋转后还能保存数据
看个图,看到了吧,人家比Activity的生命周期,还牛逼。
牛皮Plus。
ViewModel 对象存在的时间范围是获取 ViewModel 时传递给 ViewModelProvider 的 Lifecycle。ViewModel 将一直留在内存中,直到限定其存在时间范围的 Lifecycle 永久消失:对于 activity,是在 activity 完成时;而对于 fragment,是在 fragment 分离时。
三、ViewModel的使用
作用一: 屏幕旋转后依然保存数据
例子:数据保存,旋转屏幕时数据依旧保存
咔咔咔用AS北极狐创建一个Kotlin项目,弄个Empty Activity。
.
.
package com.am.testviewmodel
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import androidx.activity.viewModels
import androidx.appcompat.widget.AppCompatButton
import androidx.appcompat.widget.AppCompatTextView
class MainActivity : AppCompatActivity() {
private lateinit var tv_text:AppCompatTextView
private lateinit var btn_add:AppCompatButton
private lateinit var tv_text2:AppCompatTextView
private lateinit var btn_add2:AppCompatButton
// androidx.fragment:fragment-ktx:1.4.0
private val model:MyViewModel by viewModels()
var normalInt:Int = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// ViewModel 方式
tv_text = findViewById(R.id.tv_text)
btn_add = findViewById(R.id.btn_add)
tv_text.text = model.num.toString()
btn_add.setOnClickListener(View.OnClickListener {
var num = model.num
num ++
tv_text.text = num.toString()
model.num = num
})
// 普通方式
tv_text2 = findViewById(R.id.tv_text2)
btn_add2 = findViewById(R.id.btn_add2)
tv_text2.text = normalInt.toString()
btn_add2.setOnClickListener(View.OnClickListener {
var num2:Int = normalInt
num2 ++
tv_text2.text = num2.toString()
normalInt = num2
})
}
}
.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center_horizontal"
tools:context=".MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tv_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="100dp"
android:text="0"
android:textColor="#ff0000"
android:textSize="30dp" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tv_text2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="100dp"
android:text="0"
android:layout_marginLeft="100dp"
android:textColor="#0000ff"
android:textSize="30dp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center_horizontal"
>
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btn_add"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:textColor="#ff0000"
android:text="ViewModel方式添加" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btn_add2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:textColor="#0000ff"
android:text="普通方式添加" />
</LinearLayout>
</LinearLayout>
.
.
效果:
.
.
.
.
作用二: Fragment数据共享
- 传统的Fragment通信方式 在FragmentA中获取MainActivity的对象,在通过MainActivity的对象获取FragmnetB对象,最后调用FragmnetB中方法,实现Fragment之间互相通信
- ViewModel 的方式共享数据
接下来我们使用 ViewModel。
- 弄一个model继承自 ViewModel,定义好需要共享的数据值
- 弄两个Frangment,通过
private val model:ShareViewModel by activityViewModels<ShareViewModel>()的方式获取我们Viewmodel - FragmentA修改Viewmodel的值,FragmentA通过
observe观察共享值的变化
引入
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.1'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0-alpha04'
implementation "androidx.fragment:fragment-ktx:1.3.3"
.
ShareViewModel
class ShareViewModel :ViewModel() {
// 后面的0是给定的初始值
var count:MutableLiveData<Int> = MutableLiveData<Int>(0)
fun add(num:Int){
count.value = num
}
}
.
- MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: ShareViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// viewModel = ViewModelProvider(this).get(ShareViewModel::class.java)
}
}
.
布局
<?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"
android:background="@color/black"
tools:context=".MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<fragment
android:id="@+id/frag_a"
android:name="com.am.fragviewmodel.FragmentA"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<View
android:layout_width="wrap_content"
android:layout_height="10dp"/>
<fragment
android:id="@+id/frag_b"
android:name="com.am.fragviewmodel.FragmentB"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
FragmentA
class FragmentA : Fragment() {
private lateinit var tvValueA:TextView
private lateinit var btnA:Button
// activityViewModels 需要 "androidx.fragment:fragment-ktx:1.3.3"
private val model:ShareViewModel by activityViewModels<ShareViewModel>()
private var n = 0
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view:View = layoutInflater.inflate(R.layout.frangment_a,container,false)
tvValueA = view.findViewById(R.id.tv_value_a)
btnA = view.findViewById(R.id.btn_a)
tvValueA.text = model.count.value.toString()
btnA.setOnClickListener(View.OnClickListener {
n++
model.count.value = n
tvValueA.text = n.toString()
})
return view
}
}
.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="FragA"
android:textSize="18dp"
/>
<TextView
android:id="@+id/tv_value_a"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0"
android:layout_marginTop="10dp"
android:textSize="20dp"
/>
<Button
android:id="@+id/btn_a"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="ADD"
android:layout_marginTop="10dp"
/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
.
.
FragmentB
class FragmentB : Fragment() {
private lateinit var tvValueB:TextView
// activityViewModels 需要 "androidx.fragment:fragment-ktx:1.3.3"
private val model:ShareViewModel by activityViewModels<ShareViewModel>()
var n = 0
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view:View = layoutInflater.inflate(R.layout.frangment_b,container,false)
tvValueB = view.findViewById(R.id.tv_value_b)
// 获取共享的值
model.count.observe(viewLifecycleOwner, Observer {
tvValueB.text = it.toString()
})
return view
}
}
.
fragment_b 布局
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="FragB"
android:textSize="18dp"
/>
<TextView
android:id="@+id/tv_value_b"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0"
android:layout_marginTop="10dp"
android:textSize="20dp"
/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
.
.
.
.
再来一个例子
比如有一个用户列表界面
- ViewModel类负责为界面准备数据,在配置更改期间会自动保留 ViewModel 对象,以便它们存储的数据立即可供下一个 activity 或 fragment 实例使用。例如,如果您需要在应用中显示用户列表,请确保将获取和保留该用户列表的责任分配给 ViewModel,而不是 activity 或 fragment
class MyViewModel : ViewModel() {
private val users: MutableLiveData<List<User>> by lazy {
MutableLiveData<List<User>>().also {
loadUsers()
}
}
fun getUsers(): LiveData<List<User>> {
return users
}
private fun loadUsers() {
// Do an asynchronous operation to fetch users.
}
}
可以从 Activity 访问该列表
class MyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// 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.
// Use the 'by viewModels()' Kotlin property delegate
// from the activity-ktx artifact
val model: MyViewModel by viewModels()
model.getUsers().observe(this, Observer<List<User>>{ users ->
// update UI
})
}
}
如果重新创建了该 Activity,它接收的 MyViewModel 实例与第一个 Activity 创建的实例相同。当所有者 Activity 完成时,框架会调用 ViewModel 对象的 onCleared() 方法,以便它可以清理资源。
注意:ViewModel 绝不能引用视图、Lifecycle 或可能存储对 Activity 上下文的引用的任何类。
ViewModel 对象存在的时间比视图或 LifecycleOwners 的特定实例存在的时间更长。这还意味着,您可以更轻松地编写涵盖 ViewModel 的测试,因为它不了解视图和 Lifecycle 对象。ViewModel 对象可以包含 LifecycleObservers,如 LiveData 对象。但是,ViewModel 对象绝不能观察对生命周期感知型可观察对象(如 LiveData 对象)的更改。如果 ViewModel 需要 Application 上下文(例如,为了查找系统服务),它可以扩展 AndroidViewModel 类并设置用于接收 Application 的构造函数,因为 Application 类会扩展 Context。