從0快速搭建一個(gè)實(shí)用的MVVM框架(超詳細(xì))
結(jié)合Jetpack,構(gòu)建快速開發(fā)的MVVM框架。
項(xiàng)目使用Jetpack:LiveData、ViewModel、Lifecycle、Navigation組件。
支持動(dòng)態(tài)加載多狀態(tài)布局:加載中、成功、失敗、標(biāo)題;
支持快速生成ListActivity、ListFragment;
支持使用插件快速生成適用于本框架的Activity、Fragment、ListActivity、ListFragment。
完整文章前往Github瀏覽
前言
隨著
Jetpack的完善,對(duì)于開發(fā)者來說,MVVM顯得越來越高效與方便。對(duì)于使用
MVVM的公司來說,都有一套自己的MVVM框架,但是我發(fā)現(xiàn)有些只是對(duì)框架進(jìn)行非常簡(jiǎn)單的封裝,導(dǎo)致在開發(fā)過程中會(huì)出現(xiàn)很多沒必要的冗余代碼。這篇文章主要就是分享如何從0搭建一個(gè)高效的
MVVM框架。
基于MVVM進(jìn)行快速開發(fā), 上手即用。(重構(gòu)已完成,正在編寫SampleApp)
對(duì)基礎(chǔ)框架進(jìn)行模塊分離, 分為
MVVM Library--MVVM Navigation Library--MVVM Network Library可基于業(yè)務(wù)需求使用MVVM Library、MVVM Navigation Library、MVVM Network Library
已開發(fā)一鍵生成代碼模板, 創(chuàng)建適用于本框架的Activity和Fragment. 具體查看AlvinMVVMPlugin_4_3
如何集成
To get a Git project into your build:
Step 1. Add the JitPack repository to your build file
Add it in your root build.gradle at the end of repositories:
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}Step 2. Add the dependency
dependencies {
// MVVM 基類
implementation 'com.github.Chen-Xi-g.MVVMFramework:mvvm_framework:Tag'
// MVVM Network 只負(fù)責(zé)網(wǎng)絡(luò)處理
implementation 'com.github.Chen-Xi-g.MVVMFramework:mvvm_network:Tag'
// MVVM Navigation 組件抽離
implementation 'com.github.Chen-Xi-g.MVVMFramework:mvvm_navigation:Tag'
}依賴引入后,需要初始化依賴,下面是模塊化初始化流程。
1.繼承BaseApplication
創(chuàng)建你的Application類,繼承BaseApplication,并且需要在onCreate函數(shù)中進(jìn)行配置和初始化相關(guān)參數(shù),可以在這里配置網(wǎng)絡(luò)請(qǐng)求框架的參數(shù)和UI全局參數(shù)。比如攔截器和多域名,全局Activity和Fragment屬性。
// 全局Activity設(shè)置
GlobalMVVMBuilder.initSetting(BaseActivitySetting(), BaseFragmentSetting())
private fun initHttpManager() {
// 參數(shù)攔截器
HttpManager.instance.setting {
// 設(shè)置網(wǎng)絡(luò)屬性
setTimeUnit(TimeUnit.SECONDS) // 時(shí)間類型 秒, 框架默認(rèn)值 毫秒
setReadTimeout(30) // 讀取超時(shí) 30s, 框架默認(rèn)值 10000L
setWriteTimeout(30) // 寫入超時(shí) 30s, 框架默認(rèn)值 10000L
setConnectTimeout(30) // 鏈接超時(shí) 30s,框架默認(rèn)值 10000L
setRetryOnConnectionFailure(true) // 超時(shí)自動(dòng)重連, 框架默認(rèn)值 true
setBaseUrl("https://www.wanandroid.com") // 默認(rèn)域名
// 多域名配置
setDomain {
Constant.domainList.forEach { map ->
map.forEach {
if (it.key.isNotEmpty() && it.value.isNotEmpty()) {
put(it.key, it.value)
}
}
}
}
setLoggingInterceptor(
isDebug = BuildConfig.DEBUG,
hideVerticalLine = true,
requestTag = "HTTP Request 請(qǐng)求參數(shù)",
responseTag = "HTTP Response 返回參數(shù)"
)
// 添加攔截器
setInterceptorList(hashSetOf(ResponseInterceptor(), ParameterInterceptor()))
}
}
// 需要重寫,傳入當(dāng)前是否初始Debug模式。
override fun isLogDebug(): Boolean {
// 是否顯示日志
return BuildConfig.DEBUG2.創(chuàng)建ViewModel擴(kuò)展函數(shù)
所有模塊需要依賴的base模塊創(chuàng)建ViewModel相關(guān)的擴(kuò)展函數(shù)VMKxt和Json實(shí)體類殼BaseEntity。
/**
* 過濾服務(wù)器結(jié)果,失敗拋異常
* @param block 請(qǐng)求體方法,必須要用suspend關(guān)鍵字修飾
* @param success 成功回調(diào)
* @param error 失敗回調(diào) 可不傳
* @param isLoading 是否顯示 Loading 布局
* @param loadingMessage 加載框提示內(nèi)容
*/
fun <T> BaseViewModel.request(
block: suspend () -> BaseResponse<T>,
success: (T?) -> Unit,
error: (ResponseThrowable) -> Unit = {},
isLoading: Boolean = false,
loadingMessage: String? = null
): Job {
// 開始執(zhí)行請(qǐng)求
httpCallback.beforeNetwork.postValue(
// 執(zhí)行Loading邏輯
LoadingEntity(
isLoading,
loadingMessage?.isNotEmpty() == true,
loadingMessage ?: ""
)
)
return viewModelScope.launch {
kotlin.runCatching {
//請(qǐng)求體
block()
}.onSuccess {
// 網(wǎng)絡(luò)請(qǐng)求成功, 結(jié)束請(qǐng)求
httpCallback.afterNetwork.postValue(false)
//校驗(yàn)請(qǐng)求結(jié)果碼是否正確,不正確會(huì)拋出異常走下面的onFailure
kotlin.runCatching {
executeResponse(it) { coroutine ->
success(coroutine)
}
}.onFailure { error ->
// 請(qǐng)求時(shí)發(fā)生異常, 執(zhí)行失敗回調(diào)
val responseThrowable = ExceptionHandle.handleException(error)
httpCallback.onFailed.value = responseThrowable.errorMsg ?: ""
responseThrowable.errorLog?.let { errorLog ->
LogUtil.e(errorLog)
}
// 執(zhí)行失敗的回調(diào)方法
error(responseThrowable)
}
}.onFailure { error ->
// 請(qǐng)求時(shí)發(fā)生異常, 執(zhí)行失敗回調(diào)
val responseThrowable = ExceptionHandle.handleException(error)
httpCallback.onFailed.value = responseThrowable.errorMsg ?: ""
responseThrowable.errorLog?.let { errorLog ->
LogUtil.e(errorLog)
}
// 執(zhí)行失敗的回調(diào)方法
error(responseThrowable)
}
}
}
/**
* 不過濾服務(wù)器結(jié)果
* @param block 請(qǐng)求體方法,必須要用suspend關(guān)鍵字修飾
* @param success 成功回調(diào)
* @param error 失敗回調(diào) 可不傳
* @param isLoading 是否顯示 Loading 布局
* @param loadingMessage 加載框提示內(nèi)容
*/
fun <T> BaseViewModel.requestNoCheck(
block: suspend () -> T,
success: (T) -> Unit,
error: (ResponseThrowable) -> Unit = {},
isLoading: Boolean = false,
loadingMessage: String? = null
): Job {
// 開始執(zhí)行請(qǐng)求
httpCallback.beforeNetwork.postValue(
// 執(zhí)行Loading邏輯
LoadingEntity(
isLoading,
loadingMessage?.isNotEmpty() == true,
loadingMessage ?: ""
)
)
return viewModelScope.launch {
runCatching {
//請(qǐng)求體
block()
}.onSuccess {
// 網(wǎng)絡(luò)請(qǐng)求成功, 結(jié)束請(qǐng)求
httpCallback.afterNetwork.postValue(false)
//成功回調(diào)
success(it)
}.onFailure { error ->
// 請(qǐng)求時(shí)發(fā)生異常, 執(zhí)行失敗回調(diào)
val responseThrowable = ExceptionHandle.handleException(error)
httpCallback.onFailed.value = responseThrowable.errorMsg ?: ""
responseThrowable.errorLog?.let { errorLog ->
LogUtil.e(errorLog)
}
// 執(zhí)行失敗的回調(diào)方法
error(responseThrowable)
}
}
}
/**
* 請(qǐng)求結(jié)果過濾,判斷請(qǐng)求服務(wù)器請(qǐng)求結(jié)果是否成功,不成功則會(huì)拋出異常
*/
suspend fun <T> executeResponse(
response: BaseResponse<T>,
success: suspend CoroutineScope.(T?) -> Unit
) {
coroutineScope {
when {
response.isSuccess() -> {
success(response.getResponseData())
}
else -> {
throw ResponseThrowable(
response.getResponseCode(),
response.getResponseMessage(),
response.getResponseMessage()
)
}
}
}
}以上代碼封裝了快速的網(wǎng)絡(luò)請(qǐng)求擴(kuò)展函數(shù),并且可以根據(jù)自己的情況,選擇脫殼或者不脫殼的回調(diào)處理。 調(diào)用示例:
/**
* 加載列表數(shù)據(jù)
*/
fun getArticleListData(page: Int, pageSize: Int) {
request(
{
filterArticleList(page, pageSize)
}, {
// 成功操作
it?.let {
_articleListData.postValue(it.datas)
}
}
)
}完成上面的操作,你就可以進(jìn)入愉快的開發(fā)工作了。
3.引入一鍵生成代碼插件(可選)
每次創(chuàng)建Activity、Fragment、ListActivity、ListFragment都是重復(fù)的工作,為了可以更高效的開發(fā),減少這些枯燥的操作,特地編寫的快速生成MVVM代碼的插件,該插件只適用于當(dāng)前MVVM框架,具體使用請(qǐng)前往AlvinMVVMPlugin。集成后你就可以開始像創(chuàng)建EmptyActivity這樣創(chuàng)建MVVMActivity。
框架結(jié)構(gòu)
mvvm
該組件對(duì)Activity和Fragment進(jìn)行常用屬性封裝
base包下封裝了MVVM的基礎(chǔ)組件。activity實(shí)現(xiàn)DataBinding + ViewModel的封裝,以及一些其他功能。adapter實(shí)現(xiàn)DataBinding + Adapter的封裝。fragment實(shí)現(xiàn)DataBinding + ViewModel的封裝,以及一些其他功能。livedata實(shí)現(xiàn)LiveData的基礎(chǔ)功能封裝,如基本數(shù)據(jù)類型的非空返回值。view_model實(shí)現(xiàn)BaseViewModel的處理。
help包下封裝了組件的輔助類,在BaseApplication中進(jìn)行全局Actiivty、Fragment屬性賦值。manager包下封裝了對(duì)Activity的管理。utils包下封裝了LogUtil工具類,通過BaseApplication進(jìn)行初始化。
Activity封裝
AbstractActivity是Activity的抽象基類,這個(gè)類里面的方法適用于全部Activity的需求。 該類中封裝了所有Activity必須實(shí)現(xiàn)的抽象方法。BaseActivity封裝了基礎(chǔ)的Activity功能,主要用來初始化Activity公共功能:DataBinding的初始化、沉浸式狀態(tài)欄、AbstractActivity抽象方法的調(diào)用、屏幕適配、空白區(qū)域隱藏軟鍵盤。具體功能可以自行新增。BaseDialogActivity只負(fù)責(zé)顯示Dialog Loading彈窗,一般在提交請(qǐng)求或本地流處理時(shí)使用。也可以擴(kuò)展其他的Dialog,比如時(shí)間選擇器之類。BaseContentViewActivity是對(duì)布局進(jìn)行初始化操作的Activity,他是我們的核心。這里處理了每個(gè)Activity的每個(gè)狀態(tài)的布局,一般情況下有:TitleLayout公共標(biāo)題ContentLayout主要的內(nèi)容布局,使我們需要程序內(nèi)容的主要容器。ErrorLayout當(dāng)網(wǎng)絡(luò)請(qǐng)求發(fā)生錯(cuò)誤,需要對(duì)用戶進(jìn)行友好的提示。LoadingLayout正在加載數(shù)據(jù)的布局,給用戶一個(gè)良好的體驗(yàn),避免首次進(jìn)入頁(yè)面顯示的布局沒有數(shù)據(jù)。
BaseVMActivity實(shí)現(xiàn)ViewMode的Activity基類,通過泛型對(duì)ViewModel進(jìn)行實(shí)例化。并且通過BaseViewModel進(jìn)行公共操作。BaseMVVMActivity所有Activity最終需要繼承的MVVM類,通過傳入DataBinding和ViewModel的泛型進(jìn)行初始化操作,在構(gòu)造參數(shù)中還需要獲取Layout布局BaseListActivity適用于列表的Activity,分頁(yè)操作、上拉加載、下拉刷新、空布局、頭布局、底布局封裝。
Fragment封裝
根據(jù)你的需要進(jìn)行不同的封裝,我比較傾向于和Activity具有相同功能的封裝,也就是Activity封裝的功能我Fragment也要有。這樣在使用Navigation的時(shí)候可以減少Activity和Fragment的差異。這里直接參考Activity的封裝
Adapter封裝
每個(gè)項(xiàng)目中肯定會(huì)有列表的頁(yè)面,所以還需要對(duì)Adapter進(jìn)行DataBinding適配,這里使用的Adapter是BRVAH。
abstract class BaseBindingListAdapter<T, DB : ViewDataBinding>(
@LayoutRes private val layoutResId: Int
) : BaseQuickAdapter<T, BaseViewHolder>(layoutResId) {
abstract fun convert(holder: BaseViewHolder, item: T, dataBinding: DB?)
override fun convert(holder: BaseViewHolder, item: T) {
convert(holder, item, DataBindingUtil.bind(holder.itemView))
}
}LiveData封裝
LiveData在使用的時(shí)候會(huì)出現(xiàn)數(shù)據(jù)倒灌的情況,用簡(jiǎn)單的話來描述數(shù)據(jù)倒灌:A訂閱1月1日新聞信息,B訂閱1月15日新聞信息,但是B在1月15日同時(shí)收到了1月1日的信息,這明顯不符合我們生活中的邏輯,所以需要對(duì)LiveData進(jìn)行封裝,詳細(xì)的可以查看KunMinX的**UnPeek-LiveData**。
Navigation封裝
通過重寫 FragmentNavigator 將原來的 FragmentTransaction.replace() 方法替換為 hide()/Show()
ViewModel封裝
在BaseViewModel中封裝一個(gè)網(wǎng)絡(luò)請(qǐng)求需要用的LiveData,下面是一個(gè)簡(jiǎn)單的示例
open class BaseViewModel : ViewModel() {
// 默認(rèn)的網(wǎng)絡(luò)請(qǐng)求LiveData
val httpCallback: HttpCallback by lazy { HttpCallback() }
inner class HttpCallback {
/**
* 請(qǐng)求發(fā)生錯(cuò)誤
*
* String = 網(wǎng)絡(luò)請(qǐng)求異常
*/
val onFailed by lazy { StringLiveData() }
/**
* 請(qǐng)求開始
*
* LoadingEntity 顯示loading的實(shí)體類
*/
val beforeNetwork by lazy { EventLiveData<LoadingEntity>() }
/**
* 請(qǐng)求結(jié)束后框架自動(dòng)對(duì) loading 進(jìn)行處理
*
* false 關(guān)閉 loading or Dialog
* true 不關(guān)閉 loading or Dialog
*/
val afterNetwork by lazy { BooleanLiveData() }
}
}輔助類封裝
大部分的Activity和Fragment樣式基本相同,比如布局中的TitleLayout、LoadingLayout這些都是統(tǒng)一樣式。所以可以封裝全局的輔助類來對(duì)Activity中的屬性進(jìn)行抽離。
- 定義接口
ISettingBaseActivity添加抽離的方法,并且賦于默認(rèn)值。 - 定義接口
ISettingBaseFragment添加抽離的方法,并且賦于默認(rèn)值。 - 創(chuàng)建
ISettingBaseActivity和ISettingBaseFragment的實(shí)現(xiàn)類,進(jìn)行默認(rèn)的自定義操作。 - 創(chuàng)建
GlobalMVVMBuilder進(jìn)行賦值
管理類封裝
通過Lifecycle結(jié)合AppManager對(duì)Activity的進(jìn)出棧管理。
mvvm_navigation
分離Navigation,通過重寫 FragmentNavigator 將原來的 FragmentTransaction.replace() 方法替換為 hide()/Show()。
mvvm_network
使用 Retrofit + OkHttp + Moshi 對(duì)網(wǎng)絡(luò)請(qǐng)求進(jìn)行封裝,使用密封類自定義異常處理。
到此這篇關(guān)于從0搭建一個(gè)實(shí)用的MVVM框架的文章就介紹到這了,更多相關(guān)MVVM框架搭建內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解Android的MVVM框架 - 數(shù)據(jù)綁定
這篇文章主要介紹了詳解Android的MVVM框架 - 數(shù)據(jù)綁定,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-05-05
Android自定義控件之圓形進(jìn)度條動(dòng)畫
這篇文章主要為大家詳細(xì)介紹了Android自定義控件之圓形進(jìn)度條動(dòng)畫,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-07-07
Android實(shí)現(xiàn)帶頭像的用戶注冊(cè)頁(yè)面
這篇文章主要介紹了Android實(shí)現(xiàn)帶頭像的用戶注冊(cè)頁(yè)面的相關(guān)資料,介紹的非常詳細(xì),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-06-06
android加載系統(tǒng)相冊(cè)圖片并顯示詳解
大家好,本篇文章主要講的是android加載系統(tǒng)相冊(cè)圖片并顯示詳解,感興趣的同學(xué)趕快來看一看吧,對(duì)你有幫助的話記得收藏一下,方便下次瀏覽2021-12-12

