ViewModel基本使用与数据共享简单分析

官方介绍

ViewModel 类旨在以注重生命周期的方式存储和管理界面相关的数据。ViewModel类让数据可在发生屏幕旋转等配置更改后继续留存。
如果系统销毁或重新创建界面控制器,则存储在其中的任何瞬态界面相关数据都会丢失。例如,应用可能会在它的某个 Activity 中包含用户列表。为配置更改重新创建 Activity 后,新 Activity 必须重新提取用户列表。对于简单的数据,Activity 可以使用 onSaveInstanceState() 方法从 onCreate() 中的捆绑包恢复其数据,但此方法仅适合可以序列化再反序列化的少量数据,而不适合数量可能较大的数据,如用户列表或位图。

总结:

  1. ViewModel 有生命周期感知
    官方生命周期图
  2. ViewModel 可以在屏幕旋转等情况发生页面销毁重建的情况下,保存数据、恢复数据,虽然 onSaveInstanceState 也可以做到保存数据,但是只能是简单数据且数据量有限制,方式是序列化和反序列化。ViewModel 比 onSaveInstanceState 使用简单方便、数据可操作性强。
  3. 还能简单的实现 Avtivity 和 Fragment、多个 Fragment 之间的数据共享

使用

  1. 创建 ViewModel
  2. ViewModel 中获取数据
  3. 将数据返回给视图,通常使用 LiveData 简单方便
ViewModel
public abstract class ViewModel {

    ...

    /**
     * This method will be called when this ViewModel is no longer used and will be destroyed.
     * <p>
     * It is useful when ViewModel observes some data and you need to clear this subscription to
     * prevent a leak of this ViewModel.
     */
    @SuppressWarnings("WeakerAccess")
    protected void onCleared() {
    }

    @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 是个抽象类,主要是有 onCleared 方法,子类可重写此方法,释放资源,此方法会在 ViewModel destroy 时被调用 ViewModel 不能持有视图或 LifecycleOwner 的引用(ViewModel存在的时间更长),如果 ViewModel 中需要持有 Context 上下文,可以使用 AndroidViewModel ,它是 ViewModel 的一个实现了,扩展持有 Application
使用中可以自定义基类 BaseViewModel ,如网络请求或数据库查询等耗时操作时统一Loading处理

open class BaseViewModel : ViewModel() {

    //暂时无用,现在每个请求通过ResultState.onAppLoading可实现发起请求的提示作用
    val loadingChange: UiLoadingChange by lazy { UiLoadingChange() }

    inner class UiLoadingChange {
        //显示加载框
        val showDialog by lazy { EventLiveData<String>() }
        //隐藏
        val dismissDialog by lazy { EventLiveData<Boolean>() }
    }

}

    private fun registerUiChange() {
        //显示弹窗
        mViewModel.loadingChange.showDialog.observeInActivity(this, Observer {
            showLoading(it)
        })
        //关闭弹窗
        mViewModel.loadingChange.dismissDialog.observeInActivity(this, Observer {
            dismissLoading()
        })
    }
创建ViewModel

val testViewModel = ViewModelProvider(this).get(MainViewModel::class.java)

ViewModel 要保存数据(数据共享)不能使用 ViewModel 直接创建对象的方式,通过 ViewModelProvider 提供 ViewModel 实例,已达到此目的。
ViewModelProvider 有是三个构造函数

    public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
        this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
                ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
                : NewInstanceFactory.getInstance());
    }
    
    public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
        this(owner.getViewModelStore(), factory);
    }
    
    public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
        mFactory = factory;
        mViewModelStore = store;
    }

最终都是给 ViewModelStore 和 Factory 赋值
ComponentActivity(FragmentActivity继承自它)和 Fragment 都 实现了 ViewModelStoreOwner 接口,重写 getViewModelStore 以获取 ViewModelStore

ViewModelStore
public class ViewModelStore {

    private final HashMap<String, ViewModel> mMap = new HashMap<>();

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

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

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

    /**
     *  Clears internal storage and notifies ViewModels that they are no longer used.
     */
    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.clear();
        }
        mMap.clear();
    }
}

ViewModelStore 是对 Map 的封装,用于存储不同的 ViewModel

Factory
    public interface Factory {
        /**
         * Creates a new instance of the given {@code Class}.
         * <p>
         *
         * @param modelClass a {@code Class} whose instance is requested
         * @param <T>        The type parameter for the ViewModel.
         * @return a newly created ViewModel
         */
        @NonNull
        <T extends ViewModel> T create(@NonNull Class<T> modelClass);
    }

Factory 是工厂接口,用于创建 ViewModel ,Activity 和 Fragment 都实现了 HasDefaultViewModelProviderFactory 接口,提供 SavedStateViewModelFactory(实现了Factory)

    @NonNull
    @Override
    public <T extends ViewModel> T create(@NonNull String key, @NonNull Class<T> modelClass) {
        boolean isAndroidViewModel = AndroidViewModel.class.isAssignableFrom(modelClass);
        Constructor<T> constructor;
        if (isAndroidViewModel && mApplication != null) {
            constructor = findMatchingConstructor(modelClass, ANDROID_VIEWMODEL_SIGNATURE);
        } else {
            constructor = findMatchingConstructor(modelClass, VIEWMODEL_SIGNATURE);
        }
        // doesn't need SavedStateHandle
        if (constructor == null) {
            return mFactory.create(modelClass);
        }

        SavedStateHandleController controller = SavedStateHandleController.create(
                mSavedStateRegistry, mLifecycle, key, mDefaultArgs);
        try {
            T viewmodel;
            if (isAndroidViewModel && mApplication != null) {
                viewmodel = constructor.newInstance(mApplication, controller.getHandle());
            } else {
                viewmodel = constructor.newInstance(controller.getHandle());
            }
            viewmodel.setTagIfAbsent(TAG_SAVED_STATE_HANDLE_CONTROLLER, controller);
            return viewmodel;
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Failed to access " + modelClass, e);
        } catch (InstantiationException e) {
            throw new RuntimeException("A " + modelClass + " cannot be instantiated.", e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException("An exception happened in constructor of "
                    + modelClass, e.getCause());
        }
    }

通过反射的方式创建 ViewModel

回到 ViewModelProvider 的 get 方法

    @NonNull
    @MainThread
    public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
        String canonicalName = modelClass.getCanonicalName();
        if (canonicalName == null) {
            throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
        }
        return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
    }
    
    @NonNull
    @MainThread
    public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        ViewModel viewModel = mViewModelStore.get(key);

        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);
        }
        mViewModelStore.put(key, viewModel);
        return (T) viewModel;
    }
    

先从 ViewModelStore 中获取 ViewModel,如果存在则直接返回此 ViewModel,若不存在则通过提供的 Factory 创建ViewModel 并存入ViewModelStore,同时返回。所以只要是同一个 ViewModelStore,去获取特定 ViewModel 时就能获取同一对象,以实现多个视图数据共享。如 不同 Fragment 中调用val fgTestViewModel: MainViewModel = ViewModelProvider(requireActivity()).get(MainViewModel::class.java) 就能获取到同一 ViewModel。

数据共享

上面说过实现数据共享是运维 ViewModelStore 相同,Fragment 因为传入相同的 Activity 所以 ViewModelStore 是相同的,但是屏幕旋转导致的页面重建, Activity 肯定是不相同的为什么说还能通过 ViewModel 来回复数据呢。
那就先看看销毁时做了什么操作,以 Activity 为例

    //FragmentActivity
    @Override
    protected void onDestroy() {
        super.onDestroy();
        mFragments.dispatchDestroy();
        mFragmentLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);
    }
    
    //ComponentActivity
    getLifecycle().addObserver(new LifecycleEventObserver() {
        @Override
        public void onStateChanged(@NonNull LifecycleOwner source,
                @NonNull Lifecycle.Event event) {
            if (event == Lifecycle.Event.ON_DESTROY) {
                // Clear out the available context
                mContextAwareHelper.clearAvailableContext();
                // And clear the ViewModelStore
                if (!isChangingConfigurations()) {
                    getViewModelStore().clear();
                }
            }
        }
    });

    //Activity
    public boolean isChangingConfigurations() {
        return mChangingConfigurations;
    }

mChangingConfigurations是否是因新配置(如旋转屏幕导致重建),为true就不会清除ViewModelStore数据,mChangingConfigurations 在 ActivityThread handleRelaunchActivity 方法中被置为 true
接下来看因屏幕旋转导致页面销毁重建,源码中很难找到相关的方法,那就从正常情况的 getViewModelStore 看起

    @NonNull
    @Override
    public ViewModelStore getViewModelStore() {
        if (getApplication() == null) {
            throw new IllegalStateException("Your activity is not yet attached to the "
                    + "Application instance. You can't request ViewModel before onCreate call.");
        }
        ensureViewModelStore();
        return mViewModelStore;
    }
    
    void ensureViewModelStore() {
        if (mViewModelStore == null) {
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                // Restore the ViewModelStore from NonConfigurationInstances
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
    }

可以看到 ensureViewModelStore 方法中会从 NonConfigurationInstances 中获取 ViewModelStore ,若 ViewModelStore 依旧为空才会重新创建。找到 getLastNonConfigurationInstance 方法

/**
     * Retrieve the non-configuration instance data that was previously
     * returned by {@link #onRetainNonConfigurationInstance()}.  This will
     * be available from the initial {@link #onCreate} and
     * {@link #onStart} calls to the new instance, allowing you to extract
     * any useful dynamic state from the previous instance.
     * ....
     *
     * @return the object previously returned by {@link #onRetainNonConfigurationInstance()}
     */
    @Nullable
    public Object getLastNonConfigurationInstance() {
        return mLastNonConfigurationInstances != null
                ? mLastNonConfigurationInstances.activity : null;
    }

大概意思是通过 getLastNonConfigurationInstance 可以获取到之前 onRetainNonConfigurationInstance 方法返回的实例

    /**
     * Retain all appropriate non-config state.  You can NOT
     * override this yourself!  Use a {@link androidx.lifecycle.ViewModel} if you want to
     * retain your own non config state.
     */
    @Override
    @Nullable
    @SuppressWarnings("deprecation")
    public final Object onRetainNonConfigurationInstance() {
        // Maintain backward compatibility.
        Object custom = onRetainCustomNonConfigurationInstance();

        ViewModelStore viewModelStore = mViewModelStore;
        if (viewModelStore == null) {
            // No one called getViewModelStore(), so see if there was an existing
            // ViewModelStore from our last NonConfigurationInstance
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                viewModelStore = nc.viewModelStore;
            }
        }

        if (viewModelStore == null && custom == null) {
            return null;
        }

        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.custom = custom;
        nci.viewModelStore = viewModelStore;
        return nci;
    }

这个方法将 ViewModelStore 保存在 NonConfigurationInstances 对象中,并将 NonConfigurationInstances 返回。在页面创建时,调用到了 getViewModelStore 方法,进而调用 getLastNonConfigurationInstance 获取 NonConfigurationInstances 对象,从而获取之前的 ViewModelStore 。

PS

onRetainNonConfigurationInstance 返回的 NonConfigurationInstances 对象被谁接收、如何保存、如何在重建时传递 涉及到页面旋转重启的流程,没有深入,从源码上看,onRetainNonConfigurationInstance 在 Activity 中被 retainNonConfigurationInstances 调用 onRetainNonConfigurationInstance 返回的对象被存到 NonConfigurationInstances 对象中并将该对象返回。而 retainNonConfigurationInstances 方法在 ActivityThread performDestroyActivity 方法中被调用,结果被保存在 lastNonConfigurationInstances 变量,最终调用到 performLaunchActivity ,方法中 activity 调用 attach 方法将值传回

2021-06-03 17:41:21.852 25397-25397/com.cc.kotlin_base_mvvm E/ccc0603: getLastNonConfigurationInstance
2021-06-03 17:41:21.948 25397-25397/com.cc.kotlin_base_mvvm E/ccc0603: onCreate
2021-06-03 17:41:21.948 25397-25397/com.cc.kotlin_base_mvvm E/ccc0603: store = androidx.lifecycle.ViewModelStore@fbb7964
2021-06-03 17:41:21.948 25397-25397/com.cc.kotlin_base_mvvm E/ccc0603: model = com.cc.kotlin_base_mvvm.MainViewModel@4a20394
2021-06-03 17:41:21.966 25397-25397/com.cc.kotlin_base_mvvm E/ccc0603: onStart
2021-06-03 17:41:21.976 25397-25397/com.cc.kotlin_base_mvvm E/ccc0603: onResume
2021-06-03 17:41:28.682 25397-25397/com.cc.kotlin_base_mvvm E/ccc0603: onPause
2021-06-03 17:41:28.694 25397-25397/com.cc.kotlin_base_mvvm E/ccc0603: onStop
2021-06-03 17:41:28.718 25397-25397/com.cc.kotlin_base_mvvm E/ccc0603: onDestroy
2021-06-03 17:41:28.834 25397-25397/com.cc.kotlin_base_mvvm E/ccc0603: getLastNonConfigurationInstance
2021-06-03 17:41:28.991 25397-25397/com.cc.kotlin_base_mvvm E/ccc0603: onCreate
2021-06-03 17:41:28.991 25397-25397/com.cc.kotlin_base_mvvm E/ccc0603: store = androidx.lifecycle.ViewModelStore@fbb7964
2021-06-03 17:41:28.991 25397-25397/com.cc.kotlin_base_mvvm E/ccc0603: model = com.cc.kotlin_base_mvvm.MainViewModel@4a20394
2021-06-03 17:41:29.012 25397-25397/com.cc.kotlin_base_mvvm E/ccc0603: onStart
2021-06-03 17:41:29.023 25397-25397/com.cc.kotlin_base_mvvm E/ccc0603: onResume

onRetainNonConfigurationInstance 在onStop 之后 onDestory 之前调用(无法重写,可以断点查看),getLastNonConfigurationInstance 在 onCreate 之前就被调用,屏幕旋转后可以看到 ViewModelStore 和 ViewModel 和页面重建之前是同一个对象
Fragment 中应该是和 Activity 中类似的,但是 Fragment 依附于 Activity,且 Fragment 中是使用 FragmentManager 来实现 getViewModelStore 操作的,与 Activity 不同,但内部流程原理大致应该是类似的。

与Kotlin协程使用

来看个官方文档的例子
ViewModelScope:
为应用中的每个 ViewModel 定义了 ViewModelScope。如果 ViewModel已清除,则在此范围内启动的协程都会自动取消。如果您具有仅在 ViewModel 处于活动状态时才需要完成的工作,此时协程非常有用。例如,如果要为布局计算某些数据,则应将工作范围限定至 ViewModel,以便在ViewModel清除后,系统会自动取消工作以避免消耗资源。您可以通过 ViewModel 的 viewModelScope 属性访问 ViewModel 的 CoroutineScope,如以下示例所示:

class MyViewModel: ViewModel() {
    init {
        viewModelScope.launch {
            // Coroutine that will be canceled when the ViewModel is cleared.
        }
    }
}

也就是说,用 ViewModelScope.launch 启动的协程会在 ViewModel 被销毁时自动取消,即协程中正在进行网络请求,此时页面被销毁,会回调 ViewModel 的 onCleared 方法,我们无需在这个方法中做有关协程的取消,并不会有回调请求结果、通知页面更新等操作,避免内存泄露

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值