关于android:知识点-ViewModel-四种集成方式

52次阅读

共计 9323 个字符,预计需要花费 24 分钟才能阅读完成。

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 通常会在上面三种状况下被销毁:

  1. 从以后界面永恒来到 : 用户导航至其余界面或间接敞开 Activity (通过点击返回按钮或执行的操作调用了 finish() 办法)。对应 Activity 实例被永恒敞开;
  2. Activity 配置 (configuration) 被扭转 : 例如,旋转屏幕等操作,会使 Activity 须要立刻重建;
  3. 利用在后盾时,其过程被零碎杀死 : 这种状况产生在设施残余运行内存不足,零碎又亟须开释一些内存的时候。当过程在后盾被杀死后,用户又返回该利用时,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)?: ""
    }
}
  1. 构造方法 : SavedStateHandle 作为构造方法参数传入 MyViewModel;
  2. 保留 : saveNewUser 办法展现了应用键值对的模式保留 USER_KEY 和 userId 到 SaveStateHandle 的例子。每当数据更新时,要保留新的数据到 SavedStateHandle;
  3. 获取 : 如代码中所示,调用 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 协程

总结

本文中,咱们讲了:

  1. ViewModel 应用 SaveStateHandle 组件解决 onSaveInstanceState 相干逻辑;
  2. 通过配合 View Model 和导航图来准确限定数据在 Fragment 中的共享范畴;
  3. 应用 DataBinding 库时,能够将 ViewModel 传递给数据绑定 (binding),如果同时有在 ViewModel 中应用 LiveData,则能够通过 binding.setLifecycleOwner(lifecycleOwner) 让 UI 依据 LiveData 自动更新;
  4. 在 ViewModel 中应用 Kotlin 协程时,应用 viewModelScope 来让协程在 ViewModel 被销毁时主动勾销。

以上这些性能很多都来自社区提交的申请和反馈,如果您正在寻找 ViewModel 相干的性能,能够注意 性能需要列表 或者思考 提交本人的需要。

如果您想理解架构组件和 Android Jetpack 的最新进展,请关注 Android 开发者博客,并注意 AndroidX 公布文档。

如果您对这些性能仍有疑难,能够在下方留言。感激浏览!

正文完
 0