什么是ViewModel

ViewModel是Android Jetpack架构组件中的一部分,它的设计目的是以注重生命周期的方式存储和管理界面相关的数据
在ViewModel出现之前,我们通常将数据直接存储在Activity/Fragment当中
直接将数据存储到Activity或者Fragment当中会出现什么问题了?

1.当配置变更,比如屏幕旋转,或者是应用切换语言/应用内切换黑白模式
答案:这种操作,当前的Activity的会销毁重建,存储在Activity当中的所有的临时数据都会随之丢失。那如果我们想要恢复数据,就需要使用onSaveInstanceState()
来保存或者恢复数据,但是这个方法只适合存储少量的可序列化的数据,不适合存储大量数据。

2.关于内存泄漏
如果一个Activity中启动了一个异步任务,比如说网络请求,并且在任务完成之前Activity被销毁了,那由于异步任务当中持有对Activity的引用,垃圾回收无法销毁这个Activity,从而导致内存泄漏。

3.职责过重
Activity/Fragment本事界面控制器,应该只负责处理UI和用户交互
如果同时处理网络数据数据库加载,处理业务逻辑,会导致类变得臃肿,难以测试和维护

正是由于上面这些问题,google工程师开发团队引入了ViewModel的概念
ViewModel本身对象存在的时间周期要比Activity/Fragment更长,它在配置更改的时候会自动保留,并在新的Activity或者Fragment创建的时候重新关联。

为什么设计出AndroidViewModel

我们先从代码的角度来分析这个问题,从下面看出,AndroidViewModel是继承与ViewModel,并且将Application作为构造函数的一个参数传递到ViewModel当中
从代码片段看出,这个AndroidViewModel有了使用应用上下文的能力

open class AndroidViewModel(private val application: Application) : ViewModel() {

    /**
     * Return the application.
     */
    @Suppress("UNCHECKED_CAST")
    open fun <T : Application> getApplication(): T {
        return application as T
    }
}

接下来我们分析一下为什么Google团队要专门设计一个AndroidViewModel出来
在标准的ViewModel中,我们是强烈建议不要持有对Activity,View的引用,这个我们在文章的上面有介绍,因为会导致内存泄漏的发生。
但是了,有的时候就是需要这个上下文去访问应用资源,或者系统服务
那google就给出了标准答案,这样就可以使用AndroidViewModel.

有没有很好奇,我们创建一个自定义的AndroidViewModel
这个AndroidViewModel中的Application是怎么传进去的?
看到这通过文字可能无法领会其意思,直接来看代码

class MyViewModel(application: Application) : AndroidViewModel(application) {
    // 访问字符串资源
    fun getStringResource(): String {
        return getApplication<Application>().getString(R.string.app_name)
    }
}

比如上面的,我们自定义一个MyViewModel,属于AndroidViewModel的viewmodel
再来看看怎么进行实例化的操作

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        MyViewModel myViewModel = new ViewModelProvider(this).get(MyViewModel.class);
    }
}

MyViewModel myViewModel = new ViewModelProvider(this).get(MyViewModel.class);这个方法并没有去传application
那怎么就能在model里面使用了?

来分析ViewModelProvider(this).这个在干什么?

public constructor(
        owner: ViewModelStoreOwner
    ) : this(owner.viewModelStore, defaultFactory(owner), defaultCreationExtras(owner))

internal fun defaultFactory(owner: ViewModelStoreOwner): Factory =
                if (owner is HasDefaultViewModelProviderFactory)
                    owner.defaultViewModelProviderFactory else instance

从上面的源码可以看出会有个默认的工厂去创建相关实例:defaultFactory(owner),
HasDefaultViewModelProviderFactory这是个代理工厂接口,会发现activity,fragment会实现这个接口 我们来看具体的代理工厂是什么?

	@NonNull
    @Override
    public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
        if (getApplication() == null) {
            throw new IllegalStateException("Your activity is not yet attached 	to the "
                    + "Application instance. You can't request ViewModel before onCreate call.");
        }
        if (mDefaultFactory == null) {
            mDefaultFactory = new SavedStateViewModelFactory(
                    getApplication(),
                    this,
                    getIntent() != null ? getIntent().getExtras() : null);
        }
        return mDefaultFactory;
    }

SavedStateViewModelFactory 这个就是具体的代理工厂
在结合ViewModelProvider(this).get(MyViewModel.class)这个get方法
就会走到代理工厂的create的方法 ,也就是SavedStateViewModelFactory 的create方法,下面就是具体的源码

override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
        val key = extras[ViewModelProvider.NewInstanceFactory.VIEW_MODEL_KEY]
            ?: throw IllegalStateException(
                "VIEW_MODEL_KEY must always be provided by ViewModelProvider"
            )

        return if (extras[SAVED_STATE_REGISTRY_OWNER_KEY] != null &&
            extras[VIEW_MODEL_STORE_OWNER_KEY] != null) {
            val application = extras[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY]
            val isAndroidViewModel = AndroidViewModel::class.java.isAssignableFrom(modelClass)
            val constructor: Constructor<T>? = if (isAndroidViewModel && application != null) {
                findMatchingConstructor(modelClass, ANDROID_VIEWMODEL_SIGNATURE)
            } else {
                findMatchingConstructor(modelClass, VIEWMODEL_SIGNATURE)
            }
            // doesn't need SavedStateHandle
            if (constructor == null) {
                return factory.create(modelClass, extras)
            }
            val viewModel = if (isAndroidViewModel && application != null) {
                newInstance(modelClass, constructor, application, extras.createSavedStateHandle())
            } else {
                newInstance(modelClass, constructor, extras.createSavedStateHandle())
            }
            viewModel
        } else {
            val viewModel = if (lifecycle != null) {
                create(key, modelClass)
            } else {
                throw IllegalStateException("SAVED_STATE_REGISTRY_OWNER_KEY and" +
                    "VIEW_MODEL_STORE_OWNER_KEY must be provided in the creation extras to" +
                    "successfully create a ViewModel.")
            }
            viewModel
        }
    }

通过上面的代码,挑重点来看,这个代理工厂最终会走到ViewModelProvider里面提供的ViewModelProvider.AndroidViewModelFactory,最终会走到AndroidViewModelFactory的create方法当中

ViewModelProvider.AndroidViewModelFactory

@Suppress("DocumentExceptions")
        override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
            return if (application != null) {
                create(modelClass)
            } else {
                val application = extras[APPLICATION_KEY]
                if (application != null) {
                    create(modelClass, application)
                } else {
                    // For AndroidViewModels, CreationExtras must have an application set
                    if (AndroidViewModel::class.java.isAssignableFrom(modelClass)) {
                        throw IllegalArgumentException(
                            "CreationExtras must have an application by `APPLICATION_KEY`"
                        )
                    }
                    super.create(modelClass)
                }
            }
        }

val application = extras[APPLICATION_KEY]
                if (application != null) {
                    create(modelClass, application)
                }

从这就可以看出来这个application就传到这里来了,我们就可以在这里用application了,实际上这个application其实就是activity/fragment里面直接获取的,通过viewmodel这中工厂设计模式巧妙的传到我们自定义的AndroidViewModel当中

class MyViewModel(application: Application) : AndroidViewModel(application) {
// 访问字符串资源
fun getStringResource(): String {
return getApplication().getString(R.string.app_name)
}
}

从一个小小的AndroidViewModel中的application的传值就可以让我们了解到ViewModelProvider创建viewmodel的神奇之处,这里面的这种工厂设计理论可以让我借鉴到实际业务开发当中

为什么屏幕旋转或者切换语言ViewModel的实例会是同一个

要回答这个问题,我们先来了解一下ViewModelStore这个类

open class ViewModelStore {

    private val map = mutableMapOf<String, ViewModel>()

    /**
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    fun put(key: String, viewModel: ViewModel) {
        val oldViewModel = map.put(key, viewModel)
        oldViewModel?.onCleared()
    }

    /**
     * Returns the `ViewModel` mapped to the given `key` or null if none exists.
     */
    /**
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    operator fun get(key: String): ViewModel? {
        return map[key]
    }

    /**
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    fun keys(): Set<String> {
        return HashSet(map.keys)
    }

    /**
     * Clears internal storage and notifies `ViewModel`s that they are no longer used.
     */
    fun clear() {
        for (vm in map.values) {
            vm.clear()
        }
        map.clear()
    }
}

上面这个类在干什么?
这个 ViewModelStore 类是 Android Jetpack Lifecycle 库中的核心组件,它的主要作用是:
存储和管理ViewModel实例的生命周期
ViewModel的存储 使用 MutableMap<String, ViewModel> 来存储 ViewModel 实例
每个 ViewModel 通过唯一的 key 进行标识和检索,
这个key类似与 // key 类似: “androidx.lifecycle.ViewModelProvider.DefaultKey:com.example.MyActivity”

所以具备了当配置发生变化(如屏幕旋转)时,保持 ViewModel 实例不被销毁
当组件真正被销毁时,调用 clear() 方法清理所有 ViewModel
屏幕旋转或者切语言理论是不是正真意义上的销毁,原生代码会通过标记来说这一点 这里不具体分析
所以当屏幕旋转或者是切换语言,ViewModel的实例是同一个。

为什么使用ViewModel会发生数据倒灌

因为ViewModel 的存活机制,配置更新时候比如旋转屏幕或者切换语言viewmodel的实例时同一个 这个会发生数据倒灌的前提

下面来看一下LiveData的源码

// LiveData.java
public abstract class LiveData<T> {
    private int mVersion = START_VERSION; // 版本号,初始为-1
    
    // 设置数据时版本号+1
    @MainThread
    protected void setValue(T value) {
        mVersion++;  // 关键:每次设置数据版本号增加
        mData = value;
        dispatchingValue(null);
    }
    
    // 观察者包装类
    private abstract class ObserverWrapper {
        final Observer<? super T> mObserver;
        boolean mActive = false;
        int mLastVersion = START_VERSION; // 观察者最后接收的版本
        
        void activeStateChanged(boolean newActive) {
            if (newActive) {
                considerNotify(); // 激活时检查是否需要通知
            }
        }
        
        void considerNotify() {
            // 关键判断:如果观察者的版本落后于LiveData的当前版本
            if (mLastVersion >= mVersion) {
                return; // 版本一致或更新,不通知
            }
            mLastVersion = mVersion;
            mObserver.onChanged((T) mData); // 数据倒灌发生在这里!
        }
    }
}

从上面看出当配置发生变化 mLastVersion变成初始值-1,mVersion的值变成0最终走到
mObserver.onChanged((T) mData); // 数据倒灌发生在这里!

怎么解决这个数据倒灌的问题了?
小编这里推荐使用SingleLiveEvent

public class SingleLiveEvent<T> extends MutableLiveData<T> {
    private static final String TAG = "SingleLiveEvent";

    private final AtomicBoolean mPending = new AtomicBoolean(false);

    @MainThread
    public void observe(@NonNull LifecycleOwner owner, @NonNull final Observer<? super T> observer) {

        if (hasActiveObservers()) {
           KLog.w(TAG, "Multiple observers registered but only one will be notified of changes.");
        }

        // Observe the internal MutableLiveData
        super.observe(owner, new Observer<T>() {
            @Override
            public void onChanged(@Nullable T t) {
                if (mPending.compareAndSet(true, false)) {
                    observer.onChanged(t);
                }
            }
        });
    }

    @MainThread
    public void setValue(@Nullable T t) {
        mPending.set(true);
        super.setValue(t);
    }

    /**
     * Used for cases where T is Void, to make calls cleaner.
     */
    @MainThread
    public void call() {
        setValue(null);
    }
}

具体的LiveData的分析会在下个章节详细的讲解。

Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐