目標
簡單調(diào)用、少寫重復(fù)代碼
不依賴第三方庫(只含
Retrofit+Okhttp+協(xié)程
)完全不懂協(xié)程也能立馬上手(模板代碼)
用Kotlin的方式寫Kotlin代碼,什么意思呢专筷?對比一下下面2個代碼就知道了:
mViewModel.wxArticleLiveData.observe(this, object : IStateObserver<List<WxArticleBean>>() {
override fun onSuccess(data: List<WxArticleBean>?) {
}
override fun onError() {
}
})
mViewModel.wxArticleLiveData.observeState(this) {
onSuccess { data: List<WxArticleBean>? ->
}
onError {
}
}
既然是用Kotlin了死相,就不要用Java的方式寫接口回掉了,DSL
表達式不香么扁藕?
提供兩種方式實現(xiàn):
方式一代碼量更少沮峡,網(wǎng)絡(luò)請求自帶Loading,不需要手動調(diào)用Loading
方式二解耦更徹底
兩種方式設(shè)計思路在解耦這一塊存在差異亿柑,看具體需求邢疙,沒有誰好誰差,依照自己的項目望薄,哪個更方便用哪個疟游。
基于官方架構(gòu)的封裝:
一、封裝一
核心思想是:通過一個LiveData 貫穿整個流程痕支,借用網(wǎng)上一張圖:
Activity中的代碼示例
點擊請求網(wǎng)絡(luò)
mViewModel.getArticleData()
設(shè)置監(jiān)聽颁虐,只監(jiān)聽成功的結(jié)果,使用默認異常處理
mViewModel.wxArticleLiveData.observeState(this) {
onSuccess { data ->
Log.i("wutao","網(wǎng)絡(luò)請求的結(jié)果是:$data")
}
}
如果需要單獨處理每一個回調(diào)
這些回調(diào)都是可選的卧须,不需要可不實現(xiàn)
mViewModel.wxArticleLiveData.observeState(this) {
onSuccess { data ->
Log.i("wutao","網(wǎng)絡(luò)請求的結(jié)果是:$data")
}
onEmpty{
Log.i("wutao", "返回的數(shù)據(jù)是空另绩,展示空布局")
}
onFailed {
Log.i("wutao", "后臺返回的errorCode: $it")
}
onException { e ->
Log.i("wutao","這是非后臺返回的異常回調(diào)")
}
onShowLoading {
Log.i("wutao","自定義單個請求的Loading")
}
onComplete {
Log.i("wutao","網(wǎng)絡(luò)請求結(jié)束")
}
}
請求自帶Loading
很多網(wǎng)絡(luò)請求都需要Loading
花嘶,不想每次都寫onShowLoading{}
方法笋籽,也so easy。
mViewModel.wxArticleLoadingLiveData.observeState(this, this) {
onSuccess { data ->
Log.i("wutao","網(wǎng)絡(luò)請求的結(jié)果是:$data")
}
}
observeState()
第二個方法傳入ui的引用就可察绷,這樣單個網(wǎng)絡(luò)請求之前會自動加載Loading干签,成功或者失敗自動取消Loading。
上面代碼都是Activity
中拆撼,我們來看下ViewModel
中容劳。
ViewModel中代碼示例
class MainViewModel{
private val repository by lazy { WxArticleRepository() }
val wxArticleLiveData = StateLiveData<List<WxArticleBean>>()
fun requestNet() {
viewModelScope.launch {
repository.fetchWxArticle(wxArticleLiveData)
}
}
}
很簡單喘沿,引入對應(yīng)的數(shù)據(jù)倉庫Repo,然后使用協(xié)程執(zhí)行網(wǎng)絡(luò)請求方法竭贩。來看下Repo
中的代碼蚜印。
Repository
中代碼示例
class WxArticleRepository : BaseRepository() {
private val mService by lazy { RetrofitClient.service }
suspend fun fetchWxArticle(stateLiveData: StateLiveData<List<WxArticleBean>>) {
executeResp(stateLiveData, mService::getWxArticle)
}
}
interface ApiService {
@GET("wxarticle/chapters/json")
suspend fun getWxArticle(): BaseResponse<List<WxArticleBean>>
}
獲取一個Retrofit
實例,然后調(diào)用ApiService
接口方法留量。
封裝一的優(yōu)勢
代碼很簡潔窄赋,不需要手寫線程切換代碼,沒有很多的接口回調(diào)楼熄。
自帶Loading狀態(tài)忆绰,不需要手動啟用Loading和關(guān)閉Loading。
數(shù)據(jù)驅(qū)動ui可岂,以LiveData為載體错敢,將頁面狀態(tài)和網(wǎng)絡(luò)結(jié)果通過在LiveData返回給ui。
項目地址見:
github.com/ldlywt/Fast… (分支名字是:withLoading)
封裝一的不足
*封裝一的核心思想是:一個LiveData貫穿整個網(wǎng)絡(luò)請求鏈缕粹。這是它的優(yōu)勢稚茅,也是它的劣勢。
解耦不徹底平斩,違背了"在應(yīng)用的各個模塊之間設(shè)定明確定義的職責(zé)界限"的思想
LiveData
監(jiān)聽時亚享,如果需要Loading
,BaseActivity
都需要實現(xiàn)帶有Loading方法接口绘面。obserState()
方法第二個參數(shù)中傳入了UI引用欺税。不能達到"看方法如其意",如果是剛接觸揭璃,會有很多疑問:為什么需要一個livedata作為方法的參數(shù)魄衅。網(wǎng)絡(luò)請求的返回值去哪了?
封裝一還有一個最大的缺陷:對于是多數(shù)據(jù)源塘辅,封裝一就展示了很不友好的一面。
Repository是做一個數(shù)據(jù)倉庫皆撩,項目中獲取數(shù)據(jù)的方式都在這里同意管理扣墩,網(wǎng)絡(luò)獲取數(shù)據(jù)只是其中一個方式而已。
如果想加一個從數(shù)據(jù)庫或者緩存中獲取數(shù)據(jù)扛吞,封裝一想改都不好改呻惕,如果強制改就破壞了封裝,侵入性很大滥比。
針對封裝一的不足亚脆,優(yōu)化出了封裝二。
二盲泛、封裝二
思路
想要解決上面的不足濒持,不能以LiveData為載體貫穿整個網(wǎng)絡(luò)請求键耕。
Observe()
方法中去掉ui引用,不要小看一個ui引用柑营,這個引用代表著具體的Activity跟Observe耦合起來了屈雄,并且Activity還要實現(xiàn)IUiView接口。網(wǎng)絡(luò)請求跟Loading狀態(tài)分開了官套,需要手動控制Loading酒奶。
Repository
中的方法都有返回值,會返回結(jié)果奶赔,也不需要用livedata作為方法參數(shù)惋嚎。LiveData
只存在于ViewModel
中,LiveData
不會貫穿整個請求鏈站刑。Repository中也不需要LiveData的引用另伍,Repository
的代碼就是單純的獲取數(shù)據(jù)。針對多數(shù)據(jù)源笛钝,也非常好處理质况。
跟ui沒任何關(guān)系,可以完全作為一個獨立的Lib使用玻靡。
Activity中代碼
// 請求網(wǎng)絡(luò)
mViewModel.login("username", "password")
// 注冊監(jiān)聽
mViewModel.userLiveData.observeState(this) {
onSuccess {data ->
mBinding.tvContent.text = data.toString()
}
onComplete {
dismissLoading()
}
}
observeState()
中不再需要一個ui引用了结榄。
ViewModel
中
class MainViewModel {
val userLiveData = StateLiveData<User?>()
fun login(username: String, password: String) {
viewModelScope.launch {
userLiveData.value = repository.login(username, password)
}
}
}
通過livedata的setValue
或者postValue
方法將數(shù)據(jù)發(fā)送出去。
Repository中
suspend fun login(username: String, password: String): ApiResponse<User?> {
return executeHttp {
mService.login(username, password)
}
}
Repository
中的方法都返回請求結(jié)果囤捻,并且方法參數(shù)不需要livedata臼朗。Repository
完全可以獨立出來了。
針對多數(shù)據(jù)源
// WxArticleRepository
class WxArticleRepository : BaseRepository() {
private val mService by lazy {
RetrofitClient.service
}
suspend fun fetchWxArticleFromNet(): ApiResponse<List<WxArticleBean>> {
return executeHttp {
mService.getWxArticle()
}
}
suspend fun fetchWxArticleFromDb(): ApiResponse<List<WxArticleBean>> {
return getWxArticleFromDatabase()
}
}
// MainViewModel.kt
private val dbLiveData = StateLiveData<List<WxArticleBean>>()
private val apiLiveData = StateLiveData<List<WxArticleBean>>()
val mediatorLiveDataLiveData = MediatorLiveData<ApiResponse<List<WxArticleBean>>>().apply {
this.addSource(apiLiveData) {
this.value = it
}
this.addSource(dbLiveData) {
this.value = it
}
}
可以看到蝎土,封裝二更符合職責(zé)單一原則视哑,Repository
單純的獲取數(shù)據(jù),ViewModel
對數(shù)據(jù)進行處理和發(fā)送誊涯。
三挡毅、實現(xiàn)原理
數(shù)據(jù)來源于鴻洋大神的玩Android 開放API
回數(shù)據(jù)結(jié)構(gòu)定義:
{
"data": ...,
"errorCode": 0,
"errorMsg": ""
}
封裝一和封裝二的代碼差距很小,主要看封裝二暴构。
定義數(shù)據(jù)返回類
open class ApiResponse<T>(
open val data: T? = null,
open val errorCode: Int? = null,
open val errorMsg: String? = null,
open val error: Throwable? = null,
) : Serializable {
val isSuccess: Boolean
get() = errorCode == 0
}
data class ApiSuccessResponse<T>(val response: T) : ApiResponse<T>(data = response)
class ApiEmptyResponse<T> : ApiResponse<T>()
data class ApiFailedResponse<T>(override val errorCode: Int?, override val errorMsg: String?) : ApiResponse<T>(errorCode = errorCode, errorMsg = errorMsg)
data class ApiErrorResponse<T>(val throwable: Throwable) : ApiResponse<T>(error = throwable)
基于后臺返回的基類跪呈,根據(jù)不同的結(jié)果,定義不同的狀態(tài)數(shù)據(jù)類取逾。
網(wǎng)絡(luò)請求統(tǒng)一處理:BaseRepository
open class BaseRepository {
suspend fun <T> executeHttp(block: suspend () -> ApiResponse<T>): ApiResponse<T> {
runCatching {
block.invoke()
}.onSuccess { data: ApiResponse<T> ->
return handleHttpOk(data)
}.onFailure { e ->
return handleHttpError(e)
}
return ApiEmptyResponse()
}
/**
* 非后臺返回錯誤耗绿,捕獲到的異常
*/
private fun <T> handleHttpError(e: Throwable): ApiErrorResponse<T> {
if (BuildConfig.DEBUG) e.printStackTrace()
handlingExceptions(e)
return ApiErrorResponse(e)
}
/**
* 返回200,但是還要判斷isSuccess
*/
private fun <T> handleHttpOk(data: ApiResponse<T>): ApiResponse<T> {
return if (data.isSuccess) {
getHttpSuccessResponse(data)
} else {
handlingApiExceptions(data.errorCode, data.errorMsg)
ApiFailedResponse(data.errorCode, data.errorMsg)
}
}
/**
* 成功和數(shù)據(jù)為空的處理
*/
private fun <T> getHttpSuccessResponse(response: ApiResponse<T>): ApiResponse<T> {
return if (response.data == null || response.data is List<*> && (response.data as List<*>).isEmpty()) {
ApiEmptyResponse()
} else {
ApiSuccessResponse(response.data!!)
}
}
}
Retrofit協(xié)程的錯誤碼處理是通過異常拋出來的砾隅,所以通過try...catch來捕捉非200的錯誤碼误阻。包裝成不同的數(shù)據(jù)類對象返回。
擴展LiveData和Observer
在LiveData的Observer()
來判斷是哪種數(shù)據(jù)類,進行相應(yīng)的回調(diào)處理:
abstract class IStateObserver<T> : Observer<ApiResponse<T>> {
override fun onChanged(apiResponse: ApiResponse<T>) {
when (apiResponse) {
is ApiSuccessResponse -> onSuccess(apiResponse.response)
is ApiEmptyResponse -> onDataEmpty()
is ApiFailedResponse -> onFailed(apiResponse.errorCode, apiResponse.errorMsg)
is ApiErrorResponse -> onError(apiResponse.throwable)
}
onComplete()
}
再擴展LiveData
究反,通過kotlin的DSL表達式替換java的callback
回調(diào)寻定,簡寫代碼。
class StateLiveData<T> : MutableLiveData<ApiResponse<T>>() {
fun observeState(owner: LifecycleOwner, listenerBuilder: ListenerBuilder.() -> Unit) {
val listener = ListenerBuilder().also(listenerBuilder)
val value = object : IStateObserver<T>() {
override fun onSuccess(data: T) {
listener.mSuccessListenerAction?.invoke(data)
}
override fun onError(e: Throwable) {
listener.mErrorListenerAction?.invoke(e) ?: toast("Http Error")
}
override fun onDataEmpty() {
listener.mEmptyListenerAction?.invoke()
}
override fun onComplete() {
listener.mCompleteListenerAction?.invoke()
}
override fun onFailed(errorCode: Int?, errorMsg: String?) {
listener.mFailedListenerAction?.invoke(errorCode, errorMsg)
}
}
super.observe(owner, value)
}
}
四奴紧、總結(jié)
封裝一
:代碼量更少特姐,可以根據(jù)項目需要封裝一些具體的ui相關(guān),開發(fā)起來更快速黍氮,用起來更爽唐含。
封裝二
:解耦更徹底,可以獨立于ui模塊運行沫浆。
個人認為捷枯,框架設(shè)計主要還是服務(wù)于自己的項目需求(開源項目除外),符合設(shè)計模式和設(shè)計原則更好专执,但是不滿足也沒關(guān)系淮捆,適合自己項目需求,能節(jié)省自己的時間本股,就是好的攀痊。
我們自己項目中使用,怎么輕便拄显,怎么快速苟径,怎么寫的爽就怎么來。
本文轉(zhuǎn)自 https://juejin.cn/post/6993294489125126151躬审,如有侵權(quán)棘街,請聯(lián)系刪除。
文末
您的點贊收藏就是對我最大的鼓勵承边! 歡迎關(guān)注我遭殉,分享Android干貨,交流Android技術(shù)博助。 對文章有何見解险污,或者有何技術(shù)問題,歡迎在評論區(qū)一起留言討論富岳!
最后罗心,我整理了一些學(xué)習(xí)資料,里面包括Java基礎(chǔ)城瞎、Android進階、架構(gòu)設(shè)計疾瓮、NDK脖镀、音視頻開發(fā)、跨平臺、底層源碼等技術(shù)蜒灰,還有2022年一線大廠最新面試題集錦弦蹂,都分享給大家,助大家學(xué)習(xí)路上披荊斬棘~ 能力得到提升强窖,思維得到開闊~ 有需要的可以點擊下方公號鏈接免費獲取凸椿。
公眾號鏈接:https://mp.weixin.qq.com/s/ZDvWgZ_huo4natNk26ovlw