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"// lifecycledef lifecycle_version = "2.3.1"implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"// retrofit2def 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'// okhttpdef 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 = 8Lprivate 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():申请胜利,但datanull或者data是汇合类型但为空
  • onFailure():申请失败
  • onFinish():申请完结(可在此敞开加载动画)

这里要留神onSuccess的规范:并不仅仅是 Http 申请的后果码(status code)等于 200,而且要达到Api申请胜利的规范,以玩安卓的Api 为例,errorCode 为 0时,发动的申请才是执行胜利;否则,都应该归为onFailure()的状况(能够参考文章附带的思维导图)。

理分明有几种回调状态后,就能够施行监听了。那么在哪里监听呢?LiveDataobserve()办法的第二个函数能够传入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) -> Unittypealias OnFailureCallback = (e: RequestException) -> Unittypealias OnUnitCallback = () -> Unitclass 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