利用Kotlin和协程,网络请求竟然还能这样写?

实现网络请求的特点


DSL方式的请求,自由处理各种start、response、error回调,或者交给BaseViewModel统一处理。

回调方式请求,自由处理各种start、response、error回调,或者交给BaseViewModel统一处理。

LiveData方式请求,请求直接返回LiveData。

DSL方式灵活配置OkHttpClient/Retrofit。

ShowCode


请求的声明如下:

interface TestService {

@GET(“/banner/json”)

suspend fun getBanner(): WanResponse<List>

}

可以看到加成了协程以后的retrofit在生命网络请求以后变得异常简单,不需要用Call或者Observable进行包装,直接返回想要的实体类就好。suspend是Kotlin的关键字,修饰方法是表示为挂起函数,只能运行在协程或者其他挂起函数中。

请求的OKHttp、Retrofit的配置示例如下

Request.init(context = this.applicationContext, baseUrl = “https://www.wanandroid.com”) {

okHttp {okhttpBuilder->

//配置okhttp

okhttpBuilder

}

retrofit {retrofitBuilder->

//配置retrofit

retrofitBuilder

}

}

如示例代码所示,通过DSL化的代码书写方式可以灵活的通过okHttp或者retrofit代码块来灵活配置okHttp和retrofit。当然用户可以直接选择不进行任何配置,基本的配置在Request.kt中已经配置完成,在使用默认配置的情况下完全可以不书写okHttp或者retrofit代码块。

需要说明的是初始化过程中传入了context,是由于在Request.kt中存在关于持久化cookie的配置,cookie持久化到SP中时需要context来创建SP。还有okHttp和retrofit看似是代码块其实是带函数类型参数的方法而已,正是利用了kotlin对高阶函数、扩展函数、lambda表达式的友好支持和invoke约定才能写出如上所示的DSL化的保证可读性的整洁灵活的代码。

关于DSL方式请求调用示例如下

class TestViewModel : BaseViewModel() {

private val service by lazy { Request.apiService(TestService::class.java) }

val liveData = MutableLiveData<WanResponse<List>>()

fun loadDSL() {

apiDSL<WanResponse<List>> {

onRequest {

service.getBanner()

}

onResponse {response->

Log.e(“Thread–>onResponse”, Thread.currentThread().name)

Log.e(“onResponse–>”, Gson().toJson(response))

liveData.value = response

}

onStart {

Log.e(“Thread–>onStart”, Thread.currentThread().name)

false

}

onError {

it.printStackTrace()

Log.e(“Thread–>onError”, Thread.currentThread().name)

true

}

}

}

}

如上可见,在onRequest中一股脑塞入请求就可以在onResponse中拿到请求结果。同时也可以在主线程的onStart中自由预处理一些逻辑,可以看到onStart代码块最后默认返回了false,false表示不拦截BaseViewModel中对网络请求开始时的处理(比如弹出统一样式的loading)。

如果返回true则表示该行为完全由自己处理。同理针对onError也是一样的道理,可以自己处理错误也可以交给base处理。当然也可以不写onStart和onError完全交给base来处理相关行为,使网络请求代码更简洁。

关于回调方式请求调用示例如下

fun loadCallback() {

apiCallback({

service.getBanner()

}, {

liveData.value = it//这里是onResponse的回调

}, {

true//这里是onStart的回调

}, onError ={ exception ->

false

})

}

借助函数类型(Any) -> Any来定义请求的不同回调,比如error的回调可以定义为((Exception) -> Boolean)?。接受exception来处理异常,返回bool类型来决定是否继续交给base来继续处理。同时定义成可空类型可以默认交给base出路。但是显而易见的是这种代码书写方式并不如DSL方式的请求美观和可读性高。

关于直接返回LiveData的请求调用示例如下

fun loadLiveData(): LiveData<Result<WanResponse<List>>> {

return apiLiveData(SupervisorJob() + Dispatchers.Main.immediate, timeoutInMs = 2000) {

service.getBanner()

}

}

在V层拿到LiveData后的操作如下:

viewModel.loadLiveData().observe(this, Observer {

when (it) {

is Result.Error -> {

hideLoading()

}

is Result.Response -> {

hideLoading()

it.response.apply {

showToast(Gson().toJson(this))

}

}

is Result.Start -> {

showLoading()

}

else ->{//冗余

}

}

})

显然这种方式的请求更适合轻量化的请求,适合拿到结果直接去渲染view不经过二次数据处理的场景。因为如上图所示在V层处理start、error回调感觉不是很友好,,在reponse中隐藏loading也是比较繁琐。但好处是V层直接可以拿到包含请求数据的LiveData,操作更加便捷。

关于Livedata的封装如下

protected fun apiLiveData(

context: CoroutineContext = EmptyCoroutineContext,

timeoutInMs: Long = 3000L,

request: suspend () -> Response

): LiveData<Result> {

return androidx.lifecycle.liveData(context, timeoutInMs) {

emit(Result.Start())

try {

emit(withContext(Dispatchers.IO) {

Result.Response(request())

})

} catch (e: Exception) {

e.printStackTrace()

emit(Result.Error(e))

} finally {

emit(Result.Finally())

}

}

}

此处的livedata是lifecycle-livedata-ktx,在配置了timeoutInMs后如果没有活跃的observers就会超时自动取消。在IO线程拿到请求的结果后包装成Result,像RxJava那样发射出来即可。为了保证返回的livedata中数据的一致性,start、error也被包装成了Result。

DSL封装示例


接下来我们以对okhttp和retrofit的请求配置来看下是怎么进行DSL封装的,不多说showcode。

class RequestDsl {

internal var buidOkHttp: ((OkHttpClient.Builder) -> OkHttpClient.Builder)? = null

internal var buidRetrofit: ((Retrofit.Builder) -> Retrofit.Builder)? = null

fun okHttp(builder: ((OkHttpClient.Builder) -> OkHttpClient.Builder)?) {

this.buidOkHttp = builder

}

fun retrofit(builder: ((Retrofit.Builder) -> Retrofit.Builder)?) {

this.buidRetrofit = builder

}

}

首先是DSL的配置类,主要有2个角色,一个是函数类型的buidOkHttp,一个是以buidOkHttp为参数的配置buidOkHttp的高阶函数okHttp。可见buidOkHttp变量是一个可空类型的输入和返回是非空的OkHttpClient.Builder类型的函数,既然是可空类型的我们在初始化调用时就可以选择配置OkHttpClient.Builder与否。

既然输入返回都是OkHttpClient.Builder我们就可以拿到既定的带有初始化配置的OkHttpClient.Builder进行进一部配置,只要最后返回OkHttpClient.Builder就好,同时OkHttpClient.Builder采用了建造者模式我们可以拿到builder引用之后进行二次配置最后原样返回builder的引用。

下面是初始化方法的具体实现

private fun initRequest(okHttpBuilder: OkHttpClient.Builder, requestDSL: (RequestDsl.() -> Unit)? = null) {

val dsl = if (requestDSL != null) RequestDsl().apply(requestDSL) else null

val finalOkHttpBuilder = dsl?.buidOkHttp?.invoke(okHttpBuilder) ?: okHttpBuilder

val retrofitBuilder = Retrofit.Builder()

.baseUrl(this.baseUrl)

.addConverterFactory(GsonConverterFactory.create())

.client(finalOkHttpBuilder.build())

val finalRetrofitBuilder = dsl?.buidRetrofit?.invoke(retrofitBuilder) ?: retrofitBuilder

this.retrofit = finalRetrofitBuilder.build()

}

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助

因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
57447717)]

[外链图片转存中…(img-Nz66bjJi-1715857447718)]

[外链图片转存中…(img-aiPWNRBY-1715857447719)]

[外链图片转存中…(img-VDPJfwEx-1715857447720)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值