ViewModel 库一公布,便成为了 Jetpack 中的外围组件之一。咱们在 2019 年做的一份开发者问卷显示,超过 40% 的 Android 开发者曾经在本人的利用中应用了 ViewModel。ViewModel 能够将数据层与 UI 拆散,而这种架构不仅能够简化 UI 的生命周期的管制,也能让代码取得更好的可测试性。如果想理解更多,能够参考 ViewModel: 简略介绍视频和 官网文档。
因为 ViewModel 是许多性能实现的根底,咱们在过来的几年里做了许多工作来改良 ViewModel 的易用性,也让它可能更加简便地与其余组件库相结合。上面的文章中,我将介绍 ViewModel 的四种集成形式:
- ViewModel 中的 Saved State —— 后盾过程重启时,ViewModel 的数据恢复;
- 在 NavGraph 中应用 ViewModel —— ViewModel 与导航 (Navigation) 组件库的集成;
- ViewModel 配合数据绑定 (data-binding) —— 通过应用 ViewModel 和 LiveData 简化数据绑定;
- viewModelScope —— Kotlin 协程与 ViewModel 的集成。
ViewModel 的 Saved State —— 后盾过程重启时,ViewModel 的数据恢复
- 于 lifecycle-viewmodel-savedstate 的 1.0.0-alpha01 版本时退出
- 反对 Java 和 Kotlin
onSaveInstanceState 带来的挑战
ViewModel 一公布,执行 onSaveInstanceState 的相干的逻辑时要如何操作 ViewModel,便成为了一个令人困惑的问题。Activity 和 Fragment 通常会在上面三种状况下被销毁:
- 从以后界面永恒来到 : 用户导航至其余界面或间接敞开 Activity (通过点击返回按钮或执行的操作调用了 finish() 办法)。对应 Activity 实例被永恒敞开;
- Activity 配置 (configuration) 被扭转 : 例如,旋转屏幕等操作,会使 Activity 须要立刻重建;
- 利用在后盾时,其过程被零碎杀死 : 这种状况产生在设施残余运行内存不足,零碎又亟须开释一些内存的时候。当过程在后盾被杀死后,用户又返回该利用时,Activity 也须要被重建。
在后两种状况中,咱们通常都心愿重建 Activity。ViewModel 会帮您解决第二种状况,因为在这种状况下 ViewModel 没有被销毁;而在第三种状况下,ViewModel 被销毁了。所以一旦呈现了第三种状况,便须要在 Activity 的 onSaveInstanceState 相干回调中保留和复原 ViewModel 中的数据。我在 ViewModels: 长久化、onSaveInstanceState()、复原 UI 状态与加载器 一文中更加具体地形容了这两种状况的区别。
Saved State 模块
当初,ViewModel Saved State 模块将会帮您在利用过程被杀死时复原 ViewModel 的数据。在罢黜了与 Activity 繁琐的数据交换后,ViewModel 也真正意义上的做到了治理和持有所有本人的数据。
ViewModel 的这一新性能是通过 SavedStateHandle 实现的。SavedStateHandle 和 Bundle 一样,以键值对模式存储数据,它蕴含在 ViewModel 中,并且能够在利用处于后盾时过程被杀死的状况下幸存下来。诸如用户 id 等须要在 onSaveInstanceState 时失去保留下来的数据,当初都能够存在 SavedStateHandle 中。
设置 Save State 模块
当初让咱们看看如何应用 SaveState 组件。留神接下来的代码会和 Lifecycles Codelab 第六步中的一段代码十分相似。那段是 Java 代码,而接下来的是 Kotlin 代码:
第一步: 增加依赖
SaveStateHandle 目前在一个独立的模块中,您须要在依赖中增加:
def lifecycle_version = "2.2.0"
implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version"
留神,本文公布时 lifecycle 组件的最新稳定版为 2.2.0,如果您心愿继续关注相干组件库的停顿,能够查看 lifecycle 版本公布文档。
第二步: 批改调用 ViewModelProvider 的形式
接下来,您须要创立一个持有 SaveStateHandle 的 ViewModel。在 Activity 或 Fragment 的 onCreate 办法中,将 ViewModelProvider 的调用批改为:
// 上面的 Kotlin 扩大须要依赖以下或更新新版本的 ktx 库://androidx.fragment:fragment-ktx:1.0.0(最新版本 1.2.4)或
//androidx.activity:activity-ktx:1.0.0(最新版本 1.1.0)val viewModel by viewModels {SavedStateViewModelFactory(application, this) }
// 或者不应用 ktx
val viewModel = ViewModelProvider(this, SavedStateViewModelFactory(application, this))
.get(MyViewModel::class.java)
创立 ViewModel 的类是 ViewModel 工厂 (ViewModel factory),而创立蕴含 SaveStateHandle 的 View Model 的工厂类是 SavedStateViewModelFactory。通过此工厂创立的 ViewModel 将持有一个基于传入 Activity 或 Fragment 的 SaveStateHandle。
第三步: 应用 SaveStateHandle
当后面的步骤筹备实现时,您就能够在 ViewModel 中应用 SavedStateHandle 了。上面是一个保留用户 ID 的示例:
class MyViewModel(state :SavedStateHandle) :ViewModel() {
// 将 Key 申明为常量
companion object {private val USER_KEY = "userId"}
private val savedStateHandle = state
fun saveCurrentUser(userId: String) {
// 存储 userId 对应的数据
savedStateHandle.set(USER_KEY, userId)
}
fun getCurrentUser(): String {
// 从 saveStateHandle 中取出以后 userId
return savedStateHandle.get(USER_KEY)?: ""
}
}
- 构造方法 : SavedStateHandle 作为构造方法参数传入 MyViewModel;
- 保留 : saveNewUser 办法展现了应用键值对的模式保留 USER_KEY 和 userId 到 SaveStateHandle 的例子。每当数据更新时,要保留新的数据到 SavedStateHandle;
- 获取 : 如代码中所示,调用 savedStateHandle.get(USER_KEY) 办法获取被保留的 userId。
当初,无论是第二还是第三种状况下,SavedStateHandle 都能够帮您复原界面数据了。
如果您想要在 ViewModel 中应用 LiveData,能够调用 SavedStateHandle.getLiveData(),示例如下:
// getLiveData 办法会获得一个与 key 相关联的 MutableLiveData
// 当与 key 绝对应的 value 扭转时 MutableLiveData 也会更新。private val _userId : MutableLiveData<String> = savedStateHandle.getLiveData(USER_KEY)
// 只裸露一个不可变 LiveData 的状况
val userId : LiveData<String> = _userId
如需理解更多,请移步至 Lifecycles Codelab 第六步 和 官网文档。
ViewModel 与 Jetpack 导航: 在 NavGraph 中应用 ViewModel
- 于 navigation 的 2.1.0-rc01 版本时退出
- 反对 Java 与 Kotlin
共享 ViewModel 数据所带来的挑战
Jetpack 导航组件 (Navigation) 非常实用于那些只有大量或一个 Activity,然而 Activity 中会蕴含多个 Fragment 的利用。Ian Lake 在他的演讲: 单 Activity 架构: 为什么、什么状况下以及如何应用中介绍了一些咱们抉择繁多 Activity 架构的起因,而与本文相干的一点,是这种架构容许在多个界面 (destination) 间共享 ActivityViewModel。您能够用 Activity 创立一个 ViewModel 实例,而后从这个 Activity 中的任一个 Fragment 中取得 ViewModel 的援用:
// 在 Fragment 的 onCreate 或 onActivityCreated 办法中执行
// 这个 Kotlin 扩大须要依赖最 KTX 库:androidx.fragment:fragment-ktx:1.1.0
val sharedViewModel: ActivityViewModel by activityViewModels()
- 单 Activity 架构: 为什么、什么状况下以及如何应用 https://v.youku.com/v_show/id…
假如咱们有这样一个单 Activity 利用,它蕴含了八个 Fragment,其中四个 Fragment 是购买领取流程:
△ 蕴含一些购买领取流程的导航图 (Navigation Graph)
这四个页面须要共享一些诸如收货地址、是否应用了优惠券等信息。依照后面所讲的做法,须要共享的数据会放在一个 ActivityViewModel 中,但这同时也意味着所有八个页面都会共享这些数据。领取流程外的界面并不需要关怀这些数据,这么做显然并不适合。
ViewModel 与 NavGraph 集成
Navigation 2.1.0 中引入了依靠一个导航图 (navigation graph) 创立 ViewModel 的性能。在应用时,您须要先把一个界面汇合 (例如: 登录流程、领取流程的相干界面),放到一个 嵌套导航图 (nested navigation graph) 中。此时再通过嵌套导航图创立出 ViewModel,便能够在相干界面中共享数据了。
想要创立嵌套导航图,您须要选中对应流程相干的界面,点击鼠标右键,并抉择 Nested Graph → New Graph:
△ 创立嵌套导航图的截图
留神嵌套导航图在 XML 文件中的 id,在这里是 checkout_graph:
<navigation app:startDestination="@id/homeFragment" ...>
<fragment android:id="@+id/homeFragment" .../>
<fragment android:id="@+id/productListFragment" .../>
<fragment android:id="@+id/productFragment" .../>
<fragment android:id="@+id/bargainFragment" .../>
<navigation
android:id="@+id/checkout_graph"
app:startDestination="@id/cartFragment">
<fragment android:id="@+id/orderSummaryFragment".../>
<fragment android:id="@+id/addressFragment" .../>
<fragment android:id="@+id/paymentFragment" .../>
<fragment android:id="@+id/cartFragment" .../>
</navigation>
</navigation>
以上工作实现时,便能够应用 by navGraphViewModels 获取到对应的 ViewModel:
val viewModel: CheckoutViewModel by navGraphViewModels(R.id.checkout_graph)
Java 中同样实用,代码如下:
public void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);
// 设置其余 fragment
NavController navController = NavHostFragment.findNavController(this);
ViewModelProvider viewModelProvider = new ViewModelProvider(this,
navController.getViewModelStore(R.id.checkout_graph));
CheckoutViewModel viewModel = viewModelProvider.get(CheckoutViewModel.class);
// 应用 Checkout ViewModel
}
须要留神的是,嵌套导航图绝对于导航图的其余局部是一个独立的整体。您无奈导航至嵌套导航图中蕴含的某个特定界面;当您导航至一个嵌套导航图时,关上的只会是其中的开始界面 (startDestination)。这种个性使得嵌套导航图适宜用于封装特定流程的界面组合,比方后面提到过的登录和领取流程。
ViewModel 与 NavGraph 的集成,是 2019 年 I/O 大会所公布的对于 Navigation 框架的新个性之一。
具体理解更多,请参阅:
- 主题演讲: Jetpack Navigation 的主题演讲视频
- 官网文档: [以编程形式与导航组件交互](https://developer.android.goo…
)
ViewModel 与 Data Binding: 在 Data Binding 中应用 ViewModel 和 LiveData
- 于 Android Studio 的 3.1 版本时退出
- 反对 Java 与 Kotlin
移除 LiveData 相干的模板代码
ViewModel、LiveData 与 Data Binding 的集成形式并不是什么新性能,但它始终十分好用。ViewModel 通常都蕴含一些 LiveData,而 LiveData 意味着能够被监听。所以最常见的应用场景是在 Fragment 中给 LiveData 增加一个观察者:
override fun onActivityCreated(savedInstanceState: Bundle?) {super.onActivityCreated(savedInstanceState)
myViewModel.name.observe(this, { newName ->
// 更新 UI,这里是一个 TextView
nameTextView.text = newName
})
}
Data Binding 是一个通过观察数据变动来更新 UI 的组件库。通过 ViewModel、LiveData 和 Data Binding 的组合,您能够移除以往给 LiveData 增加观察者的做法,改为间接在 XML 中绑定 View Model 和 LiveData。
应用 Data Binding、ViewModel 和 LiveData
假如您心愿在 XML 布局文件中援用 ViewModel:
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="viewmodel"
type="com.android.MyViewModel"/>
</data>
<... Rest of your layout ...>
</layout>
调用 binding.setLifecycleOwner(this) 办法,而后将 ViewModel 传递给 binding 对象,就能够将 LiveData 与 Data Binding 联合起来:
class MainActivity : AppCompatActivity() {
// 这个 ktx 扩大须要依赖 androidx.activity:activity-ktx:1.0.0
// 或更新版本
private val myViewModel: MyViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)
// 填充视图并创立 Data Binding 对象
val binding: MainActivityBinding =
DataBindingUtil.setContentView(this, R.layout.main_activity)
// 申明这个 Activity 为 Data Binding 的 lifecycleOwner
binding.lifecycleOwner = this
// 将 ViewModel 传递给 binding
binding.viewmodel = myViewModel
}
}
当初,您能够像上面这样应用 ViewModel:
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="viewmodel"
type="com.android.MyViewModel"/>
</data>
<TextView
android:id="@+id/name"
android:text="@{viewmodel.name}"
android:layout_height="wrap_content"
android:layout_width="wrap_content"/>
</layout>
留神,这里的 viewmodel.name 既能够是 String 类型,也能够是 LiveData。如果它是 LiveData,那么 UI 将依据 LiveData 值的扭转主动刷新。
ViewMode 与 Kotlin 协程: viewModelScope
- 于 Lifecycle 的 2.1.0 版本时退出
- 只反对 Kotlin
Android 平台上的协程
通常状况下,咱们应用回调 (Callback) 解决异步调用,这种形式在逻辑比较复杂时,会导致回调层层嵌套,代码也变得难以了解。Kotlin 协程 (Coroutines) 同样实用于解决异步调用,它让逻辑变得简略的同时,也确保了操作不会阻塞主线程。如果您不理解协程,这里有一系列很棒的博客《在 Android 开发中应用协程》以及 codelab: 在 Android 利用中应用 Kotlin 协程 以供参考。
一段简略的协程代码:
// 上面是示例代码,实在情景下不要应用 GlobalScope
GlobalScope.launch {longRunningFunction()
anotherLongRunningFunction()}
这段示例代码只启动了一个协程,但咱们在实在的应用环境下很容易创立出许多协程,这就难免会导致有些协程的状态无奈被跟踪。如果这些协程中刚好有您想要进行的工作时,就会导致 工作透露 (work leak)。
为了避免工作透露,您须要将协程退出到一个 CoroutineScope 中。CoroutineScope 能够继续跟踪协程的执行,它能够被勾销。当 CoroutineScope 被勾销时,它所跟踪的所有协程都会被勾销。下面的代码中,我应用了 GlobalScope,正如咱们不举荐随便应用全局变量一样,这种形式通常 不举荐 应用。所以,如果想要应用协程,您要么限定一个作用域 (scope),要么取得一个作用域的拜访权限。而在 ViewModel 中,咱们能够应用 viewModelScope 来治理协程的作用域。
viewModelScope
当 ViewModel 被销毁时,通常都会有一些与其相干的操作也该当被进行。
例如,假如您正在筹备将一个位图 (bitmap) 显示到屏幕上。这种操作就合乎咱们后面提到的一些特色: 既不能在执行时阻塞主线程,又要求在用户退出相干界面时进行执行。应用协程进行此类操作时,就该当应用 viewModelScope.viewModelScope:kotlinx.coroutines.CoroutineScope)。
viewModelScope 是一个 ViewModel 的 Kotlin 扩大属性。正如后面所说,它能在 ViewModel 销毁时 (onCleared()) 办法调用时) 退出。这样一来,只有您应用了 ViewModel,您就能够应用 viewModelScope 在 ViewModel 中启动各种协程,而不必放心工作透露。
示例如下:
class MyViewModel() : ViewModel() {fun initialize() {
viewModelScope.launch {processBitmap()
}
}
suspend fun processBitmap() = withContext(Dispatchers.Default) {// 在这里做耗时操作}
}
具体理解更多,请参阅:
- 文章: 更简便地在 Android 中应用协程: viewModelScope
- 官网文档: 将 Kotlin 协程与架构组件一起应用
- 视频演讲: 了解 Android 中的 Kotlin 协程
总结
本文中,咱们讲了:
- ViewModel 应用 SaveStateHandle 组件解决 onSaveInstanceState 相干逻辑;
- 通过配合 View Model 和导航图来准确限定数据在 Fragment 中的共享范畴;
- 应用 DataBinding 库时,能够将 ViewModel 传递给数据绑定 (binding),如果同时有在 ViewModel 中应用 LiveData,则能够通过 binding.setLifecycleOwner(lifecycleOwner) 让 UI 依据 LiveData 自动更新;
- 在 ViewModel 中应用 Kotlin 协程时,应用 viewModelScope 来让协程在 ViewModel 被销毁时主动勾销。
以上这些性能很多都来自社区提交的申请和反馈,如果您正在寻找 ViewModel 相干的性能,能够注意 性能需要列表 或者思考 提交本人的需要。
如果您想理解架构组件和 Android Jetpack 的最新进展,请关注 Android 开发者博客,并注意 AndroidX 公布文档。
如果您对这些性能仍有疑难,能够在下方留言。感激浏览!