1、前言
之前在学习郭霖《第一行代码》时循序渐进地写过一个彩云天气 App,对外面的网络申请框架的封装印象十分粗浅,很喜爱这种 Retrofit
+ Kotlin
+ 协程的搭配应用。随后也在本人的我的项目里参考了这部分的代码。但随着代码的深刻编写和性能的简单,原来的框架曾经无奈满足我的应用了。原次要有如下的痛点:
- 短少失败的回调
- 显示加载中动画比拟麻烦
前面我本人试着致力去封装一个简略易用的框架,惋惜集体能力无限,本人封装的框架总是不如人意。好在还有很多优良的博客和代码可供参考。在此基础上,对彩云天气 App 中的网络申请框架做了一些批改,尽可能地做到简略易用。以申请玩安卓的登录接口为例(用户名和明码是我本人申请的,见代码),页面上有一个按钮,点击按钮后就发动登录申请。
先来看看发动申请后的回调怎么写:
viewModel.loginLiveData.observeState(this) {
onStart {LoadingDialog.show(activity)
Log.d(TAG, "申请开始")
}
onSuccess {Log.d(TAG, "申请胜利")
showToast("登录胜利")
binding.tvResult.text = it.toString()}
onEmpty {showToast("数据为空")
}
onFailure {Log.d(TAG, "申请失败")
showToast(it.errorMsg.orEmpty())
binding.tvResult.text = it.toString()}
onFinish {LoadingDialog.dismiss(activity)
Log.d(TAG, "申请完结")
}
}
回调一共有五种,会在下文具体介绍。这里采纳了 DSL
的写法,如果你喜爱传统的写法,能够调用另外一个扩大办法observeResponse()
,因为它最初一个参数就是申请胜利的回调,所以借助 Lambda 表达式的个性,能够简洁地写成如下的模式:
viewModel.loginLiveData.observeResponse(this){binding.tvResult.text = it.toString()
}
如果还须要其余回调,能够应用 具名参数 加上,如下所示:
viewModel.loginLiveData.observeResponse(this, onStart = {LoadingDialog.show(this)
}, onFinish = {LoadingDialog.dismiss(activity)
}) {binding.tvResult.text = it.toString()
}
2、框架搭建
开始之前必须阐明,这个框架是基于《第一行代码》(第三版)中的彩云天气 App 的,它的架构图如下所示,如果你浏览过《第一行代码》或者谷歌的相干文档,那么想必对此不会生疏。
2.1 增加依赖库
// 简化在 Activity 中申明 ViewModel 的代码
implementation "androidx.activity:activity-ktx:1.3.1"
// lifecycle
def lifecycle_version = "2.3.1"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
// retrofit2
def retrofit_version = "2.9.0"
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.squareup.retrofit2:adapter-rxjava2:$retrofit_version"
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
// okhttp
def okhttp_version = "4.8.1"
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
// 日志拦截器
implementation('com.github.ihsanbal:LoggingInterceptor:3.1.0') {exclude group: 'org.json', module: 'json'}
2.2 Retrofit
构建器
Retrofit
构建器这里做了分层,基类做了一些根本的配置,子类继承后能够增加新的配置,并配置本人喜爱的日志拦截器。
private const val TIME_OUT_LENGTH = 8L
private const val BASE_URL = "https://www.wanandroid.com/"
abstract class BaseRetrofitBuilder {
private val okHttpClient: OkHttpClient by lazy {val builder = OkHttpClient.Builder()
.callTimeout(TIME_OUT_LENGTH, TimeUnit.SECONDS)
.connectTimeout(TIME_OUT_LENGTH, TimeUnit.SECONDS)
.readTimeout(TIME_OUT_LENGTH, TimeUnit.SECONDS)
.writeTimeout(TIME_OUT_LENGTH, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
initLoggingInterceptor()?.also {builder.addInterceptor(it)
}
handleOkHttpClientBuilder(builder)
builder.build()}
private val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build()
fun <T> create(serviceClass: Class<T>): T = retrofit.create(serviceClass)
inline fun <reified T> create(): T = create(T::class.java)
/**
* 子类自定义 OKHttpClient 的配置
*/
abstract fun handleOkHttpClientBuilder(builder: OkHttpClient.Builder)
/**
* 配置日志拦截器
*/
abstract fun initLoggingInterceptor(): Interceptor?}
RetrofitBuilder
:
private const val LOG_TAG_HTTP_REQUEST = "okhttp_request"
private const val LOG_TAG_HTTP_RESULT = "okhttp_result"
object RetrofitBuilder : BaseRetrofitBuilder() {override fun handleOkHttpClientBuilder(builder: OkHttpClient.Builder) {}
override fun initLoggingInterceptor()= LoggingInterceptor
.Builder()
.setLevel(Level.BASIC)
.log(Platform.INFO)
.request(LOG_TAG_HTTP_REQUEST)
.response(LOG_TAG_HTTP_RESULT)
.build()}
2.3 全局异样解决
申请时可能会遇到诸如网络断开、Json
解析失败等意外状况,如果咱们每次申请都要解决一遍这些异样,那也未免太麻烦了。正确的做法是把异样集中到一起解决。
创立一个定义各种异样的枚举类:
enum class HttpError(val code: Int, val message: String){UNKNOWN(-100,"未知谬误"),
NETWORK_ERROR(1000, "网络连接超时,请查看网络"),
JSON_PARSE_ERROR(1001, "Json 解析失败")
//······
}
创立一个文件,在外面定义一个全局办法,用于解决各种异样:
fun handleException(throwable: Throwable) = when (throwable) {is UnknownHostException -> RequestException(HttpError.NETWORK_ERROR, throwable.message)
is HttpException -> {val errorModel = throwable.response()?.errorBody()?.string()?.run {Gson().fromJson(this, ErrorBodyModel::class.java)
} ?: ErrorBodyModel()
RequestException(errorMsg = errorModel.message, error = errorModel.error)
}
is JsonParseException -> RequestException(HttpError.JSON_PARSE_ERROR, throwable.message)
is RequestException -> throwable
else -> RequestException(HttpError.UNKNOWN, throwable.message)
}
理论我的项目中遇到的异样当然不止这几个,这里只是作为举例写了少部分,理论凋谢中把它丰盛欠缺即可。
2.4 回调状态监听
回调状态一共有四种:
onStart()
:申请开始(可在此展现加载动画)onSuccess()
:申请胜利onEmpty()
:申请胜利,但data
为null
或者data
是汇合类型但为空onFailure()
:申请失败onFinish()
:申请完结(可在此敞开加载动画)
这里要留神 onSuccess
的规范:并不仅仅是 Http 申请的后果码(status code)等于 200,而且要达到 Api 申请胜利的规范,以玩安卓的 Api 为例,errorCode
为 0 时,发动的申请才是执行胜利;否则,都应该归为onFailure()
的状况(能够参考文章附带的思维导图)。
理分明有几种回调状态后,就能够施行监听了。那么在哪里监听呢?LiveData
的 observe()
办法的第二个函数能够传入 Observer
参数。Observer
是一个接口,咱们继承它自定义一个 Oberver
,借此咱们就能够监听LiveData
的值的变动。
interface IStateObserver<T> : Observer<BaseResponse<T>> {override fun onChanged(response: BaseResponse<T>?) {when (response) {
is StartResponse -> {//onStart()回调后不能间接就调用 onFinish(),必须期待申请完结
onStart()
return
}
is SuccessResponse -> onSuccess(response.data)
is EmptyResponse -> onEmpty()
is FailureResponse -> onFailure(response.exception)
}
onFinish()}
/**
* 申请开始
*/
fun onStart()
/**
* 申请胜利,且 data 不为 null
*/
fun onSuccess(data: T)
/**
* 申请胜利,但 data 为 null 或者 data 是汇合类型但为空
*/
fun onEmpty()
/**
* 申请失败
*/
fun onFailure(e: RequestException)
/**
* 申请完结
*/
fun onFinish()}
接下来咱们筹备一个 HttpRequestCallback
类,用于实现 DSL
的回调模式:
typealias OnSuccessCallback<T> = (data: T) -> Unit
typealias OnFailureCallback = (e: RequestException) -> Unit
typealias OnUnitCallback = () -> Unit
class HttpRequestCallback<T> {
var startCallback: OnUnitCallback? = null
var successCallback: OnSuccessCallback<T>? = null
var emptyCallback: OnUnitCallback? = null
var failureCallback: OnFailureCallback? = null
var finishCallback: OnUnitCallback? = null
fun onStart(block: OnUnitCallback) {startCallback = block}
fun onSuccess(block: OnSuccessCallback<T>) {successCallback = block}
fun onEmpty(block: OnUnitCallback) {emptyCallback = block}
fun onFailure(block: OnFailureCallback) {failureCallback = block}
fun onFinish(block: OnUnitCallback) {finishCallback = block}
}
而后申明新的监听办法,思考到某些时候须要自定义的LiveData
(比方为了解决数据倒灌的问题),这里采纳扩大函数的写法,便于扩大。
/**
* 监听 LiveData 的值的变动,回调为 DSL 的模式
*/
inline fun <T> LiveData<BaseResponse<T>>.observeState(
owner: LifecycleOwner,
crossinline callback: HttpRequestCallback<T>.() -> Unit) {val requestCallback = HttpRequestCallback<T>().apply(callback)
observe(owner, object : IStateObserver<T> {override fun onStart() {requestCallback.startCallback?.invoke()
}
override fun onSuccess(data: T) {requestCallback.successCallback?.invoke(data)
}
override fun onEmpty() {requestCallback.emptyCallback?.invoke()
}
override fun onFailure(e: RequestException) {requestCallback.failureCallback?.invoke(e)
}
override fun onFinish() {requestCallback.finishCallback?.invoke()
}
})
}
/**
* 监听 LiveData 的值的变动
*/
inline fun <T> LiveData<BaseResponse<T>>.observeResponse(
owner: LifecycleOwner,
crossinline onStart: OnUnitCallback = {},
crossinline onEmpty: OnUnitCallback = {},
crossinline onFailure: OnFailureCallback = {e: RequestException ->},
crossinline onFinish: OnUnitCallback = {},
crossinline onSuccess: OnSuccessCallback<T>
) {
observe(owner, object : IStateObserver<T> {override fun onStart() {onStart()
}
override fun onSuccess(data: T) {onSuccess(data)
}
override fun onEmpty() {onEmpty()
}
override fun onFailure(e: RequestException) {onFailure(e)
}
override fun onFinish() {onFinish()
}
})
}
2.5 Repository
层的封装
Repository
层作为数据的起源,有个两个渠道:网络申请和数据库。这里临时只解决了网络申请。
基类Repository
:
abstract class BaseRepository {
protected fun <T> fire(
context: CoroutineContext = Dispatchers.IO,
block: suspend () -> BaseResponse<T>): LiveData<BaseResponse<T>> = liveData(context) {
this.runCatching {emit(StartResponse())
block()}.onSuccess {
//status code 为 200,持续判断 errorCode 是否为 0
emit(when (it.success) {true -> checkEmptyResponse(it.data)
false -> FailureResponse(handleException(RequestException(it)))
}
)
}.onFailure { throwable ->
emit(FailureResponse(handleException(throwable)))
}
}
/**
* data 为 null,或者 data 是汇合类型,然而汇合为空都会进入 onEmpty 回调
*/
private fun <T> checkEmptyResponse(data: T?): ApiResponse<T> =
if (data == null || (data is List<*> && (data as List<*>).isEmpty())) {EmptyResponse()
} else {SuccessResponse(data)
}
}
子类Repository
:
object Repository : BaseRepository() {fun login(pwd: String) = fire {NetworkDataSource.login(pwd)
}
}
网络申请数据源,在这里调用网络接口:
object NetworkDataSource {private val apiService = RetrofitBuilder.create<ApiService>()
suspend fun login(pwd: String) = apiService.login(password = pwd)
}
2.6 ViewModel
层的封装
ViewModel
根本遵循了《第一行代码》中的写法,创立了两个 LiveData
。用户点击按钮时,loginAction
的值就会产生扭转,触发 switchMap
中的代码,从而达到申请数据的目标。
class MainViewModel : ViewModel() {private val loginAction = MutableLiveData<Boolean>()
/**
* loginAction 在这里只传递布尔值,不传递明码,在理论我的项目中,会应用 DataBinding 绑定 xml 布局和 ViewModel,* 不须要从 Activity 或者 Fragment 中把明码传入 ViewModel
*/
val loginLiveData = loginAction.switchMap {if (it) {Repository.login("PuKxVxvMzBp2EJM")
} else {Repository.login("123456")
}
}
/**
* 点击登录
*/
fun login() {loginAction.value = true}
fun loginWithWrongPwd() {loginAction.value = false}
}
留神:这种写法通常不从
View
向 ViewModel 层传递数据,是须要搭配DataBinding
的。如果你不想这样写,能够批改BaseRepository
中的返回值,间接返回BaseResponse
。
3、思维导图及源码
最初,用一张思维导图总结本文:
源码地址:GitHub(留神分支要抉择 dev1.0)
参考
- JetpackMvvm
- FastJetpack