Android架构组件---ViewModel使用及源码解析

一、ViewModel简介

ViewModel的出现主要有以下几个方面的考虑:

(1)在编写应用程序的时候,如果在Activity和Fragment中写入过多的逻辑,Activity会变得非常臃肿,不利于代码的维护,也违背了软件的分层思想。根据MVVM模型,可以将业务逻辑从View层拆分出来,Activity和Fragment只负责界面渲染相关、响应用户事件、进行权限申请等工作,将业务逻辑交给ViewModel来操作。

(2)在原来的应用写法中,常常会在Activity中去进行网络访问等异步调用,所以Activity需要根据自己的生命周期去管理这些调用,在必要的时候释放一些异步调用,做一些清理工作,以防止潜在的内存泄露。并且还要考虑当Activity发生configration变化时这些资源的重建等,非常麻烦,这里我们可以使用ViewModel来专门管理这些操作。

(3)在没有ViewModel时Fragment之间的通信,通常比较麻烦,需要通过Activity来进行管理,不可避免的会产生相互持有引用的情况,或者使用EventBus这样的第三方框架来通讯,这里使用ViewModel+LiveData的方式,可以更方便的进行Fragment之间的数据交换。

(4)另外,ViewModel的生命周期要比Activity长,所以方便在有Activity重建的场景下保存数据。借用一张官网的图,下图对比了Activity在屏幕旋转时的生命周期和ViewModel的生命周期。
Android架构组件---ViewModel使用及源码解析
这里需要特别注意的一点是,ViewModel中不要有Activity的引用,因为ViewModel生命周期比Activity生命周期要长,销毁Activity时可能因为ViewModel的引用导致Activity实例无法正确释放而发生内存泄露。

二、ViewModel使用

2.1 在Activity中使用ViewModel

首先我们要创建一个自己的ViewModel类,继承自ViewModel抽象类。

class UserViewModel : ViewModel() {

    private var repository: ApiRepository = ApiRepositoryImpl()

    private val users: MutableLiveData<List<User>> by lazy {
        MutableLiveData<List<User>>().also {
            getUsersFromNet()
        }
    }

    private fun getUsersFromNet() {
        Thread(Runnable {
            TimeUnit.MILLISECONDS.sleep(3000)
            users.postValue(repository.getUsersFromNet())
        }).start()
    }

    fun getUsers(): LiveData<List<User>> {
        return users
    }

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

在这里,ApiRepository是数据请求接口。然后我们定义了一个MutableLiveData类型,关于LiveData以后再详细讲,LiveData数据发生变化时会通过回调通知订阅者,订阅者则可以根据自己的情况更新UI。
这里需要注意的是onCleared()方法,这个方法在ViewModel销毁是会被系统调用,在这里可以做一些清理工作,比如Bitmap回收、网络连接释放等。
然后我们需要初始化ViewModel对象。因为现在ViewModelProviders工具类已经被弃用,所以这里我们使用ViewModelProvider类来初始化ViewModel类。

var model = ViewModelProvider(this).get(UserViewModel::class.java)

var model = ViewModelProvider(this,ViewModelProvider.NewInstanceFactory()).get(UserViewModel::class.java)

上面两种初始化的方法唯一不同是,下面的方法多加了一个Factory参数。
然后添加观察者

model.getUsers().observe(this, Observer<List<User>> { users ->
            Log.d("=====", users.toString())
        })

2.2 Fragment之间交换数据

两个Fragment之间交换数据是常见的一种应用场景,首先我们建立公共的ViewModel类。

class BottomNavViewModel: ViewModel() {
    val text = MutableLiveData<Int>().apply {
        value = 1
    }
}

这个类,只维护一个Int类型的LiveData。
然后创建数据生成的Fragment。

class HomeFragment : Fragment() {
    private lateinit var navViewModel: BottomNavViewModel
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        navViewModel =
            ViewModelProvider(requireActivity()).get(BottomNavViewModel::class.java)
        val root = inflater.inflate(R.layout.fragment_home, container, false)
        val textView: TextView = root.findViewById(R.id.text_home)
        textView.setOnClickListener {
            navViewModel.text.value = navViewModel.text.value!!.inc()
        }
        return root
    }
}

在数据生成类中,点击textView,增加BottomNavViewModel中text变量的值。然后创建接收数据的Fragment。

class DashboardFragment : Fragment() {
    private lateinit var navViewModel: BottomNavViewModel
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        navViewModel =
            ViewModelProvider(requireActivity()).get(BottomNavViewModel::class.java)
        val root = inflater.inflate(R.layout.fragment_dashboard, container, false)
        val textView: TextView = root.findViewById(R.id.text_dashboard)
        navViewModel.text.observe(requireActivity(), Observer {
            textView.text = it.toString()
        })
        return root
    }
}

当BottomNavViewModel中text值发生变化时更新DashboardFragment上textView的值。

三、ViewModel源码解析

首先我们从ViewModel初始化的这句代码开始看。

var model = ViewModelProvider(this).get(UserViewModel::class.java)

这句代码分两步进行,先创建了一个ViewModelProvider对象,然后调用get方法。

3.1、ViewModelProvider类创建

我们点开看ViewModelProvider类这个只有一个参数的构造方法。

public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
        this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
                ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
                : NewInstanceFactory.getInstance());
    }

在这个方法里传入需要传入一个ViewModelStoreOwner对象,通过其getViewModelStore方法获取ViewModelStore对象。而上面我们在Activity中是传入的一个Activity对象,于是我们找到Activity的继承链:AppCompatActivity–>FragmentActivity–>ComponentActivity,这里的ComponentActivity就实现在了ViewModelStoreOwner接口。

ViewModelProvider类有三个构造方法,最终都会执行这个构造方法:

public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
        mFactory = factory;
        mViewModelStore = store;
    }

在这个构造方法中需要传入一个ViewModelStore对象,一个Factory对象。
这个ViewModelStore对象就是我们用来在Configuration变化时存储ViewModel的地方。

3.1.1、Factory

Factory对象则是用来创建ViewModel实例用的。我们常常会遇到的Factory有三个:

一个是当我们使用只有一个参数的构造函数时,系统默认的HasDefaultViewModelProviderFactory对象,这是一个接口,是由ComponentActivity实现的,由于它的实现机制过于复杂,这里我也没完全看懂,所以就不写了。

二是AndroidViewModelFactory这个Factory接受一个Application对象,它返回的ViewModel的生命周期与Application相同。

三是NewInstanceFactory这个Fractory返回一个普通的ViewModel对象。

三个Factory的代码较多,这里我就不贴出来了,有兴趣的同学可以自己去看。

3.1.2、ViewModelStore

3.1.2.1 ViewModelStore创建过程

在说到ViewModelProvider类的创建时我们提到,只有一个参数的构造方法需要传ViewModelStoreOwner对象,而这个对象就是ComponentActivity,然后我们去看ComponentActivity中是如何实现这个方法的。

@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.");
        }
        if (mViewModelStore == null) {
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                // Restore the ViewModelStore from NonConfigurationInstances
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
        return mViewModelStore;
    }

这里的mViewModelStore就是我们的ViewModelStore实例对象。代码中首先判断mViewModelStore是否为空,若为空,则使用getLastNonConfigurationInstance方法从之前存储的数据中去获取NonConfigurationInstances,而我们的ViewModelStore对象就存储在其中;如果之前没有存储过mViewModelStore对象,则新创建一个,新创建的mViewModelStore对象,在Activity销毁前系统会调用onRetainNonConfigurationInstance方法来保存数据。

这里需要注意的就是getLastNonConfigurationInstanceonRetainNonConfigurationInstance两个方法是对应的,一个保存一个获取,都由系统来调用,后者在销毁Activity前调用,前者在Activity创建后都可调用。

3.1.2.2 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类的实现相对来说就比较简单了,当中包括一个HashMap,用来缓存我们的ViewModel类,这里需要注意的有两点。
一个是put方法,put方法在往HashMap中放入一个ViewModel后,如果之前有相同key的ViewModel,则需要被清理。
二个是clear方法,clear方法遍历HashMap中的ViewModel,然后调用每个ViewModel的clear方法来进行清理操作。那么它是如何被调用的呢?我们可以在ComponentActivity的构造方法中找到相关的代码,当Activity被destroy并且不是处于configuration正在变化的状态时调用clear方法

public ComponentActivity() {
    //省略
    getLifecycle().addObserver(new LifecycleEventObserver() {
        @Override
        public void onStateChanged(@NonNull LifecycleOwner source,
                @NonNull Lifecycle.Event event) {
            if (event == Lifecycle.Event.ON_DESTROY) {
                if (!isChangingConfigurations()) {
                    getViewModelStore().clear();
                }
            }
        }
    });
    //省略
}

3.2、ViewModelProvider.get()

我们再来看初始化语句的第二步get方法。

var model = ViewModelProvider(this).get(UserViewModel::class.java)

该方法传入一个ViewModel的Class对象。

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

该方法调用下面的方法

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

这个方法中涉及到OnRequeryFactoryKeyedFactory,与上面的HasDefaultViewModelProviderFactory相关,就不详述了,理由跟上面一样。
我们简单理解这个方法,就是从mViewModelStroe对象中通过键获取我们的ViewModel对象。

四、总结

(1)ViewModel中不要持有Activity的引用。
(2)ViewModel对象存储在ViewModelStroe中,ViewModelStroe通过getLastNonConfigurationInstanceonRetainNonConfigurationInstance两个方法由系统来存储。
(3)当Activity不是因为configuration变化而销毁时,ViewModel才会被回收。
(4)关于应用方面的总结看第一段简介。

上一篇:ViewModel源码分析


下一篇:Android - 带着问题看源码之 ViewModel