Android开发之MVVM模式实践(六):协程与网络请求的结合

二、与Retrofit的结合

目前在Android开发中,最主流的网络请求框架应该就是Retrofit+OkHttp+RxJava这一套了。那么下面我们就使用Retrofit来结合协程进行封装。在网络请求中,协程起的作用其实与RxJava是一致的,所以如果在别处没有使用RxJava的需求,此处可以不引入RxJava,只需引入Retrofit+OkHttp

Interface

interface FlyInterface {
/**

  • 获取文章列表
    */
    @GET(“article/”)
    suspend fun get_article_list(@Query(“page_size”) size: Int): ApiResponse<CommonListDto
    >
    }

interface的改造非常简单,仅仅是在函数前加上suspend修饰。

ApiResponse

abstract class HttpResponse(val code: Int, val msg: String, val data: T?) {
abstract fun isSuccess(): Boolean
}

class ApiResponse(code: Int, msg: String, data: T?) : HttpResponse(code, msg, data) {

override fun isSuccess(): Boolean {
return code == 0
}
}

ApiResponse是上述interface中函数的返回值,实现也非常简单。因为接口返回的数据格式一般都是统一的,例如:

{
“code”: 0;
“message”: “Success”;
“data”: {

}
}

所以,我们也需要将返回的数据格式用一个统一的数据模型来处理。

HttpError

我们可以事先定义一些事先常见的网络错误,方便后续使用。

enum class HttpError(val code: Int, @StringRes val message: Int) {
// 未知错误
UNKNOWN(-1, R.string.fly_http_error_unknow),

// 网络连接错误
CONNECT_ERROR(-2, R.string.fly_http_error_connect),

// 连接超时
CONNECT_TIMEOUT(-3, R.string.fly_http_error_connect_timeout),

// 错误的请求
BAD_NETWORK(-4, R.string.fly_http_error_bad_network),

// 数据解析错误
PARSE_ERROR(-5, R.string.fly_http_error_parse),

// 取消请求
CANCEL_REQUEST(-6, R.string.fly_http_cancel_request),
}

Retrofit

相信大部分同学在使用Retrofit时都会自己做二次封装的,此处就不附上详细的代码了,主要看关键代码,需要完整代码的可以去小益的Github上自行查看。

class BaseHttpClient {

/**

  • 获取service对象
  • @param service api所在的interface
    */
    fun getService(service: Class): T {
    var retrofitService: T? = serviceCache.get(service.canonicalName) as T
    if (retrofitService == null) {
    retrofitService = retrofitClient.create(service)
    serviceCache.put(service.canonicalName, retrofitService)
    }
    return retrofitService!!
    }

/**

  • 建议调用此方法发送网络请求

  • 因为协程中出现异常时,会直接抛出异常,所以使用try…catch方法捕获异常
    */
    suspend fun <T : Any, D : Any> requestSafely(
    apiInterface: Class,
    call: suspend (service: T) -> HttpResponse
    ): ParseResult {
    try {
    val s = getService(apiInterface)
    val response = call(s)
    return if (response.isSuccess()) {
    ParseResult.Success(response.data)
    } else {
    ParseResult.Failure(response.code, response.msg)
    }
    } catch (ex: Throwable) {
    return ParseResult.ERROR(ex, parseException(ex))
    }
    }

    }

  • getService:获取我们定义的interface

  • requestSafely:此方法中最值得注意的是try...catch,因为使用协程来进行网络请求时,如遇到问题会抛出异常,所以此处使用try...catch捕获。另外,此方法也对返回的Response做了简单的解析处理,并返回具体的ParseResult

ParseResult

sealed class ParseResult {
/* 请求成功,返回成功响应 */
data class Success(val data: T?) : ParseResult()

/* 请求成功,返回失败响应 */
data class Failure(val code: Int, var msg: String? = null) :
ParseResult()

/* 请求失败,抛出异常 */
data class ERROR(val ex: Throwable, val error: HttpError) : ParseResult()

private var successBlock: (suspend (data: T?) -> Unit)? = null
private var failureBlock: (suspend (code: Int, msg: String?) -> Unit)? = null
private var errorBlock: (suspend (ex: Throwable, error: HttpError) -> Unit)? = null
private var cancelBlock: (suspend () -> Unit)? = null

/**

  • 设置网络请求成功处理
    */
    fun doSuccess(successBlock: (suspend (data: T?) -> Unit)?): ParseResult {
    this.successBlock = successBlock
    return this
    }

/**

  • 设置网络请求失败处理
    */
    fun doFailure(failureBlock: (suspend (code: Int, msg: String?) -> Unit)?): ParseResult {
    this.failureBlock = failureBlock
    return this
    }

/**

  • 设置网络请求异常处理
    */
    fun doError(errorBlock: (suspend (ex: Throwable, error: HttpError) -> Unit)?): ParseResult {
    this.errorBlock = errorBlock
    return this
    }

/**

  • 设置网络请求取消处理
    */
    fun doCancel(cancelBlock: (suspend () -> Unit)?): ParseResult {
    this.cancelBlock = cancelBlock
    return this
    }

suspend fun procceed() {
when (this) {
is Success -> successBlock?.invoke(data)
is Failure -> failureBlock?.invoke(code, msg)
is ERROR -> {
if (this.error == HttpError.CANCEL_REQUEST) {
cancelBlock?.invoke()
} else {
errorBlock?.invoke(ex, error)
}
}
}
}
}

ParseResult是对HttpResponse解析后返回的类。ParseResult解析HttpResponse后出现三种返回:

  • Success:继承于ParseResult,网络请求成功并且返回的的Response状态也是成功,持有具体的Response数据
  • Failure:继承于ParseResult,网络请求成功但是返回的Response状态是失败,持有失败的Code码与Message
  • Error:继承于ParseResult,网络请求异常,未成功,持有异常信息

ParseResultdo开头的函数都是设置对应处理的代码块,另外有个procceed函数是真正执行响应处理。其中在对Error处理时分为了两种情况:

  • 一种是因为网络请求被取消产生的异常(经测试,网络请求取消会抛出取消异常)
  • 另一种是非网络请求取消产生的异常

因为网络请求取消从一定程度上来说不应该当作错误处理,所以要分开处理;防止项目中对异常错误进行了集中处理,比如弹出toast提示,此时如果用户取消了网络请求,也弹出一个网络请求取消的提示,这样的用户体验就比较糟糕了。

具体使用

fun get_article_list() {
launchOnUI {
ApiClient.getInstance()
.requestSafely(FlyInterface::class.java) {
it.get_article_list(20)
}.doSuccess {
articleList.value = it!!.results
}
.doFailure { code, msg -> showToast(msg ?: “获取文章列表失败”) }
.doError { ex, error -> showToast(error.message) }
.procceed()
}
}

此处的ApiClientBaseHttpClient的子类即对Retrofit+OkHttp的封装,并做了单例处理,整个请求流程呈现链式结构。虽然doSuccessdoFailure以及doError看上去有些像回调,但其实都是同步的。我们完全可以这么写:

fun get_article_info() {
launchOnUI {
println(“>>>>>> 开始”)
var articles = ArrayList

()
ApiClient.getInstance()
.requestSafely(FlyInterface::class.java) {
it.get_article_list(20)
}.doSuccess {
println(“>>>>>> 第一次”)
articles = it!!.results
}
.procceed()
ApiClient.getInstance()
.requestSafely(FlyInterface::class.java) {
it.get_article(articles[0].id)
}.doSuccess {
println(“>>>>>> 第二次”)
}
.procceed()
println(“>>>>>> 结束”)
}
}

先获取文章列表,再从文章列表中提取列表头部的文章ID用于获取文章详情,最后打印的结果为:

开始
第一次
第二次
结束

可以看出,完全是顺序执行。

请求并发

fun get_info() {
launchOnUI {
val listAsync = async {
var articles = ArrayList

()
ApiClient.getInstance()
.requestSafely(FlyInterface::class.java) {
it.get_article_list(20)
}.doSuccess {
articles = it!!.results
}
.procceed()
return@async articles
}
val detailAsync = async {
var article: Article? = null
ApiClient.getInstance()
.requestSafely(FlyInterface::class.java) {
it.get_article(2)
}.doSuccess {
article = it
}
.procceed()
return@async article!!
}
val articles = listAsync.await()
val articleDetail = detailAsync.await()
}

学习福利

【Android 详细知识点思维脑图(技能树)】

其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

虽然 Android 没有前几年火热了,已经过去了会四大组件就能找到高薪职位的时代了。这只能说明 Android 中级以下的岗位饱和了,现在高级工程师还是比较缺少的,很多高级职位给的薪资真的特别高(钱多也不一定能找到合适的),所以努力让自己成为高级工程师才是最重要的。

这里附上上述的面试题相关的几十套字节跳动,京东,小米,腾讯、头条、阿里、美团等公司19年的面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。

由于篇幅有限,这里以图片的形式给大家展示一小部分。

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!
试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。

由于篇幅有限,这里以图片的形式给大家展示一小部分。

[外链图片转存中…(img-OKPS8Myh-1714511783793)]

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

  • 13
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值