Android-Jetpack笔记-ViewModel

723 阅读4分钟

ViewModel处于数据逻辑层,他的生命周期贯穿整个宿主,如act因屏幕旋转销毁重建时,其依然存活,只有act.finish后,才会自动销毁,因此可以用他来维持宿主的数据状态。现在比较流行的方式是把他当做唯一数据源来驱动UI展示:

view层:          view      (act / fragment)
数据逻辑层:     viewModel
数据源:        repository   (db / network)

另外,还可以通过共享viewModel实现页面间通信,如两个fragment共享act的一个viewModel。

Jetpack笔记代码

本文源码基于SDK 29

使用

引入依赖:

def lifecycle_version = "2.2.0"
//extensions包含Lifecycles、LiveData、ViewModel
implementation "android.arch.lifecycle:extensions:$lifecycle_version"

创建ViewModel

class CommonViewModel extends ViewModel {
    public MutableLiveData<String> text = new MutableLiveData<>();
}

在布局文件中使用,

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <data>
        <import type="com.holiday.jetpackstudy.viewmodel_livedata.CommonViewModel" />
        <variable
            name="commonViewModel"
            type="CommonViewModel" />
    </data>

    <TextView
          android:id="@+id/tv_text"
          android:text="@{commonViewModel.text}"
          android:textSize="@dimen/tv_text_size" />
</layout>

在act中使用,

class ViewModelActivity extends BaseActivity {
    CommonViewModel mCommonViewModel;

    void onCreate(Bundle savedInstanceState) {
        //传this,基于act创建viewModel
        mCommonViewModel = ViewModelProviders.of(this).get(CommonViewModel.class);
        mBinding.setCommonViewModel(mCommonViewModel);
        //观察数据变化
        mCommonViewModel.text.observe(this, new Observer<String>() {
            @Override
            public void onChanged(String s) {
                //更新UI
                mBinding.tvText.setText(s);
            }
        });
        //旋转屏幕重建act后,viewModel还是同一个实例
        QrLog.e(String.valueOf(mCommonViewModel.hashCode()));
    }

    //点击按钮改变数据
    public void changeData(View view) {
        mCommonViewModel.text.setValue(String.valueOf(System.currentTimeMillis()));
    }
}

在onCreate方法打印ViewModel的hashCode,可见屏幕旋转导致act重建时,mCommonViewModel还是同一个实例,

原理

ViewModelProviders.of(this).get(CommonViewModel.class)为入口,先看ViewModelProviders.of

//ViewModelProviders.java
ViewModelProvider of(FragmentActivity activity) {
    return of(activity, null);
}

ViewModelProvider of(FragmentActivity activity, Factory factory) {
    if (factory == null) {
        //工厂类,用来反射创建viewModel
        factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
    }
    //ViewModelStore用来存储ViewModel
    return new ViewModelProvider(activity.getViewModelStore(), factory);
}

这里只需知道ViewModelProviders.of得到了当前act的ViewModelProvider,接着看get方法,

//ViewModelProvider.java
<T extends ViewModel> T get(Class<T> modelClass) {
    //获取类全名
    String canonicalName = modelClass.getCanonicalName();
    //加上前缀,得到唯一标识key
    return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}

<T extends ViewModel> T get(String key, Class<T> modelClass) {
    //获取viewModel
    ViewModel viewModel = mViewModelStore.get(key);
    if (modelClass.isInstance(viewModel)) {
        //如果已经存在,则直接返回
        return (T) viewModel;
    } else {
        //忽略
    }
    if (mFactory instanceof KeyedFactory) {
        viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
    } else {
        //调用前边提到的工厂类,反射创建viewModel
        viewModel = (mFactory).create(modelClass);
    }
    //存储viewModel
    mViewModelStore.put(key, viewModel);
    return (T) viewModel;
}

看起来代码不是很多,那么viewModel是如何实现act重建后依然存活的呢?

首先viewModel存储在mViewModelStore,而这个store是创建ViewModelProvider时传进来的,即activity.getViewModelStore()

//ComponentActivity.java
ViewModelStore getViewModelStore() {
    if (mViewModelStore == null) {
        NonConfigurationInstances nc =
            (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            // Restore the ViewModelStore from NonConfigurationInstances
            //act屏幕旋转重建时,在这里取出ViewModelStore
            mViewModelStore = nc.viewModelStore;
        }
        if (mViewModelStore == null) {
            //act第一次创建时,在这里创建了ViewModelStore
            mViewModelStore = new ViewModelStore();
        }
    }
    return mViewModelStore;
}

NonConfigurationInstances又是如何存活的呢?

//Activity.java
Object getLastNonConfigurationInstance() {
    return mLastNonConfigurationInstances != null
        ? mLastNonConfigurationInstances.activity : null;
}

mLastNonConfigurationInstances在attach的时候获取值,

//Activity.java
void attach() {
    mLastNonConfigurationInstances = lastNonConfigurationInstances;
}

Activity.attachActivityThread.performLaunchActivity中被调用,

//ActivityThread.java
Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    activity.attach(appContext, this, getInstrumentation(), r.token,
                    r.ident, app, r.intent, r.activityInfo, title, r.parent,
                    r.embeddedID, r.lastNonConfigurationInstances, config,
                    r.referrer, r.voiceInteractor, window, r.configCallback,
                    r.assistToken);
}

可见来源于ActivityClientRecord.lastNonConfigurationInstances,查找一下可知,r.lastNonConfigurationInstancesperformDestroyActivity时赋值,

//ActivityThread.java
performDestroyActivity(){
    r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances();
}

这边有点绕,再看到activity,

//Activity.java
NonConfigurationInstances retainNonConfigurationInstances() {
    //具体实现看下一个方法
    Object activity = onRetainNonConfigurationInstance();
    onConfigurationInstances nci = new NonConfigurationInstances();
    //nci.activity存储的是ComponentActivity里边的NonConfigurationInstances
    nci.activity = activity;
    return nci;
}

//ComponentActivity.java
public final Object onRetainNonConfigurationInstance() {
    NonConfigurationInstances nci = new NonConfigurationInstances();
    //这里的NonConfigurationInstances存储了viewModelStore
    nci.viewModelStore = viewModelStore;
    return nci;
}

至此,简单的概括一下就是,

Activity销毁时,借助ActivityClientRcord来间接保存viewModelStore;Activity重建时,从ActivityClientRcord中间接取出viewModelStore

而所有ActivityClientRcord又被存储在ActivityThread的成员变量里能长期存活:

//ActivityThread.java
final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>();

至于act退出时viewModel可以自动销毁,是因为ComponentActivity里添加了一个观察者:

//ComponentActivity.java
ComponentActivity() {
    getLifecycle().addObserver(new LifecycleEventObserver() {
        @Override
        public void onStateChanged(LifecycleOwner source,Lifecycle.Event event) {
            if (event == Lifecycle.Event.ON_DESTROY) {
                //如果是屏幕旋转或语言切换引起的destroy,则无需回调
                if (!isChangingConfigurations()) {
                    //内部会遍历所有ViewModel,回调onCleared
                    getViewModelStore().clear();
                }
            }
        }
    });
}

关于Lifecycles,可以阅读我的早些的笔记。

优缺点

  • 优点:
    • 页面退出时,自动销毁
    • 屏幕旋转、语言切换后数据不丢失,而onSaveInstanceState在面对复杂数据时需要序列化
    • 不持有view层,方便单元测试
  • 缺点:
    • 虽然要比onSaveInstanceState简单,但是viewModel只能在屏幕旋转和语言切换后的页面重建维持数据,当页面意外销毁时数据无法恢复,而这点onSaveInstanceState可以做到,关于viewModel如何实现这一点,可以看我的下一篇笔记。

参考文章

本文使用 mdnice 排版