Android 知识小结 —— ViewModel(一)

Android 知识小结 —— ViewModel(一)

Android小彩虹2021-08-22 18:10:25250A+A-

零、前言

之前看过很多源码,写过很多笔记,可被问起的时候还是很难清晰描述出一个知识点的全貌。尤其是涉及源码的时候,缺乏清晰的结构化思维,一旦陷入代码调用顺序描述里,就是画地为牢了。于是我想再整理,描述一下自己对知识点的总结,如有遗漏、模糊、错误之处,希望路过的各位能指点一二,谢谢。

一、ViewModel 功能描述

本文内的 ViewModel 是指 Android Jetpack 中的 ViewModel 库

提纲:

  1. ViewModel 生命周期(对象创建到clear)
  2. ViewModel 可以实现 Fragment 数据共享
  3. 在 onClear 的时候清理数据
  4. 通过 setTagIfAbsent 可以在 ViewModel 中保存数据,如果是 Closeable 数据,还会在 clear 时自动关闭

正文:

ViewModel 是 Android 官方 MVVM 架构的基本组成部分,主要功能是管理 UI 相关数据,将「业务逻辑」提取出来,通常配合 LiveData 使用。ViewModel 的最大特点是其生命周期不受屏幕旋转等配置修改的影响,避免了页面重建导致的数据恢复问题。

ViewModel 通过 ViewModelProvider 创建,接收一个 ViewModelStoreOwner 参数,并通过 ViewModel 的 class 获取实例。可以推断出 ViewModel 的创建应该用到了反射。

homeViewModel = ViewModelProvider(owner).get(HomeViewModel::class.java)

ViewModelStoreOwner 提供 ViewModel 的生命周期,在一个生命周期内,每次 get 获取到的都是同一个 ViewModel 实例。如此一来,使用 Activity 作为 ViewModelStoreOwner 即可在 Activity 中的所有 Fragment 之间共享 ViewModel 中的数据。

ViewModel 的创建是在 ViewModelProvider 第一次 get 的时候,onClear 则是在 ViewModelStoreOwner 销毁的时候触发。我们可以在 onClear 的时候做一些清除工作。

ViewModel 有一套对象保存机制,用于扩展 ViewModel 的功能,分别对应 setTagIfAbsent 和 getTag。tag 支持泛型,通过 key 存取,比较特殊的一点是 tag 对象如果实现了 Closeable 接口,在 ViewModel clear 的时候对应 tag 的 close 方法也会触发。

二、部分源码分析

ViewModel 库本身的源码很少,本文对应的 2.2.0 版本只有 6 个文件:

ViewModel 的生命周期依赖 ViewModelStoreOwner,Android SDK 中的 ComponentActivity 和 Fragment 都实现了 ViewModelStoreOwner 接口。ViewModelStoreOwner 唯一的功能是提供一个 ViewModelStore:

public interface ViewModelStoreOwner {
    /**
     * Returns owned {@link ViewModelStore}
     *
     * @return a {@code ViewModelStore}
     */
    @NonNull
    ViewModelStore getViewModelStore();
}

ViewModelStore 内部是 HashMap,api 也相差不大,但在 put 和 clear 函数中,清除 HashMap 中的 ViewModel 之前会先调用 ViewModel 的 clear 方法。举个栗子:

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

ViewModelStore 通过 LifecycleOwner 绑定生命周期,Lifecycle 库的功能是提供统一的生命周期获取和监听。在 Lifecycle.Event.ON_DESTROY 的时候,只有不是因为配置变化导致的销毁才会触发 ViewModelStore 的 clear,也就实现了屏幕旋转时不重建 ViewModel 的功能:

getLifecycle().addObserver(new LifecycleEventObserver() {
    @Override
    public void onStateChanged(@NonNull LifecycleOwner source,
            @NonNull Lifecycle.Event event) {
        if (event == Lifecycle.Event.ON_DESTROY) {
            if (!isChangingConfigurations()) {
                getViewModelStore().clear();
            }
        }
    }
});

ViewModel 的 tag 机制 内部实现也是 HashMap:

private final Map<String, Object> mBagOfTags = new HashMap<>();

// 注意是 package-private 的
<T> T setTagIfAbsent(String key, T newValue) {
    T previous;
    synchronized (mBagOfTags) {
        previous = (T) mBagOfTags.get(key);
        if (previous == null) {
            mBagOfTags.put(key, newValue);
        }
    }
    T result = previous == null ? newValue : previous;
    if (mCleared) {
        // It is possible that we'll call close() multiple time
        // Closeable interface requires close method to be idem
        // "if the stream is already closed then invoking this 
        closeWithRuntimeException(result);
    }
    return result;
}

<T> T getTag(String key) {
    if (mBagOfTags == null) {
        return null;
    }
    synchronized (mBagOfTags) {
        return (T) mBagOfTags.get(key);
    }
}

代码通过加锁的方式保证了 HashMap 的线程安全,setTagIfAbsent 只有在 key 不存在的时候才会存 tag,否则存不上也没有通知结果,所以在使用的时候一定要自己 getTag 先判断一下是否 key 是否已存在。getTag 就简单很多,只是取值返回,key 不存在则返回 null。

前面还提到 clear 的时候会清除可关闭的 tag,清除的方式就是遍历 HashMap。onCleard 回调发生在清除之后,如果使用了 tag 保存对象,在 onCleard 时应该已经关闭过了,但对象还能获取到。

@MainThread
final void clear() {
    mCleared = true;
    // Since clear() is final, this method is still called on mock objects
    // and in those cases, mBagOfTags is null. It'll always be empty though
    // because setTagIfAbsent and getTag are not final so we can skip
    // clearing it
    if (mBagOfTags != null) {
        synchronized (mBagOfTags) {
            for (Object value : mBagOfTags.values()) {
                // see comment for the similar call in setTagIfAbsent
                closeWithRuntimeException(value);
            }
        }
    }
    onCleared();
}

三、定制 ViewModel

虽然有根据业务内容定制 ViewModel 的可能,但本文不假设业务场景,只说技术实现,所以关于 ViewModel 的定制也从源码和官方扩展库讲起。

AndroidViewModel 是官方提供的拥有 application 的 ViewModel,这里的 application 是通过构造函数传进去的,但我们创建 ViewModel 的方式并不会调用构造函数。

控制的方式在 ViewModelProvider 的 Factory 中,Factory 负责创建 ViewModel 对象,默认的 Factory 就提供了对 AndroidViewModel 的支持。

public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
    this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
            ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
            : NewInstanceFactory.getInstance());
}

Activity 和 Fragment 都实现了 HasDefaultViewModelProviderFactory,也是要通过他们提供的 Factory 才能获取到 application,NewInstanceFactory.getInstance() 则只能创建普通的 ViewModel。通过反射创建对象实现在 AndroidViewModelFactory 中。

@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
    if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
        //noinspection TryWithIdenticalCatches
        try {
            return modelClass.getConstructor(Application.class).newInstance(mApplication)
        } catch (NoSuchMethodException e) {
            throw new RuntimeException("Cannot create an instance of " + modelClass, e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Cannot create an instance of " + modelClass, e);
        } catch (InstantiationException e) {
            throw new RuntimeException("Cannot create an instance of " + modelClass, e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException("Cannot create an instance of " + modelClass, e);
        }
    }
    return super.create(modelClass);
}

通过 getConstructor 获取指定的构造函数,就避免了开发者手动指定。如果项目中需要再构造 ViewModel 的时候传值进去,可以让 BaseFragment 和 BaseActivity 实现 HasDefaultViewModelProviderFactory 接口,自定义 Factory 的 create 函数,按需寻找构造函数。

题外话:HILT 向 ViewModel 中注入对象的时候,也可以通过 ViewModel 的构造函数传入,实现方式估计也是相似的。留一个问题,看到 HILT 的时候补上。

再看一个关于 tag 的使用,源码来自协程对 ViewModel 的扩展库。在常用协程 Scope 中,viewModelScope 应该是使用频率最高的了,viewModelScope 是一个 ViewModel 的扩展函数,其内容是这样的:

val ViewModel.viewModelScope: CoroutineScope
        get() {
            val scope: CoroutineScope? = this.getTag(JOB_KEY)
            if (scope != null) {
                return scope
            }
            return setTagIfAbsent(JOB_KEY,
                CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate))
        }

internal class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope {
    override val coroutineContext: CoroutineContext = context

    override fun close() {
        coroutineContext.cancel()
    }
}

协程的部分不展开了,获取 ViewModelScope 的流程是先 getTag 获取指定的 key,如果不为空就直接返回,否则会新建一个 CloseableCoroutineScope 并通过 setTagIfAbsent 存到 ViewModel 里。CloseableCoroutineScope 会在 close 的时候取消当前 Scope 内的协程,实现了异步任务自动取消的功能。

四、未完待续

熟悉 ViewModel 的朋友应该意识到本文缺了什么了,关联 Fragment 生命周期的具体实现方式还没提到,进一步展开的话,Activity 和 Fragment 中的实现不算相同,尤其是 Fragment 中的实现方式值得单独写一下,就分成(一)(二)两篇吧。

本文的关联知识点主要有 LiveData、Lifecycle、Kotlin-Coroutine 三部分,接下来也都会写到。

点击这里复制本文地址 以上内容由权冠洲的博客整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!

支持Ctrl+Enter提交

联系我们| 本站介绍| 留言建议 | 交换友链 | 域名展示
本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除

权冠洲的博客 © All Rights Reserved.  Copyright quanguanzhou.top All Rights Reserved
苏公网安备 32030302000848号   苏ICP备20033101号-1
本网站由 提供CDN/云存储服务

联系我们