android - Retrofit + Kotlin + MVVM 的网络请求框架的封装尝试

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的,它的架构图如下所示,如果你阅读过《第一行代码》或者谷歌的相关文档,那么想必对此不会陌生。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-07L5aUZi-1681961041572)(https://segmentfault.com/img/remote/1460000040921869 “MVVM架构图.png”)]

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():请求成功,但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) -> 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

后文

更多的android学习资料可以扫描下方二维码免费领取!

《Android编程入门指南》+Android基础课程资料包

Flutter是谷歌的移动UI框架,可以快速在iOS和Android上构建高质量的原生用户界面。 Flutter可以与现有的代码一起工作。在全世界,Flutter正在被越来越多的开发者和组织使用,并且Flutter是完全免费、开源的。

在这里为了方便大家系统的学习Flutter,这里特意联合了阿里P7架构师和谷歌技术团队共同整理了一份适合零基础的Android全家桶学习资料。

内容概要:Android编程入门指南、Android基础课程资料包;

内容特点:条理清晰,含图像化表示更加易懂;

完整文档领取方式👇

《Android编程入门指南》

目录

img

第一章 Java语言基础从入门到熟悉

​ ● Java综述

​ ● Java基础

​ ● class

​ ● 面向对象

​ ● 容器

​ ● 多线程

​ ● IO

​ ● 正则表达式

​ ● 日历

​ ● 注解使用与简单说明

​ ● Java使用加密算法

img

第二章 Kotlin语言基础从入门到熟悉

​ ● Kotlin系列简介

​ ● 新建项目

​ ● 概念介绍

​ ● 常用写法

​ ● lambda

​ ● 高阶函数

​ ● 扩展函数

​ ● 协程

img

第三章 Android 技术栈从入门到熟悉

​ ● 开始

​ ● Activity

​ ● Service 服务

​ ● Broadcast 广播机制

​ ● Fragment

​ ● res应用资源

​ ● 权限

​ ● TextView 显示文字

​ ● EditText 用户输入

​ ● Button 按钮

​ ● ImageView 图片显示

​ ● Layout布局

​ ● DrawerLayout侧滑栏

​ ● RecyclerView 显示列表

​ ● 多线程

​ ● IO 操作

​ ● 存储

​ ● NetWork 网络

​ ● ProgressBar进度条

​ ● View

​ ● 自定义View

​ ● 动画

​ ● WebView

​ ● StatusBar

​ ● Camera相机

​ ● Media

​ ● Bluetooth 蓝牙

​ ● WiFi连接

​ ● 获取手机当前角度

​ ● launcher

​ ● AIDL

​ ● zip

​ ● Binder

​ ● Notification 通知

​ ● PackageManager

​ ● RTFSC

​ ● 设计模式

​ ● 第三方库

​ ● Android Studio

​ ● 模块化

​ ● Gradle

​ ● NDK

​ ● 调试

img

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当然,我很愿意回答您的问题。下面是一个 Kotlin + 协程 + Retrofit + MVVM 实现网络请求的示例: 1. 在 build.gradle 中添加以下依赖: ``` implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0' implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0' implementation 'com.squareup.retrofit2:retrofit:2.8.1' implementation 'com.squareup.retrofit2:converter-gson:2.8.1' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9' ``` 2. 创建 Retrofit 接口: ```kotlin interface ApiService { @GET("users/{user}/repos") suspend fun getRepos(@Path("user") user: String): List<Repo> } ``` 3. 创建数据模型: ```kotlin data class Repo(val name: String) ``` 4. 创建 Repository: ```kotlin class MainRepository(private val apiService: ApiService) { suspend fun getRepos(user: String): List<Repo> { return apiService.getRepos(user) } } ``` 5. 创建 ViewModel: ```kotlin class MainViewModel(private val repository: MainRepository) : ViewModel() { private val _repos = MutableLiveData<List<Repo>>() val repos: LiveData<List<Repo>> = _repos fun getRepos(user: String) { viewModelScope.launch { _repos.value = repository.getRepos(user) } } } ``` 6. 创建 Activity/Fragment: ```kotlin class MainActivity : AppCompatActivity() { private lateinit var viewModel: MainViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val apiService = Retrofit.Builder() .baseUrl("https://api.github.com/") .addConverterFactory(GsonConverterFactory.create()) .build() .create(ApiService::class.java) val repository = MainRepository(apiService) viewModel = ViewModelProvider(this, MainViewModelFactory(repository))[MainViewModel::class.java] viewModel.repos.observe(this, Observer { repos -> // do something with repos }) viewModel.getRepos("octocat") } } ``` 以上就是一个使用 Kotlin + 协程 + Retrofit + MVVM 实现网络请求的示例。希望对您有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值