Android Jetpack组件之ViewModel使用

1、前言

最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面。

Android Architecture组件是Android Jetpack的一部分,它们是一组库,旨在帮助开发者设计健壮、可测试和可维护的应用程序,包含一下组件:

  • Android Jetpack组件总览
  • Android Jetpack 组件之 Lifecycle使用
  • Android Jetpack 组件之 Lifecycle源码
  • Android Jetpack组件之ViewModel使用
  • Android Jetpack组件之 LiveData使用-源码
  • Android Jetpack组件之 Paging使用-源码
  • Android Jetpack组件之 Room使用-源码
  • Android Jetpack组件之Navigation使用-源码
  • Android Jetpack组件之WorkManger使用介绍
  • Android Jetpack组件App Startup简析
  • Android Jetpack组件之Hilt使用

本系列文章是各处copy过来的,个人感觉所有的开发者都应该尽早的熟悉Jetpack组件,相信一定会被它的魅力所吸引,最近也在完成一个使用以上所有组件实现的项目,作为对Jetpack组件的项目实践,下面来分析一下每个组件对项目开发的帮助。

2、VIewModel简介

Android框架管理UI控制器的生命周期,例如Activity和Fragment。框架可以决定销毁或重新创建UI控制器以响应完全不受控制的某些用户动作或设备事件,那设想一种情况当用户在界面操作录入了一些信息后,因为某种原因导致Activity重新创建,那此时用户写好的信息呢?如果要重头再来可能有的用户就会不耐烦了,进而减少了使用,可能有人说,可以使用onSaveInstanceState()方法并从包中恢复其数据 onCreate(),但此方法仅适用于可以序列化然后反序列化的少量数据,如果要恢复的数据量比较大,此时就时VIewModel的厉害之处了。

ViewModel之所以能在Activity重建时保存并恢复数据,因为Activity初次创建时会初始化创建VIewModel,在Activity销毁时,ViewModel对象不会销毁,在新的Activity重新创建后,仍然会执行之前的获取ViewModel的过程,Android系统采取了处理机制,使现在拿到的ViewModel就是前一次创建的对象,设想一下数据都储存在VIewModel中,而两次拿到的都是同一个VIewModel,那显示的数据自然就和之前的一样喽,这里先放一张系统处理ViewModel的创建、存储和获取流程图:

3、ViewModel的使用

  • 创建ViewModel类继承系统的ViewModel
class Model : ViewModel() {
    var textName = "Empty"
}
  • 在使用的Activity、Fragment中获取ViewModel
val model = ViewModelProviders.of(this)[Model::class.java]
  • 设置ViewModel中储存的数据
 tvModel.text = model.textName

实例:先添加一个按钮,点击按钮会修改Model中textName的值,然后在旋转手机,如果显示的为第二次设置的值,那么说明数据被保存且恢复了;

btn_change.setOnClickListener {
            model.textName = "Change = 22222"
            tvModel.text = model.textName
        }

注意:因为ViewModel在Activity销毁时是不会重新创建的,这也意味者ViewModel中不可以引用Activity的对象,否则会有内存泄露的问题,那么当Model中需要Context呢?Android为我们提供了AndroidViewModel,只需继承AndroidViewModel即可。

4、生命周期

  • ViewModel对象的范围是在获取ViewModel时传递给ViewModelProvider的Lifecycle生命周期
  • ViewModel在内存中直到Activity销毁或Fragment被移除
  • 系统首次调用活动对象的onCreate()方法时,通常会请求ViewModel
  • 系统可能会在整个活动的整个生命周期中多次调用onCreate(),例如当设备屏幕旋转时
  • ViewModel从第一次请求ViewModel直到活动完成并销毁时存在

5、源码解析

5.1 、ViewModelProviders.of(this)[Model::class.java]

从上面的方法中可以看出ViewModel的获取过程分为两步:

  • 获取ViewProvider:

ViewModelProviders提供四个构造方法创建VIewProvider,两个带有factory两个没有,不过没有factory的其实使用的是默认的Factory,所以四个方法基本只是Fragment和Activity的区分:

@NonNull
@MainThread
public static ViewModelProvider of(@NonNull Fragment fragment) {
        return of(fragment, null);
    }
 
   
@NonNull
@MainThread
public static ViewModelProvider of(@NonNull FragmentActivity activity) {
        return of(activity, null);
    }
 
@NonNull
@MainThread
public static ViewModelProvider of(@NonNull Fragment fragment, @Nullable Factory factory) {
        Application application = checkApplication(checkActivity(fragment));
        if (factory == null) {
            factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
        }
        return new ViewModelProvider(ViewModelStores.of(fragment), factory);
    }
 
@NonNull
@MainThread
public static ViewModelProvider of(@NonNull FragmentActivity activity,
            @Nullable Factory factory) {
        Application application = checkApplication(activity);
        if (factory == null) {
            factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
        }
        return new ViewModelProvider(ViewModelStores.of(activity), factory);
    }
  • 获取ViewModelStore:由前面的源码可以知道创建ViewProvider时传入两个参数:ViewModelStore 和 Factory;显然从名字就可以看出他们的作用,Factory负责创建,ViewModelStore负责存储
  @NonNull
    @MainThread
    public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        ViewModel viewModel = mViewModelStore.get(key);
 
        if (modelClass.isInstance(viewModel)) {
            //noinspection unchecked
            return (T) viewModel;
        } else {
            //noinspection StatementWithEmptyBody
            if (viewModel != null) {
                // TODO: log a warning.
            }
        }
 
        viewModel = mFactory.create(modelClass);
        mViewModelStore.put(key, viewModel);
        //noinspection unchecked
        return (T) viewModel;
    }

上面的执行是,先去ViewModelStore中获取,如果为空就调用Factory的create()创建ViewModel,并储存在VIewmoStore中,与我们所想一致;

ViewModelProviders.of(this)[Model::class.java]方法,忽略创建和储存细节,执行逻辑为:

5.2 、VIewModelStore

  • ViewModelStore.of(this)

上述过程中使用ViewModelStore.of(this) 创建ViewModelStore,方法源码:

 @NonNull
    @MainThread
    public static ViewModelStore of(@NonNull FragmentActivity activity) {
        if (activity instanceof ViewModelStoreOwner) {
            return ((ViewModelStoreOwner) activity).getViewModelStore();
        }
        return holderFragmentFor(activity).getViewModelStore();
    }

先判断Activity是否为 ViewModelStoreOwner,如果是直接获取其中的ViewModelStore,否则调用holderFragmentFor(activity).getViewModelStore()获取

  • holderFragmentFor(activity).
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public static HolderFragment holderFragmentFor(FragmentActivity activity) {
        return sHolderFragmentManager.holderFragmentFor(activity);
    }
 
// HolderFragmentManger
 
 HolderFragment holderFragmentFor(FragmentActivity activity) {
            FragmentManager fm = activity.getSupportFragmentManager();
            HolderFragment holder = findHolderFragment(fm); // 根据TAG查找Fragment
            if (holder != null) {
                return holder;
            }
            holder = mNotCommittedActivityHolders.get(activity); // 从储存的集合中获取
            if (holder != null) {
                return holder;
            }
 
            if (!mActivityCallbacksIsAdded) {
                mActivityCallbacksIsAdded = true;
                activity.getApplication().registerActivityLifecycleCallbacks(mActivityCallbacks);
            }
            holder = createHolderFragment(fm);  // 创建HolderFragment
            mNotCommittedActivityHolders.put(activity, holder); // 保存到集合
            return holder;
        }
  • findHolderFragment() & createHolderFragment()
private static HolderFragment findHolderFragment(FragmentManager manager) {
            if (manager.isDestroyed()) {
                throw new IllegalStateException("Can't access ViewModels from onDestroy");
            }
             // 根据TAG查找
            Fragment fragmentByTag = manager.findFragmentByTag(HOLDER_TAG);
            ...
            return (HolderFragment) fragmentByTag;
        }
 
 
 private static HolderFragment createHolderFragment(FragmentManager fragmentManager) {
            HolderFragment holder = new HolderFragment();
            fragmentManager.beginTransaction().add(holder, HOLDER_TAG).commitAllowingStateLoss();   // 设置TAG
            return holder;
        }

上面的整体逻辑是在Activity中添加一个隐士的Fragment,然后通过HolderFragmentManager管理、保存所有添加的Fragment,从源码中看出在HolderFragment的sHolderFragmentManager是一个静态实例,所以在整个系统中会保存者Fragmen对象;

5.3、HolderFragment

上面的过程都是获取或创建HolderFragment的过程,那HolderFragment是如何做到在活动销毁时保存数据的呢?

在Android开发中存在一种保存数据在销毁重建后恢复的方法,就是使用Fragment并设置销毁时保存,此处的HolderFragment就实现此功能,却保Fragment的能一直存在;

public HolderFragment() {
        setRetainInstance(true);
    }

HolderFragment中保存了ViewModelStore的实例,当Fragment不被销毁则ViewModelStore会一直存在

    private ViewModelStore mViewModelStore = new ViewModelStore();
 
    @NonNull
    @Override
    public ViewModelStore getViewModelStore() {
        return mViewModelStore;
    }

获取ViewModelStore的执行逻辑:

ViewModelStore是如何获取、储存Viewmodel的呢?

在前面的分析中知道,在创建ViewModelProvider时会传入ViewModelStore对象,在调用get()获取ViewModel对象时也会先从ViewModelStore中获取,或者创建新的对象后保存在ViewModelStore中,而ViewModelStore内部维护者一个Map集合保存者ViewModel对象的键值对

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);
    }

整个ViewModel的源码分析到此结束了,总结一下内部的储存逻辑

  • 根据传入的Activity获取、创建、添加并已键值对保存Fragment
  • 获取Fragment中保存的ViewStore对象(ViewStore中使用Map储存ViewModel)
  • 创建ViewProvider实例,ViewProvider中封装了获取的ViewStore和创建用的Factory
  • 从VIewStore的Map中或Factory的create()中获取ViewModel

6、思考

如何保证两次创建的activity(旋转前后)获取到的为同一个ViewModel,

在上面获取Fragment中时,创建过HolderFragment后保存在Map中,现在我们看一下这个map

 private Map<Activity, HolderFragment> mNotCommittedActivityHolders = new HashMap<>();

Map的键就是Activity的实例,所以无论多少次创建都是此Activity的实例,也就获得唯一的一个对应的Fragment

针对上面的特性是否有其他用处

按照上一个问题的逻辑,主要是传入的Activity的对象一致,那获取到就是同一个Fragment,存储的也是同一个VIewStore,那设想一下,如果一个Activity中有多个Fragment,利用这个特性就可以实现数据交互了;例如:两个Fragment之间。一个显示标题列表,点击某一个标题,另一个Fragment显示内容,此时使用一个ViewModel实现两者的传递。

public class SharedViewModel extends ViewModel {
    private final MutableLiveData<Item> selected = new MutableLiveData<Item>();
 
    public void select(Item item) {
        selected.setValue(item);
    }
 
    public LiveData<Item> getSelected() {
        return selected;
    }
}
 
 
public class MasterFragment extends Fragment {
    private SharedViewModel model;
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        itemSelector.setOnClickListener(item -> {
            model.select(item);  // 设置数据
        });
    }
}
 
public class DetailFragment extends Fragment {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        model.getSelected().observe(this, { item ->
           // Update the UI.
        });
    }
}
  • 上述代码中使用了LiveData,暂且把他看成一个观察改变并发射数据的功能,在第一个Fragment中修改,第二个Fragment也会响应变化;
  • 在获取ViewModelProvider时,这两个片段都使用getActivity() 因此,这两个片段都会收到相同的SharedViewModel实例,该实例的作用域为Acyivity
  • 使用ViewModel共享数据的好处,两个Fragment之间,Fragment和Activity之间无需任何联系,实现真正的解耦,每个片段都有其自己的生命周期,并且不受其他生命周期的影响

到此ViewModel的介绍完成了,将ViewModel和LiveData或其他组件联合使用,构造数据驱动型的界面,相信一定会带来不一样的体验。

7、源码分析更新

在项目切入到Androidx后,其中对ViewModel(使用lifecycle-extensions:2.1.0-alpha03版本)的源码逻辑进行了修改,ViewModel的存储依然在ViewModelStore中,但是替换了之前使用添加Fragment来保存ViewModelStore的方式,在androidx包中的Activity继承FragmentActivity,而FragmentActivity直接实现了ViewModelStoreOwner接口并创建了ViewModelStore对象。

在androidx中初始化和保存ViewModelStore的方式,变成了使用getLastNonConfigurationInstance()和onRetainNonConfigurationInstance()这一对函数,它们的最用也是在Activity销毁时保存数据的一对方法,在onRetainNonConfigurationInstance()时保存ViewModelStore对象,在活动创建时使用getLastNonConfigurationInstance()获取ViewMdelStore对象;

// 保存数据
public final Object onRetainNonConfigurationInstance() {
        Object custom = onRetainCustomNonConfigurationInstance();
 
        FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();
 
        if (fragments == null && mViewModelStore == null && custom == null) {
            return null;
        }
 
        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.custom = custom;
        nci.viewModelStore = mViewModelStore;
        nci.fragments = fragments;
        return nci;
    }
 
 
// 获取保存数据
  NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null && nc.viewModelStore != null && mViewModelStore == null) {
            mViewModelStore = nc.viewModelStore; // 获取ViewModelStore对象
        }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android Jetpack是Google提供的一套用于加速Android应用开发的工具包,其中包括了许多架构组件,其中之一就是ViewModelViewModel是一种设计模式,用于保存和管理与UI相关的数据。在传统的Android开发中,当屏幕旋转或者因为其他原因导致Activity或Fragment重建时,之前保存的临时数据就会丢失。而ViewModel的出现解决了这个问题。 ViewModel的主要作用是将数据与UI组件分离。它的工作方式是创建一个ViewModel类,并在其中保存需要与UI组件交互的数据。这样,当屏幕旋转或重建时,ViewModel实例不会销毁,数据也会得到保留。然后,在Activity或Fragment中,通过获取ViewModel实例,可以轻松地访问这些数据。 使用ViewModel的好处有很多。首先,它可以避免内存泄漏,因为ViewModel的生命周期与Activity或Fragment无关。其次,它可以节省资源,因为当Activity或Fragment销毁时,ViewModel实例可以被系统缓存起来,下次再创建时可以直接返回该实例。另外,由于ViewModel保存了与UI相关的数据,可以减少因为屏幕旋转导致的数据重复加载的问题。 在使用ViewModel时,你可以选择使用Android Jetpack中的其他架构组件来进一步提高开发效率,比如通过LiveData实现数据的观察和通知,或者通过DataBinding来实现UI与数据的自动绑定。 总之,ViewModelAndroid Jetpack中非常重要的一个架构组件,它的出现实现了数据与UI的解耦,提高了开发效率,并且解决了数据丢失的问题。希望通过这篇文档的详解,你对ViewModel有了更深入的理解。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值