文章目錄
- Android開發架構
- MVC
- MVP
- MVVM
- UI驅動 vs 資料驅動
- MVVM的具體實作
- Jetpack MVVM
- 使用舉例
- 封裝
- 參考
Android開發架構
如果開發程序中大家各自為戰,沒有統一規范,久而久之,專案代碼會變得混亂且后續難以維護,當使用統一的架構模式后,有很多的好處,如:
- 統一開發規范,使得代碼整潔、規范,后續易于維護及擴展
- 提高開發效率(尤其在團隊人員較多時)
- 模塊單一職責,使得模塊專注自己內部(面向物件),模塊間解耦
總之,開發架構是前人總結出來的一套行之有效的開發模式,目的是達到高內聚,低耦合的效果,使得專案代碼更健壯、易維護,
Android中常見的架構模式有MVC(Model-View-Controller)、MVP(Model-View-Presenter)、MVVM(Model-View-ViewModel),一起來看下各自的特點:
MVC
MVC(Model-View-Controller)是比較早期的架構模式,模式整體也比較簡單,

MVC模式將程式分成了三個部分:
- Model模型層:業務相關的資料(如網路請求資料、本地資料庫資料等)及其對資料的處理
- View視圖層:頁面視圖(通過XML布局撰寫視圖層),負責接收用戶輸入、發起資料請求及展示結果頁面
- Controller控制器層:M與V之間的橋梁,負責業務邏輯
MVC特點:
- 簡單易用:上圖表述了資料整個流程:
View接收用戶操作,通過Controller去處理業務邏輯,并通過Model去獲取/更新資料,然后Model層又將最新的資料傳回View層進行頁面展示, - 架構簡單的另一面往往是對應的副作用:由于XML布局能力弱,我們的View層的很多操作都是寫在Activity/Fragment中,同時,Controller、Model層的代碼也大都寫在Activity/Fragment中,這就會導致一個問題,當業務邏輯比較復雜時,Activity/Fragment中的代碼量會很大,其違背了類單一職責,不利于后續擴展及維護,尤其是后期你剛接手的專案,一個Activity/Fragment類中的代碼動輒上千行代碼,那感覺著實酸爽:

當然,如果業務很簡單,使用MVC模式還是一種不錯的選擇,
MVP
MVP(Model-View-Presenter),架構圖如下:

MVP各模塊職責如下:
- Model模型:業務相關的資料(如網路請求資料、本地資料庫資料等)及其對資料的處理
- View視圖:頁面視圖(Activity/Fragment),負責接收用戶輸入、發起資料請求及展示結果頁面
- Presenter:M與V之間的橋梁,負責業務邏輯
MVP特點:
View層接收用戶操作,并通過持有的Presenter去處理業務邏輯,請求資料;接著Presenter層通過Model去獲取資料,然后Model又將最新的資料傳回Presenter層,Presenter層又持有View層的參考,進而將資料傳給View層進行展示,
MVP相比MVC的幾處變化:
- View層與Model層不再互動,而是通過Presenter去進行聯系
- 本質上MVP是面向介面編程,Model/View/Presenter每層的職責分工明確,當業務復雜時,整個流程邏輯也是很清晰的
當然,MVP也不是十全十美的,MVP本身也存在以下問題:
View層會抽象成IView介面,并在IView中宣告一些列View相關的方法;同樣的,Presenter會被抽象成IPresenter介面及其一些列方法,每當實作一個功能時,都需要撰寫多個介面及其對應的方法,實作起來相對比較繁瑣,而且每次有改動時,對應的介面方法也基本都會再去改動,View層與Presenter層相互持有,當View層關閉時,由于Presenter層不是生命周期感知的,可能會導致記憶體泄漏甚至是崩潰,
ps:如果你的專案中使用了RxJava,可以使用 AutoDispose 自動解綁,
MVVM
MVVM(Model-View-ViewModel),架構圖如下:

MVVM各職責如下:
- Model模型:業務相關的資料(如網路請求資料、本地資料庫資料等)及其對資料的處理
- View視圖:頁面視圖(Activity/Fragment),負責接收用戶輸入、發起資料請求及展示結果頁面
- ViewModel:M與V之間的橋梁,負責業務邏輯
MVVM特點:
- View層接收用戶操作,并通過持有的ViewModel去處理業務邏輯,請求資料;
- ViewModel層通過Model去獲取資料,然后Model又將最新的資料傳回ViewModel層,到這里,ViewModel與Presenter所做的事基本是一樣的,但是ViewModel不會也不能持有View層的參考,而是View層會通過觀察者模式監聽ViewModel層的資料變化,當有新資料時,View層能自動收到新資料并重繪界面,
UI驅動 vs 資料驅動
MVP中,Presenter中需要持有View層的參考,當資料變化時,需要主動呼叫View層對應的方法將資料傳過去并進行UI重繪,這種可以認為是UI驅動;而MVVM中,ViewModel并不會持有View層的參考,View層會監聽資料變化,當ViewModel中有資料更新時,View層能直接拿到新資料并完成UI更新,這種可以認為是資料驅動,顯然,MVVM相比于MVP來說更加解耦,
MVVM的具體實作
上面介紹了MVC/MVP/MVVM的各自特點,其中MVC/MVP的具體使用方式,本文不再展開實作,接下來主要聊一下MVVM的使用及封裝,MVVM也是官方推薦的架構模式,
Jetpack MVVM
Jetpack是官方推出的一系列組件庫,使用組件庫開發有很多好處,如:
- 遵循最佳做法:采用最新的設計方法構建,具有向后兼容性,可以減少崩潰和記憶體泄漏
- 消除樣板代碼:開發者可以更好地專注業務邏輯
- 減少不一致:可以在各種Android版本中運行,兼容性更好,
為了實作上面的MVVM架構模式,Jetpack提供了多個組件來實作,具體來說有Lifecycle、LiveData、ViewModel(這里的ViewModel是MVVM中ViewModel層的具體實作),其中Lifecycle負責生命周期相關;LiveData賦予類可觀察,同時還是生命周期感知的(內部使用了Lifecycle);ViewModel旨在以注重生命周期的方式存盤和管理界面相關的資料,針對這幾個庫的詳細介紹及使用方式不再展開,有興趣的可以參見前面的文章:
- Android Jetpack系列之Lifecycle
- Android Jetpack系列之LiveData
- Android Jetpack系列之ViewModel
通過這幾個庫,就可以實作MVVM了,官方也發布了MVVM的架構圖:

其中Activity/Fragment為View層,ViewModel+LiveData為ViewModel層,為了統一管理網路資料及本地資料資料,又引入了Repository中間管理層,本質上是為了更好地管理資料,為了簡單把他們統稱為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實體,注意這里不能直接new,因為ViewModel的生命周期比Activity長
mViewModel = ViewModelProvider(this).get(WanViewModel::class.java)
mBtnQuest.setOnClickListener {
//請求資料
mViewModel.getWanInfo()
}
//ViewModel中的LiveData注冊觀察者并監聽資料變化
mViewModel.mWanLiveData.observe(this) { list ->
val builder = StringBuilder()
for (index in list.indices) {
//每條資料進行折行顯示
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中間層 管理所有資料來源 包括本地的及網路的
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 {
//請求網路資料
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請求了網路資料 玩Android 開放API,如果需要添加本地資料,只需要在方法里添加本地資料處理即可,即 Repository是資料的管理中間層,對資料進行統一管理,ViewModel層中不需要關心資料的來源,大家各司其職即可,符合單一職責,代碼可讀性更好,同時也更加解耦,在View層點擊按鈕請求資料,執行結果如下:

以上就完成了一次網路請求,相比于MVP,MVVM既不用宣告多個介面及方法,同時ViewModel也不會像Presenter那樣去持有View層的參考,而是生命周期感知的,MVVM方式更加解耦,
封裝
上一節介紹了Jetpack MVVM的使用例子,可以看到有一些代碼邏輯是可以抽離出來封裝到公共部分的,那么本節就嘗試對其做一次封裝,
首先,請求資料時可能會展示Loading,請求完后可能是空資料、錯誤資料,對應下面的IStatusView介面宣告:
interface IStatusView {
fun showEmptyView() //空視圖
fun showErrorView(errMsg: String) //錯誤視圖
fun showLoadingView(isShow: Boolean) //展示Loading視圖
}
因為ViewModel是在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 子類可以復寫,自行初始化
*/
protected open fun getViewModel(): VM? {
//當前物件超類的Type
val type = javaClass.genericSuperclass
//ParameterizedType表示引數化的型別
if (type != null && type is ParameterizedType) {
//回傳此型別實際型別引數的Type物件陣列
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擴展庫,初始化方式: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 {
//請求資料
mViewModel.getWanInfo()
}
/**
* 這里使用了擴展函式,等同于mViewModel.mWanLiveData.observe(this) {}
*/
observe(mViewModel.mWanLiveData) { list ->
val builder = StringBuilder()
for (index in list.indices) {
//每條資料進行折行顯示
if (index != list.size - 1) {
builder.append(list[index])
builder.append("\n\n")
} else {
builder.append(list[index])
}
}
mTvContent.text = builder.toString()
}
}
}
我們把ViewModel的初始化放到了父類里進行,代碼看上去更簡單了,監聽資料變化mViewModel.mWanLiveData.observe(this) {} 方式改成observe(mViewModel.mWanLiveData) {}方式,少傳了一個LifecycleOwner,其實這是一個擴展函式,如下:
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),依然使用了擴展函式,如下:
fun <T : View> Activity.id(id: Int) = lazy {
findViewById<T>(id)
}
不用像寫java代碼中那樣時刻要想著判空,同時只會在使用時才會進行初始化,很實用!
說回來,接著是ViewModel層的封裝,BaseViewModel.kt:
abstract class BaseViewModel : ViewModel() {
//loading
val loadingLiveData = SingleLiveData<Boolean>()
//例外
val errorLiveData = SingleLiveData<String>()
/**
* @param request 正常邏輯
* @param error 例外處理
* @param showLoading 請求網路時是否展示Loading
*/
fun launchRequest(
showLoading: Boolean = true,
error: suspend (String) -> Unit = { errMsg ->
//默認例外處理,子類可以進行覆寫
errorLiveData.postValue(errMsg)
}, request: suspend () -> Unit
) {
//是否展示Loading
if (showLoading) {
loadStart()
}
//使用viewModelScope.launch開啟協程
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)
}
}
擴展一下:
1、上面執行網路請求時,使用viewModelScope.launch來啟動協程,引入方式:
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'
這樣就可以直接在ViewModel中啟動協程并且當ViewModel生命周期結束時協程也會自動關閉,避免使用GlobalScope.launch { }或MainScope().launch { }還需自行關閉協程,
當然,如果是在Activity/Fragment、liveData中使用協程,也可以按需引入:
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'
具體可以參見官方的 將 Kotlin 協程與生命周期感知型組件一起使用 這篇文章,
2、另外細心的讀者可能觀察到,上面我們的Loading、Error資訊監聽都是用的SingleLiveData,把這個類打代碼貼一下:
/**
* 多個觀察者存在時,只有一個Observer能夠收到資料更新
* 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,
//即當前Observer能夠收到更新,后面如果還有訂閱者,不能再收到更新通知了
if (mPending.compareAndSet(true, false)) {
observer.onChanged(t)
}
}
}
override fun setValue(@Nullable value: T?) {
//AtomicBoolean中設定的值設定為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,區別是當多個觀察者存在時,只有一個Observer能夠收到資料更新,本質上是在observe()時通過CAS加了限制,注釋已經很詳細了,不再贅述,
子類中繼承如下:
class WanViewModel : BaseViewModel() {
//LiveData
val mWanLiveData = MutableLiveData<List<WanModel>>()
//Repository中間層 管理所有資料來源 包括本地的及網路的
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
}
}
資料基類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()
}
}
}
到此,基本上就完成了,代碼中有一些細節沒貼出來,完整代碼參見:Jetpack MVVM
參考
【1】Android應用架構指南
【2】https://www.php.cn/faq/417265.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/300271.html
標籤:其他
