Android開發(fā)之MVVM模式實(shí)踐(一):ViewModel的封裝

前言

本文發(fā)布于去年下半年,在發(fā)布兩篇系列文章后未再更新后續(xù)系列。期間雖然收到多位讀者催更,但因工作原因以及去年自我學(xué)習(xí)驅(qū)動在Python上,未再做更新。在此感謝各位同學(xué)的關(guān)注同時(shí)對斷更表示抱歉。本系列文章將在今后兩周內(nèi)正常更新完畢,同時(shí)將重新更新包括本篇在內(nèi)已經(jīng)發(fā)布的系列文章,提升閱讀體驗(yàn)。本系列文章涉及知識點(diǎn)主要為ViewModelLiveDataDataBinding以及Kotlin的協(xié)程,歡迎各位同學(xué)關(guān)注。

推薦

文章將率先在公眾號「碼途有道」上發(fā)布,歡迎大家關(guān)注!

架構(gòu)境況

目前Android開發(fā)中,常用的幾種項(xiàng)目架構(gòu)模式分別是MVCMVPMVVM。當(dāng)然根據(jù)項(xiàng)目的體量和業(yè)務(wù)的不同,可能還會對這幾種模式進(jìn)行融合,產(chǎn)生其他變種模式,這個(gè)我們暫且不談。我們本篇的主角是近兩年越來越受歡迎的MVVM,主要依托于Google的推出的Jetpack全家桶實(shí)現(xiàn)。

一、初識ViewModel

ViewModelJetpack全家桶中的一員,也是構(gòu)建MVVM模式的重要組成部分。因?yàn)閂iewModel擁有比Activity/Fragment還要長的生命周期,所以ViewModel中最好不要持有Activity/Fragment的引用,以免引起內(nèi)存泄漏。一般建議在ViewModel只做數(shù)據(jù)處理,保存Activity/Fragment中的頁面數(shù)據(jù),在Activity被銷毀重建時(shí)也能拿到之前的頁面數(shù)據(jù)。此外,Google建議一個(gè)Activity/Fragment最好只擁有一個(gè)ViewModel,一個(gè)ViewModel中可以擁有多個(gè)Model實(shí)例(即數(shù)據(jù)邏輯處理的類,比如網(wǎng)絡(luò)請求數(shù)據(jù))。

eg: 當(dāng)屏幕旋轉(zhuǎn)時(shí),Activity可能會先被銷毀,再重新創(chuàng)建新的實(shí)例;而因?yàn)閂iewModel的生命周期長于Activity,新的Activity中的ViewModel持有的是之前被銷毀的Activity的引用(具體原理可看ViewModelProvider源碼中對ViewModel的存取處理),這樣就會導(dǎo)致內(nèi)存泄漏。

二、給ViewModel添加頁面的生命周期函數(shù)

在開發(fā)中,ViewModel中的方法不可避免的會被在指定的Activity/Fragment的生命周期函數(shù)中調(diào)用,我們可以在Activity/Fragment的生命周期函數(shù)中主動調(diào)用ViewModel中的方法,但是我們還有一種更完美的方法,即讓ViewModel擁有和Activity/Fragment一樣的生命周期函數(shù),ViewModel在自己的生命周期函數(shù)中主動調(diào)用自己的方法即可,與Activity/Fragment更加解耦,也更加方便單元測試。

常見的生命周期實(shí)現(xiàn)方法

ViewModel擁有和Activity一樣的生命周期函數(shù),我們常見的做法是在Activity基類中的生命周期函數(shù)中調(diào)用ViewModel基類中對應(yīng)的生命周期函數(shù),達(dá)到ViewModel的生命周期函數(shù)和Activity的生命周期函數(shù)同步的效果。

class BaseActivity:AppCompatActivity(){
    ......
    override fun onResume() {
        super.onResume()
        viewModel.onResume()
    }
}

class BaseViewModel:ViewModel(){
    fun onResume(){
    }
}

LifecycleObserver的使用

雖然上面的常見方法已經(jīng)可以滿足我們的基本需求,但是Google的Jetpack還給了我們一種更加完美的實(shí)現(xiàn):LifecycleObserverLifecycleObserver是一個(gè)接口,表示組件生命周期的觀察者,使用如下:

interface ViewModelLifecycle:LifecycleObserver {

    @OnLifecycleEvent(Lifecycle.Event.ON_ANY)
    fun onAny(owner: LifecycleOwner, event: Lifecycle.Event)

    @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
    fun onCreate()

    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    fun onStart()

    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    fun onResume()

    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    fun onPause()

    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    fun onStop()

    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    fun onDestroy()
}

上述代碼中,我們創(chuàng)建了一個(gè)名為ViewModelLifecycle的接口,并繼承于LifecycleObserver。然后在ViewModelLifecycle中定義了onCreateonResume等諸多方法,并在方法上加上了與生命周期函數(shù)對應(yīng)的注解,表示Activity在執(zhí)行自己的生命周期函數(shù)時(shí),也會調(diào)用對應(yīng)注解修飾的自定義函數(shù)(注解中的Lifecycle.Event.ON_ANY表示只要Activity執(zhí)行自己的生命周期函數(shù),此注解修飾的方法就會被調(diào)用)。

關(guān)于Lifecycle相關(guān)的知識本篇內(nèi)容不會詳細(xì)講解,有興趣的同學(xué)可以自行查詢相關(guān)資料

為ViewModel添加生命周期函數(shù)

在定義完接口后,我們下一步就需要ViewModel去實(shí)現(xiàn)上述的ViewModelLifecycle接口,為自己添加生命周期函數(shù),實(shí)現(xiàn)如下:

abstract class BaseViewModel : ViewModel(), ViewModelLifecycle {
    private lateinit var lifcycleOwner: LifecycleOwner

    override fun onAny(owner: LifecycleOwner, event: Lifecycle.Event) {
        this.lifcycleOwner = owner
    }

    override fun onCreate() {

    }

    override fun onStart() {

    }

    override fun onResume() {

    }

    override fun onPause() {

    }

    override fun onStop() {

    }

    override fun onDestroy() {

    }
}

至此,ViewModel就擁有了自己的生命周期函數(shù)了,但是這只是半成品。在上述內(nèi)容中提到LifecycleObserver表示的是組件生命周期的觀察者,既然有觀察者,那么一定就有被觀察過者被觀察者需要實(shí)現(xiàn)的接口是LifecycleOwner。而作為被觀察的對象Activity/Frament則需要實(shí)現(xiàn)LifecycleOwner。不過在AndroidX中的Activity/Frament已經(jīng)默認(rèn)實(shí)現(xiàn)了LifecycleOwner接口,我們只需要在Activity/Fragment中進(jìn)行如下操作即可使ViewModel的生命周期函數(shù)與Activity/Fragment的生命周期函數(shù)同步:

// 將實(shí)現(xiàn)了LifecycleObserver接口的ViewModol實(shí)例作為觀察者,添加到Activity/Fragment的生命周期觀察者隊(duì)列中
// 可以在實(shí)例化ViewModel后,即刻調(diào)用這個(gè)方法
getLifecycle().addObserver(viewModel)

// 將ViewModel從Activity/Fragment的生命周期觀察者隊(duì)列中移除
// 一般可以在onDestory()方法中調(diào)用這個(gè)方法
getLifecycle().removeObserver(viewModel)

三、為ViewModel添加一些常用事件

我們在開發(fā)中經(jīng)常會遇到這樣的一些場景:

  • 網(wǎng)絡(luò)請求數(shù)據(jù)時(shí),發(fā)生錯(cuò)誤,彈出toast提示
  • 網(wǎng)絡(luò)請求數(shù)據(jù)后,數(shù)據(jù)源為空,顯示無數(shù)據(jù)視圖
  • 網(wǎng)絡(luò)請求數(shù)據(jù)時(shí),彈出Loading視圖,在網(wǎng)絡(luò)請求結(jié)束后關(guān)閉
  • ......

對于上述提到的類似操作,我們可以事先在ViewModel中埋下用于通信使用的對應(yīng)的LiveData,利用LiveData的觀察者機(jī)制和在頁面處于非活躍狀態(tài)下不會通知UI更新的特性,便捷的通知Activity/Fragment作出對應(yīng)的UI處理,并且完美的避過內(nèi)存泄漏。

  1. 定義常用的UI操作
interface ViewBehavior {
    /**
     * 是否顯示Loading視圖
     */
    fun showLoadingUI(isShow: Boolean)

    /**
     * 是否顯示空白視圖
     */
    fun showEmptyUI(isShow: Boolean)

    /**
     * 彈出Toast提示
     */
    fun showToast(map: Map<String, *>)

    /**
     * 不帶參數(shù)的頁面跳轉(zhuǎn)
     */
    fun navigate(page: Any)

    /**
     * 返回鍵點(diǎn)擊
     */
    fun backPress(arg: Any?);

    /**
     * 關(guān)閉頁面
     */
    fun finishPage(arg: Any?)
}
  1. ViewModel中添加事件LiveData
abstract class BaseViewModel : ViewModel(), ViewModelLifecycle, ViewBehavior {
    // loading視圖顯示Event
    var _loadingEvent = MutableLiveData<Boolean>()
        private set

    // 無數(shù)據(jù)視圖顯示Event
    var _emptyPageEvent = MutableLiveData<Boolean>()
        private set

    // toast提示Event
    var _toastEvent = MutableLiveData<Map<String, *>>()
        private set

    // 不帶參數(shù)的頁面跳轉(zhuǎn)Event
    var _pageNavigationEvent = MutableLiveData<Any>()
        private set

    // 點(diǎn)擊系統(tǒng)返回鍵Event
    var _backPressEvent = MutableLiveData<Any?>()
        private set

    // 關(guān)閉頁面Event
    var _finishPageEvent = MutableLiveData<Any?>()
        private set

    lateinit var application: Application

    private lateinit var lifcycleOwner: LifecycleOwner

    override fun onAny(owner: LifecycleOwner, event: Lifecycle.Event) {
        this.lifcycleOwner = owner
    }

    override fun onCreate() {

    }

    override fun onStart() {

    }

    override fun onResume() {

    }

    override fun onPause() {

    }

    override fun onStop() {

    }

    override fun onDestroy() {

    }

    override fun showLoadingUI(isShow: Boolean) {
        _loadingEvent.postValue(isShow)
    }

    override fun showEmptyUI(isShow: Boolean) {
        _emptyPageEvent.postValue(isShow)
    }

    override fun showToast(map: Map<String, *>) {
        _toastEvent.postValue(map)
    }

    override fun navigate(page: Any) {
        _pageNavigationEvent.postValue(page)
    }

    override fun backPress(arg: Any?) {
        _backPressEvent.postValue(arg)
    }

    override fun finishPage(arg: Any?) {
        _finishPageEvent.postValue(arg)
    }

    protected fun showToast(str: String) {
        showToast(str, null)
    }

    protected fun showToast(str: String, duration: Int?) {
        val map = HashMap<String, Any>().apply {
            put(
                FlyBaseConstants.FLY_TOAST_KEY_CONTENT_TYPE,
                FlyBaseConstants.FLY_TOAST_CONTENT_TYPE_STR
            )
            put(FlyBaseConstants.FLY_TOAST_KEY_CONTENT, str)
            if (duration != null) {
                put(FlyBaseConstants.FLY_TOAST_KEY_DURATION, duration)
            }
        }
        showToast(map)
    }

    protected fun showToast(@StringRes resId: Int) {
        showToast(resId, null)
    }

    protected fun showToast(@StringRes resId: Int, duration: Int?) {
        val map = HashMap<String, Any>().apply {
            put(
                FlyBaseConstants.FLY_TOAST_KEY_CONTENT_TYPE,
                FlyBaseConstants.FLY_TOAST_CONTENT_TYPE_RESID
            )
            put(FlyBaseConstants.FLY_TOAST_KEY_CONTENT, resId)
            if (duration != null) {
                put(FlyBaseConstants.FLY_TOAST_KEY_DURATION, duration)
            }
        }
        showToast(map)
    }

    protected fun backPress() {
        backPress(null)
    }

    protected fun finishPage() {
        finishPage(null)
    }
}
  1. Activity中對ViewModel中的通知事件進(jìn)行處理
abstract class BaseBVMActivity<B : ViewDataBinding, VM : BaseViewModel> : BaseBindingActivity<B>(),
    ViewBehavior {

    protected lateinit var viewModel: VM

    protected fun injectViewModel() {
        val vm = createViewModel()
        viewModel = ViewModelProvider(this, BaseViewModel.createViewModelFactory(vm))
            .get(vm::class.java)
        viewModel.application = application
        lifecycle.addObserver(viewModel)
    }

    override fun init(savedInstanceState: Bundle?) {
        injectViewModel()
        initialize(savedInstanceState)
        initInternalObserver()
    }

    fun getActivityViewModel(): VM {
        return viewModel
    }

    override fun onDestroy() {
        super.onDestroy()
        binding.unbind()
        lifecycle.removeObserver(viewModel)
    }

    protected fun initInternalObserver() {
        viewModel._loadingEvent.observeNonNull(this, {
            showLoadingUI(it)
        })
        viewModel._emptyPageEvent.observeNonNull(this, {
            showEmptyUI(it)
        })
        viewModel._toastEvent.observeNonNull(this, {
            showToast(it)
        })
        viewModel._pageNavigationEvent.observeNonNull(this, {
            navigate(it)
        })
        viewModel._backPressEvent.observeNullable(this, {
            backPress(it)
        })
        viewModel._finishPageEvent.observeNullable(this, {
            finishPage(it)
        })
    }

    protected abstract fun createViewModel(): VM

    /**
     *  初始化操作
     */
    protected abstract fun initialize(savedInstanceState: Bundle?)
}

四、為ViewModel添加Application

ViewModel中,我們可能會使用到Context,但是在文章開始時(shí)就提到,在ViewModel中并不適合持有Activity/Fragment的引用。所以Google為我們提供了一個(gè)AndroidViewModel,源碼很簡單,如下:

public class AndroidViewModel extends ViewModel {
    @SuppressLint("StaticFieldLeak")
    private Application mApplication;

    public AndroidViewModel(@NonNull Application application) {
        mApplication = application;
    }

    /**
     * Return the application.
     */
    @SuppressWarnings("TypeParameterUnusedInFormals")
    @NonNull
    public <T extends Application> T getApplication() {
        //noinspection unchecked
        return (T) mApplication;
    }
}

上面的AndroidViewModel僅僅是在ViewMdoel中添加了一個(gè)Application的引用,我們完全可以自己實(shí)現(xiàn)這一步,讓我們的封裝更靈活。

abstract class BaseViewModel : ViewModel(), ViewModelLifecycle, ViewBehavior {
    ......

    @SuppressLint("StaticFieldLeak")
    lateinit var application: Application

    ......
}

abstract class BaseBVMActivity<B : ViewDataBinding, VM : BaseViewModel> : BaseBindingActivity<B>(),
    ViewBehavior {

    protected lateinit var viewModel: VM

    protected fun injectViewModel() {
        val vm = createViewModel()
        viewModel = ViewModelProvider(this, BaseViewModel.createViewModelFactory(vm))
            .get(vm::class.java)
        viewModel.application = application
        lifecycle.addObserver(viewModel)
    }
}

五、使用工廠模式創(chuàng)建ViewModel

ViewModel的創(chuàng)建并不止一種形式,我們常用的創(chuàng)建方式,如下

 val viewModel = ViewModelProvider(this).get(vm::class.java)

使用以上的方式創(chuàng)建ViewModel基本可以滿足我們一般的需求,但是假如我們需要在實(shí)例化ViewModel的時(shí)候傳入?yún)?shù),那么我們就必須使用工廠方式來創(chuàng)建ViewModel,示例如下:

val vm = LoginViewModel(loginRepository)
viewModel = ViewModelProvider(this,BaseViewModel.createViewModelFactory(vm)).get(vm:class.java)

六、小結(jié)

本章對于ViewModel的封裝暫時(shí)告一段落,下一章我們將主要講解對Activity的封裝。完整的封裝代碼已上傳至Github,項(xiàng)目地址為:https://github.com/albert-lii/Fly-Android。最后附上完整的ViewModel封裝代碼:

interface ViewModelLifecycle:LifecycleObserver {

    @OnLifecycleEvent(Lifecycle.Event.ON_ANY)
    fun onAny(owner: LifecycleOwner, event: Lifecycle.Event)

    @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
    fun onCreate()

    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    fun onStart()

    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    fun onResume()

    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    fun onPause()

    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    fun onStop()

    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    fun onDestroy()
}

/**
 * @author: Albert Li
 * @contact: albertlii@163.com
 * @time: 2020/6/9 8:01 PM
 * @description: 頁面的常用操作
 * @since: 1.0.0
 */
interface ViewBehavior {
    /**
     * 是否顯示Loading視圖
     */
    fun showLoadingUI(isShow: Boolean)

    /**
     * 是否顯示空白視圖
     */
    fun showEmptyUI(isShow: Boolean)

    /**
     * 彈出Toast提示
     */
    fun showToast(map: Map<String, *>)

    /**
     * 不帶參數(shù)的頁面跳轉(zhuǎn)
     */
    fun navigate(page: Any)

    /**
     * 返回鍵點(diǎn)擊
     */
    fun backPress(arg: Any?);

    /**
     * 關(guān)閉頁面
     */
    fun finishPage(arg: Any?)
}

/**
 * @author: Albert Li
 * @contact: albertlii@163.com
 * @time: 2020/6/7 10:30 PM
 * @description: ViewModel的基類
 * @since: 1.0.0
 */
abstract class BaseViewModel : ViewModel(), ViewModelLifecycle, ViewBehavior {

    // loading視圖顯示Event
    var _loadingEvent = MutableLiveData<Boolean>()
        private set

    // 無數(shù)據(jù)視圖顯示Event
    var _emptyPageEvent = MutableLiveData<Boolean>()
        private set

    // toast提示Event
    var _toastEvent = MutableLiveData<Map<String, *>>()
        private set

    // 不帶參數(shù)的頁面跳轉(zhuǎn)Event
    var _pageNavigationEvent = MutableLiveData<Any>()
        private set

    // 點(diǎn)擊系統(tǒng)返回鍵Event
    var _backPressEvent = MutableLiveData<Any?>()
        private set

    // 關(guān)閉頁面Event
    var _finishPageEvent = MutableLiveData<Any?>()
        private set

    @SuppressLint("StaticFieldLeak")
    lateinit var application: Application

    private lateinit var lifcycleOwner: LifecycleOwner

    override fun onAny(owner: LifecycleOwner, event: Lifecycle.Event) {
        this.lifcycleOwner = owner
    }

    override fun onCreate() {

    }

    override fun onStart() {

    }

    override fun onResume() {

    }

    override fun onPause() {

    }

    override fun onStop() {

    }

    override fun onDestroy() {

    }

    override fun showLoadingUI(isShow: Boolean) {
        _loadingEvent.postValue(isShow)
    }

    override fun showEmptyUI(isShow: Boolean) {
        _emptyPageEvent.postValue(isShow)
    }

    override fun showToast(map: Map<String, *>) {
        _toastEvent.postValue(map)
    }

    override fun navigate(page: Any) {
        _pageNavigationEvent.postValue(page)
    }

    override fun backPress(arg: Any?) {
        _backPressEvent.postValue(arg)
    }

    override fun finishPage(arg: Any?) {
        _finishPageEvent.postValue(arg)
    }

    protected fun showToast(str: String) {
        showToast(str, null)
    }

    protected fun showToast(str: String, duration: Int?) {
        val map = HashMap<String, Any>().apply {
            put(
                FlyBaseConstants.FLY_TOAST_KEY_CONTENT_TYPE,
                FlyBaseConstants.FLY_TOAST_CONTENT_TYPE_STR
            )
            put(FlyBaseConstants.FLY_TOAST_KEY_CONTENT, str)
            if (duration != null) {
                put(FlyBaseConstants.FLY_TOAST_KEY_DURATION, duration)
            }
        }
        showToast(map)
    }

    protected fun showToast(@StringRes resId: Int) {
        showToast(resId, null)
    }

    protected fun showToast(@StringRes resId: Int, duration: Int?) {
        val map = HashMap<String, Any>().apply {
            put(
                FlyBaseConstants.FLY_TOAST_KEY_CONTENT_TYPE,
                FlyBaseConstants.FLY_TOAST_CONTENT_TYPE_RESID
            )
            put(FlyBaseConstants.FLY_TOAST_KEY_CONTENT, resId)
            if (duration != null) {
                put(FlyBaseConstants.FLY_TOAST_KEY_DURATION, duration)
            }
        }
        showToast(map)
    }

    protected fun backPress() {
        backPress(null)
    }

    protected fun finishPage() {
        finishPage(null)
    }

    companion object {

        @JvmStatic
        fun <T : BaseViewModel> createViewModelFactory(viewModel: T): ViewModelProvider.Factory {
            return ViewModelFactory(viewModel)
        }
    }
}


/**
 * 創(chuàng)建ViewModel的工廠,以此方法創(chuàng)建的ViewModel,可在構(gòu)造函數(shù)中傳參
 */
class ViewModelFactory(val viewModel: BaseViewModel) : ViewModelProvider.Factory {

    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return viewModel as T
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。