Android Jetpack-ViewModel

558 阅读7分钟

一、概述

为什么需要ViewModel或者说ViewModel的优势是什么?\

如果Activity或者Fragment销毁或者重建,存储在其中的数据会丢失,对于简单的数据比如Activity可以使用onSaveInstanceState()方法来从onCreate()中恢复数据,但这个方法只适合可以序列化再反序列化的少量数据,而不适合较大的数据。

另外一个问题是,界面经常需要异步操作,比如网络请求等,当界面销毁时,往往需要手动维护异步取消的动作,这无疑显得特别繁琐。并且把所有代码都写在界面中,会变得特别臃肿。

于是就需要将视图数据与界面分离,让层次清晰且高效。且ViewModel可以维护自己的生命周期,不需要手动操作,这无疑大大降低开发难度。

二、简单实现ViewModel

简单的ViewModel实现

class UserInfoViewModel : ViewModel() {

    //延迟创建LiveData对象
    val userInfoLiveData: MutableLiveData<UserInfo> by lazy {
        //创建LiveData
        MutableLiveData<UserInfo>()
    }

    /**
     * 请求用户信息
     */
    fun requestUserInfo() {
        //伪代码:请求到了用户信息
        var userInfo = UserInfo("zhangsan", 18)
        //将请求到的值userInfo设置给LiveData,更新数据
        userInfoLiveData.value = userInfo
    }
    
}

在Activity中调用

class MainActivity : AppCompatActivity() {

    //创建ViewModel对象
    private val userInfoViewModel: UserInfoViewModel by lazy {
        UserInfoViewModel()
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        var textview = findViewById<TextView>(R.id.tv_start)

        //调用viewmodel的方法请求数据
        userInfoViewModel.requestUserInfo()
        //通知修改数据
        userInfoViewModel.userInfoLiveData.observe(this, Observer<UserInfo> {
            //拿到了请求到的UserInfo对象,通知修改数据
            textview.text = it?.name
        })
    }
}

需要注意的点:

  • 如果Activity重新创建,它接收的ViewModel实例与第一个Activity创建的实例相同。
  • 当Activity完成(不能简单理解为destroy)时,框架会调用ViewModelonCleared()方法,以便它可以清理资源。
  • ViewModel的生命周期比视图的生命周期长,所以ViewModel绝不能持有视图、Lifecycle或Activity的上下文等引用,否则就会造成内存泄漏。
  • ViewModel绝不能观察对生命周期感知型对象(如LiveData对象)的更改。
  • 如果ViewModel需要持有上下文,只能是Application的上下文,扩展AndroidViewModel类并通过构造方法传入,如下代码:
/**
 * 继承AndroidViewModel,创建时需要通过构造方法传入Application对象
 */
class CustomApplicationViewModel(application: Application) : AndroidViewModel(application) {

    override fun onCleared() {
        super.onCleared()
    }
}

//因为这个ViewModel持有Application的引用,所以它的生命周期会很长
//于是可以在Application类中创建全局的ViewModel
class App : Application() {

    //创建全局ViewModel对象
    val mCustomApplicationViewModel: CustomApplicationViewModel by lazy {
        CustomApplicationViewModel(this)
    }

    override fun onCreate() {
        super.onCreate()
    }
}

//调用
var app: App = application as App
app.mCustomApplicationViewModel...

三、ViewModel的初始化方式

1、懒加载的初始化方式

这种方式是最简单的方式,但是封装架构的时候一般不这样用。代码见本文第二章节最上面ViewModel的代码。

2、简单工厂初始化ViewModel

ViewModel的类

class UserInfoViewModel : ViewModel() {
...
}

创建ViewModel对象

var userInfoViewModel = ViewModelProvider.NewInstanceFactory()
.create(UserInfoViewModel::class.java)

或另一种写法

var userInfoViewModel =
    ViewModelProvider(this).get(UserInfoViewModel::class.java)

二者的区别create是创建一个新的实例,而get是先从HashMap中找,找不到就创建新的实例。看下源码也就知道为什么重新创建的ViewModel是同一个对象。

public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
    //ViewModelStore底层是HashMap集合
    //通过key从集合中取出ViewModel对象
    ViewModel viewModel = mViewModelStore.get(key);
    //判断这个ViewModel类的实例对象是否是需要ViewModel类的实例对象
    if (modelClass.isInstance(viewModel)) {
        if (mFactory instanceof OnRequeryFactory) {
            ((OnRequeryFactory) mFactory).onRequery(viewModel);
        }
        //如果是直接返回
        return (T) viewModel;
    } else {
        //noinspection StatementWithEmptyBody
        if (viewModel != null) {
            // TODO: log a warning.
        }
    }
    //如果不存在就创建
    if (mFactory instanceof KeyedFactory) {
        viewModel = ((KeyedFactory) mFactory).create(key, modelClass);
    } else {
        viewModel = mFactory.create(modelClass);
    }
    //并且将创建的ViewModel实例对象存在HashMap中
    mViewModelStore.put(key, viewModel);
    return (T) viewModel;
}

3、使用ktx扩展(推荐)

添加依赖

implementation 'androidx.activity:activity-ktx:1.4.0'
implementation 'androidx.fragment:fragment-ktx:1.4.1'

ViewModel的类

class UserInfoViewModel : ViewModel() {
...
}

在Activity中创建ViewModel的方式

class MainActivity :AppCompatActivity() {

    //通过ktx扩展创建ViewModel对象
    private  val userInfoViewModel by viewModels<UserInfoViewModel>{
        ViewModelProvider.NewInstanceFactory()
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        var textview = findViewById<TextView>(R.id.tv_start)

        //调用viewmodel的方法请求数据
        userInfoViewModel.requestUserInfo()
        //通知修改数据
        userInfoViewModel.userInfoLiveData.observe(this, Observer<UserInfo> {
            //拿到了请求到的UserInfo对象,通知修改数据
            textview.text = it?.name
        })
    }
}

在Fragment中创建ViewModel的方式

class CustomFragment:Fragment() {

    //通过ktx扩展创建ViewModel对象
    private  val userInfoViewModel by activityViewModels<UserInfoViewModel>{
        ViewModelProvider.NewInstanceFactory()
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

    }
}

四、ViewModel的生命周期

ViewModel对象存在的时间范围是获取 ViewModel时传递给 ViewModelProvider的 LifecycleViewModel将一直留在内存中,直到限定其存在时间范围的 Lifecycle永久消失:对于 activity,是在 activity 完成时;而对于 fragment,是在 fragment 分离时。

如下官方给的生命周期示例图:

image.png

上图说明了 Activity 经历屏幕旋转而后结束时所处的各种生命周期状态。该图还在 Activity 生命周期的旁边显示了 ViewModel 的生命周期。此图表说明了 Activity 的各种状态。这些基本状态同样适用于 Fragment 的生命周期。所以不要简单理解Activity的完成状态就是Destroy状态,上图屏幕旋转会经历一次onDestroy

通过上图可以看到,ViewModel创建后,它并不关心onDestroy之前的生命周期,它只关系视图控件(Activity,Fragment等)何时退出,我们跟踪一下源码。

1、Activity中ViewModel何时退出

首先通过上面的分析,可以知道ViewModel创建时会放入一个HashMap集合(ViewModelStore.mMap)中,调用的时候也是从这个集合中取,那么何时从这个集合中删除?

//ComponentActivity部分源码
//ComponentActivity的构造方法,创建Activity时调用
public ComponentActivity() {
    ...
    getLifecycle().addObserver(new LifecycleEventObserver() {
        @Override
        public void onStateChanged(@NonNull LifecycleOwner source,
                @NonNull Lifecycle.Event event) {
            //当收到Activity的ON_DESTROY事件时
            if (event == Lifecycle.Event.ON_DESTROY) {
                // Clear out the available context
                mContextAwareHelper.clearAvailableContext();
                // And clear the ViewModelStore
                //isChangingConfigurations()永远返回false
                if (!isChangingConfigurations()) {
                    //清除ViewModelStore
                    getViewModelStore().clear();
                }
            }
        }
    });
    getLifecycle().addObserver(new LifecycleEventObserver() {
        @Override
        public void onStateChanged(@NonNull LifecycleOwner source,
                @NonNull Lifecycle.Event event) {
            //担保ViewModelStore,如果不是正常的onDestroy这里会恢复ViewModelStore
            ensureViewModelStore();
            getLifecycle().removeObserver(this);
        }
    });
    ...
 }

可以看到当触发onDestroy事件时,ViewModel的集合确实会被清除掉,但是下面又有担保的方式恢复,看下担保恢复的方法:

@SuppressWarnings("WeakerAccess") /* synthetic access */
void ensureViewModelStore() {
    if (mViewModelStore == null) {
        //取出之前配置的实例
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            //恢复ViewModelStore
            // Restore the ViewModelStore from NonConfigurationInstances
            mViewModelStore = nc.viewModelStore;
        }
        //如果ViewModel还是null,创建新的ViewModelStore给这个新的Activity用
        if (mViewModelStore == null) {
            mViewModelStore = new ViewModelStore();
        }
    }
}

可以看到配置的实例中如果还有就取出来重新用,如果没有就创建一个新的ViewModelStore给新的Activity用。继续跟源码,跟到Activity类就是系统方法了,并没有找到答案。

@Nullable
public Object getLastNonConfigurationInstance() {
    //如果不为null返回一个Activity实例
    return mLastNonConfigurationInstances != null
            ? mLastNonConfigurationInstances.activity : null;
}

//给上面的Activity实例赋值的地方
...
NonConfigurationInstances retainNonConfigurationInstances() {
    Object activity = onRetainNonConfigurationInstance();
...


/**
 * Called by the system, as part of destroying an
 * activity due to a configuration change, ...
 */
public Object onRetainNonConfigurationInstance() {
    return null;   //系统调用的方法,只能看到null
}

2、Fragment中ViewModel何时退出

与上面的原理类似,看ViewModelStore何时移除

//FragmentStateManager
//销毁时的方法
void destroy() {
    ...
    //是否Fragment应该被移除
    boolean beingRemoved = mFragment.mRemoving && !mFragment.isInBackStack();
    // Clear any previous saved state
    if (beingRemoved && !mFragment.mBeingSaved) {
        mFragmentStore.setSavedState(mFragment.mWho, null);
    }
    //Fragment是否应该被销毁
    boolean shouldDestroy = beingRemoved
            || mFragmentStore.getNonConfig().shouldDestroy(mFragment);
    //如果应该被销毁
    if (shouldDestroy) {
        FragmentHostCallback<?> host = mFragment.mHost;
        boolean shouldClear;
        if (host instanceof ViewModelStoreOwner) {
            shouldClear = mFragmentStore.getNonConfig().isCleared();
        } else if (host.getContext() instanceof Activity) {
            Activity activity = (Activity) host.getContext();
            shouldClear = !activity.isChangingConfigurations();
        } else {
            shouldClear = true;
        }
        if ((beingRemoved && !mFragment.mBeingSaved) || shouldClear) {
            //移除的关键代码
            mFragmentStore.getNonConfig().clearNonConfigState(mFragment);
        }
       ...
}
void clearNonConfigState(@NonNull Fragment f) {
    if (FragmentManager.isLoggingEnabled(Log.DEBUG)) {
        Log.d(TAG, "Clearing non-config state for " + f);
    }
    clearNonConfigStateInternal(f.mWho);
}

void clearNonConfigState(@NonNull String who) {
    if (FragmentManager.isLoggingEnabled(Log.DEBUG)) {
        Log.d(TAG, "Clearing non-config state for saved state of Fragment " + who);
    }
    clearNonConfigStateInternal(who);
}

private void clearNonConfigStateInternal(@NonNull String who) {
    // Clear and remove the Fragment's child non config state
    FragmentManagerViewModel childNonConfig = mChildNonConfigs.get(who);
    if (childNonConfig != null) {
        childNonConfig.onCleared();
        mChildNonConfigs.remove(who);
    }
    // Clear and remove the Fragment's ViewModelStore
    ViewModelStore viewModelStore = mViewModelStores.get(who);
    if (viewModelStore != null) {
        //移除ViewModelStore
        viewModelStore.clear();
        mViewModelStores.remove(who);
    }
}

五、在Fragment之间共享数据

Activity 中的两个或更多 Fragment 需要相互通信是一种很常见的现象。多个 fragment 可以在其 activity 范围内共享 ViewModel存储的数据。 先看下官方的示例代码,这里做了精简:

class SharedViewModel : ViewModel() {
     //创建LiveData
    val selected = MutableLiveData<Item>()

    //为LiveData赋值
    fun select(item: Item) {
        selected.value = item
    }
}

class ListFragment : Fragment() {
    //创建(获取)ViewModel对象
    private val model: SharedViewModel by activityViewModels()
    ...
}

class DetailFragment : Fragment() {
    //创建(获取)ViewModel对象
    private val model: SharedViewModel by activityViewModels()
    ...
}

请注意,这两个 Fragment 都会检索包含它们的 Activity。这样,当这两个 Fragment 各自获取 ViewModelProvider时,它们会收到相同的 SharedViewModel 实例(其范围限定为该 Activity)。

此方法具有以下优势:

  • Activity 不需要执行任何操作,也不需要对此通信有任何了解。
  • 除了 SharedViewModel 约定之外,Fragment 不需要相互了解。如果其中一个 Fragment 消失,另一个 Fragment 将继续照常工作。
  • 每个 Fragment 都有自己的生命周期,而不受另一个 Fragment 的生命周期的影响。如果一个 Fragment 替换另一个 Fragment,界面将继续工作而没有任何问题。

简单看下源码是如何实现的,Fragment在Attach时会调用FragmentManager的下面的方法:

@SuppressWarnings("deprecation")
@SuppressLint("SyntheticAccessor")
void attachController(@NonNull FragmentHostCallback<?> host,
        @NonNull FragmentContainer container, @Nullable final Fragment parent) {
        ...
    // Get the FragmentManagerViewModel
    if (parent != null) {
        mNonConfig = parent.mFragmentManager.getChildNonConfig(parent);
    } else if (host instanceof ViewModelStoreOwner) {
        //获取ViewModelStore赋值给FragmentManager自己的viewModelStore
        ViewModelStore viewModelStore = ((ViewModelStoreOwner) host).getViewModelStore();
        mNonConfig = FragmentManagerViewModel.getInstance(viewModelStore);
    } else {
        mNonConfig = new FragmentManagerViewModel(false);
    }
       ...

getViewModelStore()方法获取的就是Activity的ViewModelStore

@NonNull
@Override
public ViewModelStore getViewModelStore() {
    return FragmentActivity.this.getViewModelStore();
}