Android Jetpack系列之MVVM使用及封裝

Android開發(fā)架構(gòu)

如果開發(fā)過程中大家各自為戰(zhàn),沒有統(tǒng)一規(guī)范,久而久之,項(xiàng)目代碼會變得混亂且后續(xù)難以維護(hù)。當(dāng)使用統(tǒng)一的架構(gòu)模式后,有很多的好處,如:

  • 統(tǒng)一開發(fā)規(guī)范,使得代碼整潔、規(guī)范,后續(xù)易于維護(hù)及擴(kuò)展
  • 提高開發(fā)效率(尤其在團(tuán)隊(duì)人員較多時)
  • 模塊單一職責(zé),使得模塊專注自己內(nèi)部(面向?qū)ο?,模塊間解耦

總之,開發(fā)架構(gòu)是前人總結(jié)出來的一套行之有效的開發(fā)模式,目的是達(dá)到高內(nèi)聚,低耦合的效果,使得項(xiàng)目代碼更健壯、易維護(hù)。

Android中常見的架構(gòu)模式有MVC(Model-View-Controller)、MVP(Model-View-Presenter)、MVVM(Model-View-ViewModel),一起來看下各自的特點(diǎn):

MVC

MVC(Model-View-Controller)是比較早期的架構(gòu)模式,模式整體也比較簡單。

MVC模式將程序分成了三個部分:

  • Model模型層:業(yè)務(wù)相關(guān)的數(shù)據(jù)(如網(wǎng)絡(luò)請求數(shù)據(jù)、本地?cái)?shù)據(jù)庫數(shù)據(jù)等)及其對數(shù)據(jù)的處理
  • View視圖層:頁面視圖(通過XML布局編寫視圖層),負(fù)責(zé)接收用戶輸入、發(fā)起數(shù)據(jù)請求及展示結(jié)果頁面
  • Controller控制器層:M與V之間的橋梁,負(fù)責(zé)業(yè)務(wù)邏輯

MVC特點(diǎn):

  • 簡單易用:上圖表述了數(shù)據(jù)整個流程:View接收用戶操作,通過Controller去處理業(yè)務(wù)邏輯,并通過Model去獲取/更新數(shù)據(jù),然后Model層又將最新的數(shù)據(jù)傳回View層進(jìn)行頁面展示。
  • 架構(gòu)簡單的另一面往往是對應(yīng)的副作用:由于XML布局能力弱,我們的View層的很多操作都是寫在Activity/Fragment中,同時,Controller、Model層的代碼也大都寫在Activity/Fragment中,這就會導(dǎo)致一個問題,當(dāng)業(yè)務(wù)邏輯比較復(fù)雜時,Activity/Fragment中的代碼量會很大,其違背了類單一職責(zé),不利于后續(xù)擴(kuò)展及維護(hù)。尤其是后期你剛接手的項(xiàng)目,一個Activity/Fragment類中的代碼動輒上千行代碼,那感覺著實(shí)酸爽:當(dāng)然,如果業(yè)務(wù)很簡單,使用MVC模式還是一種不錯的選擇。

MVP

MVP(Model-View-Presenter),架構(gòu)圖如下:

MVP各模塊職責(zé)如下

  • Model模型:業(yè)務(wù)相關(guān)的數(shù)據(jù)(如網(wǎng)絡(luò)請求數(shù)據(jù)、本地?cái)?shù)據(jù)庫數(shù)據(jù)等)及其對數(shù)據(jù)的處理
  • View視圖:頁面視圖(Activity/Fragment),負(fù)責(zé)接收用戶輸入、發(fā)起數(shù)據(jù)請求及展示結(jié)果頁面
  • Presenter:M與V之間的橋梁,負(fù)責(zé)業(yè)務(wù)邏輯

MVP特點(diǎn)View層接收用戶操作,并通過持有的Presenter去處理業(yè)務(wù)邏輯,請求數(shù)據(jù);接著Presenter層通過Model去獲取數(shù)據(jù),然后Model又將最新的數(shù)據(jù)傳回Presenter層,Presenter層又持有View層的引用,進(jìn)而將數(shù)據(jù)傳給View層進(jìn)行展示。

MVP相比MVC的幾處變化

  • View層與Model層不再交互,而是通過Presenter去進(jìn)行聯(lián)系
  • 本質(zhì)上MVP是面向接口編程,Model/View/Presenter每層的職責(zé)分工明確,當(dāng)業(yè)務(wù)復(fù)雜時,整個流程邏輯也是很清晰的

當(dāng)然,MVP也不是十全十美的,MVP本身也存在以下問題:

  • View層會抽象成IView接口,并在IView中聲明一些列View相關(guān)的方法;同樣的,Presenter會被抽象成IPresenter接口及其一些列方法,每當(dāng)實(shí)現(xiàn)一個功能時,都需要編寫多個接口及其對應(yīng)的方法,實(shí)現(xiàn)起來相對比較繁瑣,而且每次有改動時,對應(yīng)的接口方法也基本都會再去改動。
  • View層與Presenter層相互持有,當(dāng)View層關(guān)閉時,由于Presenter層不是生命周期感知的,可能會導(dǎo)致內(nèi)存泄漏甚至是崩潰。

ps:如果你的項(xiàng)目中使用了RxJava,可以使用 AutoDispose 自動解綁。

MVVM

MVVM(Model-View-ViewModel),架構(gòu)圖如下:

MVVM各職責(zé)如下

  • Model模型:業(yè)務(wù)相關(guān)的數(shù)據(jù)(如網(wǎng)絡(luò)請求數(shù)據(jù)、本地?cái)?shù)據(jù)庫數(shù)據(jù)等)及其對數(shù)據(jù)的處理
  • View視圖:頁面視圖(Activity/Fragment),負(fù)責(zé)接收用戶輸入、發(fā)起數(shù)據(jù)請求及展示結(jié)果頁面
  • ViewModel:M與V之間的橋梁,負(fù)責(zé)業(yè)務(wù)邏輯

MVVM特點(diǎn)

  • View層接收用戶操作,并通過持有的ViewModel去處理業(yè)務(wù)邏輯,請求數(shù)據(jù);
  • ViewModel層通過Model去獲取數(shù)據(jù),然后Model又將最新的數(shù)據(jù)傳回ViewModel層,到這里,ViewModel與Presenter所做的事基本是一樣的。但是ViewModel不會也不能持有View層的引用,而是View層會通過觀察者模式監(jiān)聽ViewModel層的數(shù)據(jù)變化,當(dāng)有新數(shù)據(jù)時,View層能自動收到新數(shù)據(jù)并刷新界面。

UI驅(qū)動 vs 數(shù)據(jù)驅(qū)動

MVP中,Presenter中需要持有View層的引用,當(dāng)數(shù)據(jù)變化時,需要主動調(diào)用View層對應(yīng)的方法將數(shù)據(jù)傳過去并進(jìn)行UI刷新,這種可以認(rèn)為是UI驅(qū)動;而MVVM中,ViewModel并不會持有View層的引用,View層會監(jiān)聽數(shù)據(jù)變化,當(dāng)ViewModel中有數(shù)據(jù)更新時,View層能直接拿到新數(shù)據(jù)并完成UI更新,這種可以認(rèn)為是數(shù)據(jù)驅(qū)動,顯然,MVVM相比于MVP來說更加解耦。

MVVM的具體實(shí)現(xiàn)

上面介紹了MVC/MVP/MVVM的各自特點(diǎn),其中MVC/MVP的具體使用方式,本文不再展開實(shí)現(xiàn),接下來主要聊一下MVVM的使用及封裝,MVVM也是官方推薦的架構(gòu)模式。

Jetpack MVVM

Jetpack是官方推出的一系列組件庫,使用組件庫開發(fā)有很多好處,如:

  • 遵循最佳做法:采用最新的設(shè)計(jì)方法構(gòu)建,具有向后兼容性,可以減少崩潰和內(nèi)存泄漏
  • 消除樣板代碼:開發(fā)者可以更好地專注業(yè)務(wù)邏輯
  • 減少不一致:可以在各種Android版本中運(yùn)行,兼容性更好。

為了實(shí)現(xiàn)上面的MVVM架構(gòu)模式,Jetpack提供了多個組件來實(shí)現(xiàn),具體來說有Lifecycle、LiveData、ViewModel(這里的ViewModel是MVVM中ViewModel層的具體實(shí)現(xiàn)),其中Lifecycle負(fù)責(zé)生命周期相關(guān);LiveData賦予類可觀察,同時還是生命周期感知的(內(nèi)部使用了Lifecycle);ViewModel旨在以注重生命周期的方式存儲和管理界面相關(guān)的數(shù)據(jù),針對這幾個庫的詳細(xì)介紹及使用方式就不再展開了。

通過這幾個庫,就可以實(shí)現(xiàn)MVVM了,官方也發(fā)布了MVVM的架構(gòu)圖:

其中Activity/FragmentView層,ViewModel+LiveDataViewModel層,為了統(tǒng)一管理網(wǎng)絡(luò)數(shù)據(jù)及本地?cái)?shù)據(jù)數(shù)據(jù),又引入了Repository中間管理層,本質(zhì)上是為了更好地管理數(shù)據(jù),為了簡單把他們統(tǒng)稱為Model層吧。

使用舉例

  • View層代碼:
//MvvmExampleActivity.kt
class MvvmExampleActivity : BaseActivity() {

    private val mTvContent: TextView by id(R.id.tv_content)
    private val mBtnQuest: Button by id(R.id.btn_request)
    private val mToolBar: Toolbar by id(R.id.toolbar)

    override fun getLayoutId(): Int {
        return R.layout.activity_wan_android
    }

    override fun initViews() {
        initToolBar(mToolBar, "Jetpack MVVM", true)
    }

    override fun init() {
        //獲取ViewModel實(shí)例,注意這里不能直接new,因?yàn)閂iewModel的生命周期比Activity長
        mViewModel = ViewModelProvider(this).get(WanViewModel::class.java)

        mBtnQuest.setOnClickListener {
            //請求數(shù)據(jù)
            mViewModel.getWanInfo()
        }

        //ViewModel中的LiveData注冊觀察者并監(jiān)聽數(shù)據(jù)變化
        mViewModel.mWanLiveData.observe(this) { list ->
            val builder = StringBuilder()
            for (index in list.indices) {
                //每條數(shù)據(jù)進(jìn)行折行顯示
                if (index != list.size - 1) {
                    builder.append(list[index])
                    builder.append("\n\n")
                } else {
                    builder.append(list[index])
                }
            }
            mTvContent.text = builder.toString()
        }
    }
}

  • ViewModel層代碼:
//WanViewModel.kt
class WanViewModel : ViewModel() {
    //LiveData
    val mWanLiveData = MutableLiveData<List<WanModel>>()
    //loading
    val loadingLiveData = SingleLiveData<Boolean>()
    //異常
    val errorLiveData = SingleLiveData<String>()

    //Repository中間層 管理所有數(shù)據(jù)來源 包括本地的及網(wǎng)絡(luò)的
    private val mWanRepo = WanRepository()

    fun getWanInfo(wanId: String = "") {
        //展示Loading
        loadingLiveData.postValue(true)
        viewModelScope.launch(Dispatchers.IO) {
            try {
                val result = mWanRepo.requestWanData(wanId)
                when (result.state) {
                    State.Success -> mWanLiveData.postValue(result.data)
                    State.Error -> errorLiveData.postValue(result.msg)
                }
            } catch (e: Exception) {
                error(e.message ?: "")
            } finally {
                loadingLiveData.postValue(false)
            }
        }
    }
}

  • Repository層(Model層)代碼:
class WanRepository {

    //請求網(wǎng)絡(luò)數(shù)據(jù)
    suspend fun requestWanData(drinkId: String): BaseData<List<WanModel>> {
        val service = RetrofitUtil.getService(DrinkService::class.java)

        val baseData = service.getBanner()
        if (baseData.code == 0) {
            //正確
            baseData.state = State.Success
        } else {
            //錯誤
            baseData.state = State.Error
        }
        return baseData
    }
}

這里只通過Retrofit請求了網(wǎng)絡(luò)數(shù)據(jù) 玩Android 開放API,如果需要添加本地?cái)?shù)據(jù),只需要在方法里添加本地?cái)?shù)據(jù)處理即可,即 Repository是數(shù)據(jù)的管理中間層,對數(shù)據(jù)進(jìn)行統(tǒng)一管理,ViewModel層中不需要關(guān)心數(shù)據(jù)的來源,大家各司其職即可,符合單一職責(zé),代碼可讀性更好,同時也更加解耦。在View層點(diǎn)擊按鈕請求數(shù)據(jù),執(zhí)行結(jié)果如下:

以上就完成了一次網(wǎng)絡(luò)請求,相比于MVPMVVM既不用聲明多個接口及方法,同時ViewModel也不會像Presenter那樣去持有View層的引用,而是生命周期感知的,MVVM方式更加解耦。

封裝

上一節(jié)介紹了Jetpack MVVM的使用例子,可以看到有一些代碼邏輯是可以抽離出來封裝到公共部分的,那么本節(jié)就嘗試對其做一次封裝。

首先,請求數(shù)據(jù)時可能會展示Loading,請求完后可能是空數(shù)據(jù)、錯誤數(shù)據(jù),對應(yīng)下面的IStatusView接口聲明:

interface IStatusView {
    fun showEmptyView() //空視圖
    fun showErrorView(errMsg: String) //錯誤視圖
    fun showLoadingView(isShow: Boolean) //展示Loading視圖
}

因?yàn)閂iewModel是在Activity中初始化的,所以可以封裝成一個Base類:

abstract class BaseMvvmActivity<VM : BaseViewModel> : BaseActivity(), IStatusView {

    protected lateinit var mViewModel: VM
    protected lateinit var mView: View
    private lateinit var mLoadingDialog: LoadingDialog

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mLoadingDialog = LoadingDialog(this, false)
        mViewModel = getViewModel()!!
        init()
        registerEvent()
    }

    /**
     * 獲取ViewModel 子類可以復(fù)寫,自行初始化
     */
    protected open fun getViewModel(): VM? {
        //當(dāng)前對象超類的Type
        val type = javaClass.genericSuperclass
        //ParameterizedType表示參數(shù)化的類型
        if (type != null && type is ParameterizedType) {
            //返回此類型實(shí)際類型參數(shù)的Type對象數(shù)組
            val actualTypeArguments = type.actualTypeArguments
            val tClass = actualTypeArguments[0]
            return ViewModelProvider(this).get(tClass as Class<VM>)
        }
        return null
    }

    override fun showLoadingView(isShow: Boolean) {
        if (isShow) {
            mLoadingDialog.showDialog(this, false)
        } else {
            mLoadingDialog.dismissDialog()
        }
    }

    override fun showEmptyView() {
       ......
    }

    //錯誤視圖 并且可以重試
    override fun showErrorView(errMsg: String) {
       .......
    }

    private fun registerEvent() {
       //接收錯誤信息
       mViewModel.errorLiveData.observe(this) { errMsg ->
           showErrorView(errMsg)
       }
       //接收Loading信息
       mViewModel.loadingLiveData.observe(this, { isShow ->
           showLoadingView(isShow)
       })
    }

    abstract fun init()
}

Base類中初始化ViewModel,還可以通過官方activity-ktx、fragment-ktx擴(kuò)展庫,初始化方式:val model: VM by viewModels()。

子類中繼承如下:

class MvvmExampleActivity : BaseMvvmActivity<WanViewModel>() {

    private val mTvContent: TextView by id(R.id.tv_content)
    private val mBtnQuest: Button by id(R.id.btn_request)
    private val mToolBar: Toolbar by id(R.id.toolbar)

    override fun getLayoutId(): Int {
        return R.layout.activity_wan_android
    }

    override fun initViews() {
        initToolBar(mToolBar, "Jetpack MVVM", true)
    }

    override fun init() {
        mBtnQuest.setOnClickListener {
            //請求數(shù)據(jù)
            mViewModel.getWanInfo()
        }
        /**
         * 這里使用了擴(kuò)展函數(shù),等同于mViewModel.mWanLiveData.observe(this) {}
         */
        observe(mViewModel.mWanLiveData) { list ->
            val builder = StringBuilder()
            for (index in list.indices) {
                //每條數(shù)據(jù)進(jìn)行折行顯示
                if (index != list.size - 1) {
                    builder.append(list[index])
                    builder.append("\n\n")
                } else {
                    builder.append(list[index])
                }
            }
            mTvContent.text = builder.toString()
        }
    }
}

我們把ViewModel的初始化放到了父類里進(jìn)行,代碼看上去更簡單了。監(jiān)聽數(shù)據(jù)變化mViewModel.mWanLiveData.observe(this) {} 方式改成observe(mViewModel.mWanLiveData) {}方式,少傳了一個LifecycleOwner,其實(shí)這是一個擴(kuò)展函數(shù),如下:

fun <T> LifecycleOwner.observe(liveData: LiveData<T>, observer: (t: T) -> Unit) {
    liveData.observe(this, { observer(it) })
}

ps:我們初始化View控件時,如 private val mBtnQuest: Button by id(R.id.btn_request),依然使用了擴(kuò)展函數(shù),如下:

fun <T : View> Activity.id(id: Int) = lazy {
    findViewById<T>(id)
}

不用像寫java代碼中那樣時刻要想著判空,同時只會在使用時才會進(jìn)行初始化,很實(shí)用!

說回來,接著是ViewModel層的封裝,BaseViewModel.kt

abstract class BaseViewModel : ViewModel() {
    //loading
    val loadingLiveData = SingleLiveData<Boolean>()
    //異常
    val errorLiveData = SingleLiveData<String>()

    /**
     * @param request 正常邏輯
     * @param error 異常處理
     * @param showLoading 請求網(wǎng)絡(luò)時是否展示Loading
     */
    fun launchRequest(
        showLoading: Boolean = true,
        error: suspend (String) -> Unit = { errMsg ->
            //默認(rèn)異常處理,子類可以進(jìn)行覆寫
            errorLiveData.postValue(errMsg)
        }, request: suspend () -> Unit
    ) {
        //是否展示Loading
        if (showLoading) {
            loadStart()
        }

        //使用viewModelScope.launch開啟協(xié)程
        viewModelScope.launch(Dispatchers.IO) {
            try {
                request()
            } catch (e: Exception) {
                error(e.message ?: "")
            } finally {
                if (showLoading) {
                    loadFinish()
                }
            }
        }
    }

    private fun loadStart() {
        loadingLiveData.postValue(true)
    }

    private fun loadFinish() {
        loadingLiveData.postValue(false)
    }
}

擴(kuò)展一下1、上面執(zhí)行網(wǎng)絡(luò)請求時,使用viewModelScope.launch來啟動協(xié)程,引入方式:

implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'

這樣就可以直接在ViewModel中啟動協(xié)程并且當(dāng)ViewModel生命周期結(jié)束時協(xié)程也會自動關(guān)閉,避免使用GlobalScope.launch { }MainScope().launch { }還需自行關(guān)閉協(xié)程, 當(dāng)然,如果是在Activity/Fragment、liveData中使用協(xié)程,也可以按需引入:

implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'

具體可以參見官方的 將 Kotlin 協(xié)程與生命周期感知型組件一起使用 這篇文章。

2、另外細(xì)心的讀者可能觀察到,上面我們的Loading、Error信息監(jiān)聽都是用的SingleLiveData,把這個類打代碼貼一下:

/**
 * 多個觀察者存在時,只有一個Observer能夠收到數(shù)據(jù)更新
 * https://github.com/android/architecture-samples/blob/dev-todo-mvvm-live/todoapp/app/src/main/java/com/example/android/architecture/blueprints/todoapp/SingleLiveEvent.java
 */
class SingleLiveData<T> : MutableLiveData<T>() {
    companion object {
        private const val TAG = "SingleLiveEvent"
    }
    private val mPending = AtomicBoolean(false)

    @MainThread
    override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
        if (hasActiveObservers()) {
            Log.w(TAG, "Multiple observers registered but only one will be notified of changes.")
        }
        // Observe the internal MutableLiveData
        super.observe(owner) { t ->
            //如果expect為true,那么將值update為false,方法整體返回true,
            //即當(dāng)前Observer能夠收到更新,后面如果還有訂閱者,不能再收到更新通知了
            if (mPending.compareAndSet(true, false)) {
                observer.onChanged(t)
            }
        }
    }

    override fun setValue(@Nullable value: T?) {
        //AtomicBoolean中設(shè)置的值設(shè)置為true
        mPending.set(true)
        super.setValue(value)
    }

    /**
     * Used for cases where T is Void, to make calls cleaner.
     */
    @MainThread
    fun call() {
        value = null
    }
}

可以看到SingleLiveData還是繼承自MutableLiveData,區(qū)別是當(dāng)多個觀察者存在時,只有一個Observer能夠收到數(shù)據(jù)更新,本質(zhì)上是在observe()時通過CAS加了限制,注釋已經(jīng)很詳細(xì)了,不再贅述。

子類中繼承如下:

class WanViewModel : BaseViewModel() {
    //LiveData
    val mWanLiveData = MutableLiveData<List<WanModel>>()

    //Repository中間層 管理所有數(shù)據(jù)來源 包括本地的及網(wǎng)絡(luò)的
    private val mWanRepo = WanRepository()

    fun getWanInfo(wanId: String = "") {
        launchRequest {
            val result = mWanRepo.requestWanData(wanId)
            when (result.state) {
                State.Success -> mWanLiveData.postValue(result.data)
                State.Error -> errorLiveData.postValue(result.msg)
            }
        }
    }
}

最后是對Model層的封裝,BaseRepository.kt

open class BaseRepository {
    suspend fun <T : Any> executeRequest(
        block: suspend () -> BaseData<T>
    ): BaseData<T> {
        val baseData = block.invoke()
        if (baseData.code == 0) {
            //正確
            baseData.state = State.Success
        } else {
            //錯誤
            baseData.state = State.Error
        }
        return baseData
    }
}

數(shù)據(jù)基類BaseData.kt

class BaseData<T> {
    @SerializedName("errorCode")
    var code = -1
    @SerializedName("errorMsg")
    var msg: String? = null
    var data: T? = null
    var state: State = State.Error
}

enum class State {
    Success, Error
}

子類中繼承如下:

class WanRepository : BaseRepository() {
    suspend fun requestWanData(drinkId: String): BaseData<List<WanModel>> {
        val service = RetrofitUtil.getService(DrinkService::class.java)
        return executeRequest {
            service.getBanner()
        }
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容