Jetpack 之 ViewModel (一)使用篇

ViewModel类旨在响应组件(ActivityFragmentService)生命周期的方式,来存储和管理界面相关数据。ViewModel可以在组件界面发生屏幕旋转等配置变化后,继续留存数据

第一个问题:如果组件(ActivityFragmentService)被系统销毁或者重建,则存储在其中的界面数据都会丢失。例如:Activity中可能包含一个列表,如果该Activity重新被创建之后,则列表数据必须重新获取。对于简单的数据来说,Activity可以在 onSaveInstanceStateonCreate 方法的配合使用中恢复其数据,但前提是onSaveInstanceState只适合可支持序列化的少量数据,不可以是数据量较大的列表或者位图。

第二个问题:组件(ActivityFragmentService)中,经常会执行一些异步操作。Activity组件需要手动来管理这些异步操作,以避免内存泄漏。但是这样手动管理的操作,需要大量的维护工作,并且在Activity配置改变的情况下,会造成资源浪费,因为可能需要重新发出已经发出过的异步操作。

第三个问题:组件(ActivityFragment)主要是用于显示界面数据的,如果其中还包含数据库操作和网络操作,那么该类会显得很臃肿。

从Activity组件中,分离出界面数据相关业务逻辑的做法是可行的,这是要说的 ViewModel

ViewModel的使用

Jetpack为组件(ActivityFragment)提供了 ViewModel 这个辅助类,该类负责处理界面数据相关逻辑。在组件(ActivityFragment)配置发生改变时,ViewModel会自动保存数据,以便于重建后新的(ActivityFragment)组件实例使用。例如:在组件(ActivityFragment)中展示一个列表,我们可以将列表数据的获取和保留操作,交由ViewModel来处理

示例代码如下:

public class MyViewModel extends ViewModel {
    private MutableLiveData<List<User>> users;
    public LiveData<List<User>> getUsers() {
        if (users == null) {
            users = new MutableLiveData<List<User>>();
            loadUsers();
        }
        return users;
    }

    private void loadUsers() {
        // Do an asynchronous operation to fetch users.
    }
}

然后在Activity中访问列表数据

public class MyActivity extends AppCompatActivity {
    public void onCreate(Bundle savedInstanceState) {
        // 在onCreate() 方法中,第一时间创建 ViewModel 对象。
        // 重新创建Activity实例,会得到该Activity第一次创建时的那个 ViewModel 实例。

        MyViewModel model = new ViewModelProvider(this).get(MyViewModel.class);
        model.getUsers().observe(this, users -> {
            // 更新UI的操作。
        });
    }
}

如果Activity由于配置改变而被重新创建,那么它接收到的ViewModel实例与第一个Activity中创建的实例是同一个。而且当Activity退出时,ViewModel还会自动调用onCleared()方法自动清理数据资源。

1. ViewModel是绝不能引用视图的,因为Lifecycle会存储对Activity上下文的引用,从而导致内存泄漏 (ViewModel对象的生命周期比较长)。

2. ViewModel内部可以包含像LiveData这样的LifecycleObservers,但是ViewModel绝不能用来观察那些 对生命周期感知的可观察对象(比如 LiveData) 的更改。

扫描二维码关注公众号,回复: 14884218 查看本文章

3. 如果ViewModel需要Application上下文来查询系统服务,那么它可以扩展AndroidViewModel类,并设置接收Application上下文的构造函数。

创建具有依赖项的ViewModel (即自定义构造函数)

如果ViewModel的创建需要依赖于其他对象,则可以在 ViewModel 的构造函数中将依赖对象作为参数传入 (依赖项大多都是网络层数据层中的类型)。但是这种情况是需要一种特殊的机制来创建 ViewModel 的实例。该机制的关键就是 ViewModelProvider.Factory 接口。                                  怎么理解  ViewModelProvider.Factory 呢?在了解它之前我们先来看一下 ViewModelProvider 类,通过前源码可以知道,ViewModelProviders 在内部为我们管理并调用 ViewModel 的主构造函数,创建 ViewModel 的实例并将该实例返回。所以在 ViewModel 不需要依赖项入参的情况下,我们都是使用的 ViewModelProviders 中默认的 ViewModelProvider.Factory 接口实现。因此如果想要 ViewModel 构造函数支持参数,那么我们就需要自定义一个  ViewModelProvider.Factory 接口的实现类,并在其内部实现 ViewModel 类实例的创建。

注意⚠️ 如果 ViewModel 不接受任何依赖项时,又或者只将 SavedStateHandle 类型作为依赖项,我们就不需要提供工厂来实例化该 ViewModel 类型的实例。

2.5.0之前版本实现工厂

之前的版本,需要实现 ViewModelProvider.Factory 接口,并重写 create() 方法,并在该方法中来管理创建 ViewModel 实例对象。

根据 ViewModel 所需的依赖项的不同,可以选择以下类进行扩展:

AndroidViewModelFactory : 如果需要 Application 类。

AbstractSavedStateViewModelFactory: 如果需要将 SavedStateHandle 作为参数传入。

ViewModelProvider.Factory:如果不需要 Application 或 SavedStateHandle。

下面的例子会将 Application 中的存储库SavedStateHandle 作为参数传入:

import androidx.annotation.NonNull;
import androidx.lifecycle.AbstractSavedStateViewModelFactory;
import androidx.lifecycle.SavedStateHandle;
import androidx.lifecycle.ViewModel;

public class MyViewModel extends ViewModel {
    public MyViewModel(
        MyRepository myRepository,
        SavedStateHandle savedStateHandle
    ) { /* Init ViewModel here */ }
}

public class MyViewModelFactory extends AbstractSavedStateViewModelFactory {

    private final MyRepository myRepository;

    public MyViewModelFactory(MyRepository myRepository) {
        this.myRepository = myRepository;
    }

    @SuppressWarnings("unchecked")
    @NonNull
    @Override
    protected <T extends ViewModel> T create(
                                             @NonNull String key, 
                                             @NonNull Class<T> modelClass, 
                                             @NonNull SavedStateHandle handle
                                            ) {
        return (T) new MyViewModel(myRepository, handle);
    }
}
public class MyActivity extends AppCompatActivity {

    MyViewModel myViewModel = new ViewModelProvider(
        this,
        new MyViewModelFactory(((MyApplication) getApplication()).getMyRepository())
    ).get(MyViewModel.class);

    // Rest of Activity code
}

2.5.0之后版本实现工厂

借助 CreationExtras ,您可以访问有助于实例化 ViewModel 的相关信息。

下面是可以通过 CreationExtras 来访问的键:

功能
ViewModelProvider.NewInstanceFactory.VIEW_MODEL_KEY 此 Key 提供对您传递给 ViewModelProvider.get() 的自定义键的访问权限。
ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY 提供对 Application 类实例的访问权限。
SavedStateHandleSupport.DEFAULT_ARGS_KEY 提供对您在构造 SavedStateHandle 时应使用的参数 bundle 的访问权限。
SavedStateHandleSupport.SAVED_STATE_REGISTRY_OWNER_KEY 提供对用于构造 ViewModel 的 SavedStateRegistryOwner 的访问权限。
SavedStateHandleSupport.VIEW_MODEL_STORE_OWNER_KEY 提供对用于构造 ViewModel 的 ViewModelStoreOwner 的访问权限。

如果需要创建 SaveStateHandle 的新实例,请使用 CreationExtras 的 createSavedStateHandle() 函数,并将其传给 ViewModel。

import static androidx.lifecycle.SavedStateHandleSupport.createSavedStateHandle;
import static androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY;

import androidx.lifecycle.SavedStateHandle;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.viewmodel.ViewModelInitializer;

public class MyViewModel extends ViewModel {

    public MyViewModel(
        MyRepository myRepository,
        SavedStateHandle savedStateHandle
       ) { 
         /* Init ViewModel here */ 
    }

    static final ViewModelInitializer<MyViewModel> initializer = new ViewModelInitializer<>(
        MyViewModel.class,
        creationExtras -> {
            // 获取Application中的存储库
            MyApplication app = (MyApplication) creationExtras.get(APPLICATION_KEY);
            assert app != null;
            SavedStateHandle savedStateHandle = createSavedStateHandle(creationExtras);

            return new MyViewModel(app.getMyRepository(), savedStateHandle);
        }
    );
}
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.ViewModelProvider;

public class MyActivity extends AppCompatActivity {

    MyViewModel myViewModel = new ViewModelProvider(
        this,
        ViewModelProvider.Factory.from(MyViewModel.initializer)
    ).get(MyViewModel.class);

    // Rest of Activity code
}

前面提到 SavedStateHandle,它是用来保存数据的,是个键值对类型的数据,类似于 onSaveInstanceState(),当然 SavedStateHandle 要强大很多。

SavedStateHandle 与 onSaveInstanceState

我们知道 Activity 意外销毁的情况,可以分为两种:

1. 由于屏幕旋转等配置更改而导致的 Activity 被销毁重建;

2. 由于系统资源限制导致的 Activity 被销毁重建;

先说说 第 2 种 情况下的数据恢复方式。这种情况需要依赖于 Activity 原生提供的数据保存及恢复机制,即依赖以下两个方法来实现数据保存和数据恢复。

  • onSaveInstanceState(Bundle)。通过向 Bundle 插入键值对的方式来保存数据,数据在上述两种情况发生时都会被保留下来,但该方法着也有存储容量和存取效率的限制。Bundle 有着容量限制,不适合用于存储大量数据,而且是通过将数据序列化到磁盘来进行保存的,所以如果要保存的数据很复杂或者很大,序列化就会消耗大量的内存和时间。因此 onSaveInstanceState 方法仅适合用于存储少量简单类型的数据。
  • onCreate(Bundle) 或者 onRestoreInstanceState(Bundle)。用于从 Bundle 中取出数据进行状态恢复。

再来说说 第 1 种 情况,Jetpack 提供了 ViewModel 来解决这个问题。ViewModel 可以在配置更改后继续存留,适合用于在内存中存储比较复杂或者量比较大的数据。例如,用 RecyclerView 加载的多个列表项对应的 Data 数据。但当 第 2 种 情况发生时,ViewModel 是无法被保留下来的,Activity 重建后也只会得到一个新的 ViewModel 实例,并且之前已经加载的数据也会丢失

对于 第 2 种 情况,数据的保存和恢复流程被限制在了 Activity 的特定方法里,我们无法直接在 ViewModel 中决定哪些数据需要被保留,也无法直接拿到恢复后的数据,使得整个重建流程和 ViewModel 分裂开了。

为了解决这个问题,Jetpack 提供了 SavedStateHandle 这么一个组件,可以看做是对 ViewModel 的功能扩展,使得开发者可以直接在 ViewModel 中直接操作整个数据的重建过程,从而取代原生的 onSaveInstanceState(Bundle) 方法。

SavedStateHandle 的使用

SavedStateHandle 的引入使得开发者无需直接使用 onSaveInstanceState(Bundle) 等方法来完成数据的保存和重建,而只需要在 ViewModel 里来完成即可。

使用步骤如下:

  • SavedStateHandle 作为 ViewModel 的构造参数
  • ViewModel 内部通过 SavedStateHandle.getLiveData方法来生成一个 LiveData 对象,LiveData 中的数据就是我们想要持久化保存的数据。如果是全新启动 Activity,LiveData 中保存的值为 null;如果是重建后的 Activity,LiveData 中保存的值则为重建前的值。
  • 传给getLiveData方法的 String 参数是一个唯一 Key,最终保存到 Bundle 中的键值对就以该值作为 Key,以 LiveData 的值作为 value。
class SavedStateViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {

    companion object {
        private const val KEY_NAME = "keyName"
    }
    val nameLiveData = savedStateHandle.getLiveData<String>(KEY_NAME)
    val blogLiveData = MutableLiveData<String>()
}

class MainActivity : AppCompatActivity() {

    private val savedStateViewModel by lazy {
        ViewModelProvider(this).get(SavedStateViewModel::class.java)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        log("savedStateViewModel: $savedStateViewModel")
        log("savedStateViewModel.name: ${savedStateViewModel.nameLiveData.value}")
        log("savedStateViewModel.blog: ${savedStateViewModel.blogLiveData.value}")
        log("onCreate")
        btn_test.setOnClickListener {
            savedStateViewModel.nameLiveData.value = "业志陈"
            savedStateViewModel.blogLiveData.value = "https://juejin.cn/user/923245496518439/posts"
        }
    }

    private fun log(log: String) {
        Log.e("MainActivity", log)
    }

}

打开开发者模式中"不保留活动"的选项,以此来模拟 Activity 由于系统内存不足被销毁的情况。当 MainActivity 第一次启动时,两个 LiveData 中的值都是为 null。

E/MainActivity: savedStateViewModel: github.leavesc.demo.SavedStateViewModel@df3fa77
E/MainActivity: savedStateViewModel.name: null
E/MainActivity: savedStateViewModel.blog: null
E/MainActivity: onCreate

点击按钮为这两个 LiveData 进行赋值,按 Home 键退出应用,此时 MainActivity 在后台就会被销毁。重新打开应用,此时就可以看到 ViewModel 其实已经是新的一个实例了,但通过 SavedStateHandle 构建的 nameLiveData 中还保留着之前的值,而 blogLiveData 中就还是默认值 null。

E/MainActivity: savedStateViewModel: github.leavesc.demo.SavedStateViewModel@f5fa30c
E/MainActivity: savedStateViewModel.name: 业志陈
E/MainActivity: savedStateViewModel.blog: null
E/MainActivity: onCreate

以上例子就展示了 SavedStateHandle 在 Activity 被意外杀死时也可以保留数据的能力,使得我们可以直接在 ViewModel 里完成整个数据的重建逻辑。此外,再强调的是,如果 Activity 是由于系统资源限制导致被销毁重建的话,ViewModel 实例是不会被保留下来的,所以在以上例子中第二次得到的是一个新的 ViewModel 实例,此时只能依赖 Activity 原生的数据恢复机制来保存少量简单的数据 而 SavedStateHandle 其实也是通过封装 onSaveInstanceState(Bundle)onCreate(Bundle)两个方法来实现的,SavedStateHandle 会在 Activity 被销毁时通过onSaveInstanceState(Bundle)方法将数据保存在 Bundle 中,在重建时又将数据从 onCreate(Bundle)中取出,开发者只负责向 SavedStateHandle 存取数据即可,并不需要和 Activity 直接做交互,从而简化了整个开发流程。

ViewModel的生命周期

ViewModel 对象存活的时间,是由其内部的 Lifecycle 来决定的。而 Lifecycle 的生命周期是由其宿主(Activity 或 Fragment) 来决定的。为此 ViewModel 的存活时间截止于 Activity 完成时,或 Fragment 被卸载时。

猜你喜欢

转载自blog.csdn.net/m0_49508485/article/details/127102191