ViewModel为什么可以实现数据保留与恢复?

708 阅读7分钟

数据保留的原理

ViewModel 能实现数据保留与恢复,关键就在于它的存储位置和生命周期绑定机制

ViewModel 不是和 Activity/Fragment 绑定的,而是和 ViewModelStore 绑定。ViewModelStore 能跨配置变更(比如旋转屏幕)自动保存和恢复 ViewModel 实例,所以能做到“短暂销毁不丢数据”。

ActivityFragment首次创建时,它们会请求一个ViewModel实例,通常是通过ViewModelProviderViewModelProvider负责实例化ViewModel对象,并确保在ActivityFragment的生命周期内这些对象保持不变。

  • 首次创建:如果是首次创建,ViewModelProvider会创建一个新的ViewModel实例。
  • 配置更改后:如果发生配置更改(如屏幕旋转),ActivityFragment将被销毁并重新创建。这时,ViewModelProvider会返回同一个ViewModel实例而不是创建一个新的实例。这就是ViewModel如何保持数据不丢失的关键所在。

ViewModel对象是存储在由ViewModelStore管理的一个容器中的,每个ActivityFragment都有自己的ViewModelStore。当ActivityFragment实例被销毁时,系统会检查是否是因为配置更改而销毁。如果是,ViewModelStore不会被清除,ViewModel就会继续存在。只有当ActivityFragment实例被最终销毁(例如用户完成了Activity),ViewModelStore才会被清除,随之ViewModel也会被清理。

原理拆解

  1. 存储位置不同

    • ViewModel 并不是 Activity 或 Fragment 的普通成员变量。
    • 它存放在一个叫做 ViewModelStore 的专用对象里(Activity/Fragment 都有自己的 ViewModelStore)。
  2. 生命周期“更长”

    • Activity 或 Fragment 一旦因为配置变更(如屏幕旋转、深色模式切换)被系统销毁并重建,普通变量会丢失。
    • 但系统底层会把 ViewModelStore 保留(用 onRetainNonConfigurationInstance 技术),ViewModel 依然留在内存,不会被销毁
  3. 自动恢复机制

    • 新创建的 Activity/Fragment 会复用原来的 ViewModelStore,里面的 ViewModel 也自动“复活”了。
    • 所以数据自动恢复,不需要你手动保存/恢复。
  4. 彻底销毁才会清理

    • 只有 Activity/Fragment 被最终彻底销毁(如用户退出页面、进程被杀),ViewModelStore 才被清空,ViewModel 才 onCleared。

源码流程解析

ViewModel的使用一般不能离开ViewModelProvider,我们以ViewModel的使用一般不能离开ViewModelProvider实例化为入口,看一下ViewModel是怎样运行的。

class MainActivity : AppCompatActivity() {

    // 300个字段
    // var number : Int = 0;

    private lateinit var myViewModel: MyViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        Log.d("DDD", "onCreate: ")

        // myViewModel = MyViewModel()  不能直接实例化,因为如果能这样写,无法对生命周期进行管控。

        // 旧版本的写法,更新特别快(扩展性不强)
        // ViewModelProviders.of()

        // this == ViewModelStoreOwner,
        myViewModel = ViewModelProvider(this, ViewModelProvider.NewInstanceFactory())
            .get(MyViewModel::class.java)

Activity实现了ViewModelStoreOwner,因此可以直接传入这个对象。

public interface ViewModelStoreOwner {
    @NonNull
    ViewModelStore getViewModelStore();
}

ViewModelStoreOwner里边返回了一个ViewModelStore,这个类里边,有一个hashMap,用来做数据存储。

public class ViewModelStore {
    // 用map来存储ViewModel,因为一个页面是很有可能有多个ViewModel的。
    private final HashMap<String, ViewModel> mMap = new HashMap();

    public ViewModelStore() {
    }

    final void put(String key, ViewModel viewModel) {
        ViewModel oldViewModel = (ViewModel)this.mMap.put(key, viewModel);
        if (oldViewModel != null) {
            oldViewModel.onCleared();
        }

    }

    final ViewModel get(String key) {
        return (ViewModel)this.mMap.get(key);
    }

    Set<String> keys() {
        return new HashSet(this.mMap.keySet());
    }

    public final void clear() {
        Iterator var1 = this.mMap.values().iterator();

        while(var1.hasNext()) {
            ViewModel vm = (ViewModel)var1.next();
            vm.clear();
        }

        this.mMap.clear();
    }
}

然后来看看 ViewModelProvider 的实例化的第二个参数,采用的 Factory 来实现.

ViewModel 的实例化过程中,会调用 owner.getViewModelStore(),这个的实现是在ComponentActivity处理的。

public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
    this(owner.getViewModelStore(), factory);
}

public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
    this.mFactory = factory;
    // 这就是我们最终的成果。保存这个成员变量。
    this.mViewModelStore = store;
}
public ViewModelStore getViewModelStore() {
    if (this.getApplication() == null) {
        throw new IllegalStateException("Your activity is not yet attached to the Application instance. You can't request ViewModel before onCreate call.");
    } else {
        // 构建一个ViewModelStore。
        if (this.mViewModelStore == null) {
            // 获取上一次状态的存储类的数据
            NonConfigurationInstances nc = (NonConfigurationInstances)this.getLastNonConfigurationInstance();
            if (nc != null) {
                this.mViewModelStore = nc.viewModelStore;
            }

            if (this.mViewModelStore == null) {
                this.mViewModelStore = new ViewModelStore();
            }
        }

        return this.mViewModelStore;
    }
}

当然,在Fragment中也有类似的实现。

.get(MyViewModel::class.java) 调用工厂ViewModelProvider.NewInstanceFactory(),最终会通过反射完成实例化:

@NonNull
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
    try {
        return (ViewModel)modelClass.newInstance();
    } catch (InstantiationException var3) {
        throw new RuntimeException("Cannot create an instance of " + modelClass, var3);
    } catch (IllegalAccessException var4) {
        throw new RuntimeException("Cannot create an instance of " + modelClass, var4);
    }
}

当Activity重新创建的时候,

  • 系统会调用 retainNonConfigurationInstances(),它会进一步调用你的 onRetainNonConfigurationInstance() (如果你有重写的话),并把自定义对象和 ViewModelStore 等一起打包,临时保存下来。
# public final Object onRetainNonConfigurationInstance() {
    Object custom = this.onRetainCustomNonConfigurationInstance();
    // 如果mViewModelStore不为空的话,就会通过此来进行数据的恢复。
    ViewModelStore viewModelStore = this.mViewModelStore;
    NonConfigurationInstances nci;
    if (viewModelStore == null) {
        // 获取上一次状态的存储类的数据
        nci = (NonConfigurationInstances)this.getLastNonConfigurationInstance();
        if (nci != null) {
            viewModelStore = nci.viewModelStore;
        }
    }

    if (viewModelStore == null && custom == null) {
        return null;
    } else {
        // 将这些数据存储到当前的nci中,以便下次配置变化时使用。
        nci = new NonConfigurationInstances();
        nci.custom = custom;
        nci.viewModelStore = viewModelStore;
        return nci;
    }
}
  • 新的 Activity 创建时,通过 getLastNonConfigurationInstance() 拿到这些数据,实现“数据穿越”。 最终会进入:

这就是ViewModel可以用来做数据保存的底层原理。在ViewModel没有出来之前,我们可以自行重写 onRetainNonConfigurationInstance来实现数据的恢复。

image.png

包含关系:Activity或者Fragment包含 ViewModelProvider,ViewModelProvider 包含ViewModelStore ,ViewModelStore 包含 ViewModel 。

附:Acitivity与ViewModel的生命周期对应关系

只有在Acitivity被销毁之后,ViewModel才会回收。即便不可见也是存在的。

image.png

时序图梳理

配置变更(旋转):
 ──> Activity.onDestroy()
       │
       └─> 系统把 ViewModelStore 临时保存
             │
       新 Activity.onCreate()
             │
       拿到之前保存的 ViewModelStore
             │
       旧的 ViewModel 直接复用
彻底销毁:
 ──> Activity.onDestroy()
       │
       └─> ViewModelStore 被清理
             │
       ViewModel.onCleared() 被调用,内存释放

只有彻底销毁时(比如按返回键退出页面)才会真正释放 ViewModel。配置变更时 ViewModelStore 不会被清理,所以能复用 ViewModel 实例。你看到的“销毁-重建”其实是 ViewModelStore 的“移交”而不是“清空”。

学后测验

1. 下列关于 Android ViewModel 的说法,正确的是?

A. ViewModel 只能在 Activity 被销毁后才会被回收
B. ViewModel 数据在配置更改(如旋转)时会丢失
C. ViewModel 只能被一个 Fragment 共享
D. ViewModelProvider 负责 ViewModel 的实例化和生命周期管理

答案:D
解析
A错,ViewModel 在 Activity/Fragment 销毁时才被回收;B错,配置变更不会丢失数据;C错,可用于多个 Fragment 共享同一 ViewModel(通过同一个 ViewModelStoreOwner);D对,ViewModelProvider 管理实例化及生命周期。


2. 下列哪个类实际负责存储 ViewModel 实例?

A. ViewModelProvider
B. ViewModelStore
C. ViewModelFactory
D. ViewModelStoreOwner

答案:B
解析:ViewModelStore 内部有 HashMap,真正存储 ViewModel 实例;Provider 是入口,Factory 用于创建,StoreOwner 持有 Store。


3. ViewModelProvider 获取 ViewModel 实例时,第二个参数 Factory 的主要作用是什么?

A. 指定 ViewModel 的生命周期
B. 控制 ViewModel 的回收时机
C. 控制 ViewModel 的创建方式
D. 存储 ViewModel 实例

答案:C
解析:Factory 用于自定义 ViewModel 的创建(如带参数构造等),不是生命周期或存储。


4. 配置变更(如屏幕旋转)时,ViewModel 不会被销毁,是因为以下哪项机制?

A. Application 单例模式
B. onRetainNonConfigurationInstance() 方法
C. Handler 机制
D. LiveData 机制

答案:B
解析:本质上 ViewModelStore 利用了 onRetainNonConfigurationInstance() 保存和恢复 ViewModelStore 实例,实现跨配置变更数据不丢失。


5. Activity 作为 ViewModelStoreOwner,实际提供 ViewModelStore 的逻辑位于哪个方法?

A. onCreate()
B. getViewModelStore()
C. onDestroy()
D. onSaveInstanceState()

答案:B
解析:Activity 通过实现 getViewModelStore() 提供 ViewModelStore 实例。


填空题

6. ViewModelProvider 持有的两个关键成员变量是 _______ 和 _______。

答案:ViewModelStore, Factory
解析:ViewModelProvider 需要这两者来实现获取和创建 ViewModel。


7. ViewModelStore 内部使用 _______ 数据结构来存储多个 ViewModel 实例。

答案:HashMap
解析:源码就是 private final HashMap<String, ViewModel>。


8. 当 Activity 被真正销毁(如按返回键退出),ViewModelStore 的 _______ 方法会被调用,清理所有 ViewModel。

答案:clear
解析:只有在 Activity 最终销毁才会调用 clear() 清理 ViewModel。


简答题

9. 简述为什么 ViewModel 不能直接通过 new 方式创建,而必须通过 ViewModelProvider 获取?

答案示例
因为直接 new 出来的 ViewModel 无法被系统纳入生命周期管理,也不能实现配置变更时的数据保留。ViewModelProvider 会自动管理实例复用、销毁和恢复,保证 ViewModel 在合适的生命周期内有效,并且避免出现多个重复 ViewModel 实例。


10. 画出以下对象之间的关系链:Activity(或 Fragment)— ViewModelProvider — ViewModelStore — ViewModel。

答案参考

Activity/FragmentimplementsViewModelStoreOwnergetViewModelStore()
    ↓
ViewModelStoreHashMap<String, ViewModel>
    ↓
ViewModel

编程/伪代码题

11. 写出自定义 ViewModelFactory 用于创建带参数构造函数的 ViewModel 的基本代码结构。

答案参考

class MyViewModel(val param: String) : ViewModel() {
    // ...
}

class MyViewModelFactory(private val param: String) : ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(MyViewModel::class.java)) {
            return MyViewModel(param) as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}

// 用法
val factory = MyViewModelFactory("参数")
val vm = ViewModelProvider(this, factory).get(MyViewModel::class.java)