【思货】AndroidX+协程+Retrofit-我的新思考,请走开,所有的Rx请求库!

在新版的Retrofit中,是支持直接使用的,这里我就简单介绍下,一笔带过,不做过多介绍,网上有更多详细教程。

Retrofit 的构建

fun getRetrofit(): Retrofit { // 正常的构建 Retrofit ,没有区别 val builder = OkHttpClient.Builder() return Retrofit.Builder() .baseUrl("https://api.apiopen.top") .addConverterFactory(MoshiConverterFactory.create()) // json转换器 .client(builder.build()) .build() }

Api

`interface NewsApi {
/**

  • 接口需要加上 [suspend] !
  • 返回值,直接就是你的数据类型,不需要再包装其他的东西了
    */
    @GET(“/getWangYiNews”)
    suspend fun getNews(): NewsBean
    }`

Activity 中简单的使用

class MainActivity : AppCompatActivity() { private lateinit var viewBinding: ActivityMainBinding private val mAdapter = MainRvAdapter() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) viewBinding = ActivityMainBinding.inflate(layoutInflater) setContentView(viewBinding.root) viewBinding.recyclerView.adapter = mAdapter // 网络请求开始 // 使用 activity 的 Scope 创建协程,可以感知生命周期,自动销毁 lifecycleScope.launch { try { // 正常的创建api,没有任何区别 val newsApi = getRetrofit().create(NewsApi::class.java) // 这里直接调用api里的方法就好了,不需要自己进行任何异步操作 val newsBean: NewsBean = newsApi.getNews() // 直接刷新界面,因为这里是UI线程的协程 mAdapter.setList(newsBean.list) // 以上就是获取数据的方法,三行关键代码。Retrofit 也不再需要设置 Rx 等等那些转化器了。 } catch (e: Throwable) { // 这里处理网络错误 if (e is HttpException) { // http 状态码 when (e.code()) { 400 -> { } 500 -> { } } } else if (e is SocketTimeoutException) { // 连接超时 } else if (e is SocketException || this is UnknownHostException || this is SSLException ) { // 各种其他网络错误... } } } } }

好了,基础使用就是如此,代码注释中也写了基本意思了。看完以后是不是头皮发麻,需要缓缓?

有小伙伴要问了,代码中的lifecycleScope是怎么来的?

lifecycleScopeandroidx.lifecycle:lifecycle-runtime-ktx:2.2.0扩展包里面的,官方已经实现了一个生命周期感知的协程作用域,可以直接开启协程,并在页面关闭的时候自动销毁。

lifecycleScope扩展

lifecycleScope,你还可以这么用:

// 直接开启一个协程,最常见的 lifecycleScope.launch { } // 在生命周期走到 Create 以后,开启此协程内容 lifecycleScope.launchWhenCreated { } // 在生命周期走到 Start 以后,开启此协程内容 lifecycleScope.launchWhenStarted { } // 在生命周期走到 Resume 以后,开启此协程内容 lifecycleScope.launchWhenResumed { }

有同学肯定要 What f**k?这都什么玩意,没见过啊。没想到吧,嘿嘿,Google 如此重视协程。(有兴趣的去看看源码,此处不展开了)

好了不扯远了,继续我们的网络请求。

上面那种基本用法,虽然非常简单、清晰、易读,但在项目里肯定不优雅,直接用是不行的。

利用 DSL 封装

大致内容与我之前写的思货-kotlin 协程优雅的与 Retrofit 缠绵-正文类似,不在嚼舌根子了。(代码写在 demo 中,可以自行查看)

直接使用方式如下:

lifecycleScope.retrofit<NewsBean> { api = api.getNews() onComplete { } onSuccess { bean -> } onFailed { error, code -> } }

如果你不需要什么设计模式,什么 MVVM,就想在 Activity 直接请求网络,那么使用 DSL 的封装可以解决大部分的网络请求方式了

本文重点:配合 LiveData 封装

需要增加导入的包

// LiveData的包 implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0" // activity的扩展 implementation "androidx.activity:activity-ktx:1.1.0"

MVVM的设计模式中(我们这里只讨论ViewModel这一层),如果在ViewModel中直接使用 DSL 封装的方式,然后再转换为 LiveData 的值可以么,当然可以。不过我觉得这很多此一举。

在 Google 官方的示例项目Github Browser中,ViewModel拿到的只是网络结果的LiveData对象,并且官方使用的协程是与LiveDataCallAdapterFactory转换器配合的,在Retrofit未支持协程的情况下,这是比较好的一个解决方案。但是到了现在,恕我直言,这是一个过时的写法,极其不优雅,陈平大佬敢于质疑古典经济学理论,我们为啥不可以质疑 Google 的 demo,就因为他是外国人写的,就因为他是 Google 写的?

ViewModel 到底需要什么?

抛开 Google 的写法,看本质,看本质,看本质,Google 的 demo 中,就是告诉了我们一个思想,ViewModel需要的是一个LiveData封装的网络数据,就这么简单,

那么在没有LiveDataCallAdapterFactory的情况下,我们怎么封装

- 最容易想到的方式

初期,我有仿写过 Google 的 demo 不下 4 次,每一次仿写都有新的进步,最后发现其实都是一种写法:把网络请求的结果,放到 LiveData 中。

简化的代码模型如下:

fun CoroutineScope.getHttpLiveData(): LiveData<NewsBean> { // 先定义LiveData val requestLive = MutableLiveData<NewsBean>() this.launch(Dispatchers.Main) { // 创建api val newsApi = getRetrofit().create(NewsApi::class.java) // 调用api的方法 val newsBean: NewsBean = newsApi.getNews() // 把值给LiveData requestLive.value = newsBean } return requestLive }

好了,这种方式的写法,看着好像没什么问题啊。确实,代码逻辑上来说确实可行没毛病,但是这,这,这就是差点内味,感觉这个 LiveData 是拼凑出来的,我想要是更存粹,更原汁原味的方式。

- liveData 方式(终于到主题了)

注意,我说的是liveData,不是LiveData,开头是小写,不是大写。

完了,有的同学又头晕了,“这 TM 什么鬼啊???你 TM 能说人话么?”

且听我慢慢到来。

大写开头的LiveData,是一个抽象类,是所有 LiveData 的基本类,是一个实实在在的类。

而小写的liveData,是AndroidX包中提供一个方法,注意,这是一个方法,会生成一个CoroutineLiveData,这是自带协程的 LiveData,我当时看到这个都震惊了。

本菜鸡也是在不经意间,发现的系统提供的方法,看了相关源码以后,发现,这简直就是解决问题的完美模式。

下面开始起飞,简化的模型如下:

fun CoroutineScope.getHttpLiveData(): LiveData<NewsBean> { // 使用协程的 coroutineContext 去生成一个LiveData return liveData(this.coroutineContext) { // 创建api val newsApi = getRetrofit().create(NewsApi::class.java) // 调用api的方法 val newsBean: NewsBean = newsApi.getNews() // 提交数据 emit(newsBean) } }

核心思想就上面几行代码,好了,完事了,我们接下来可以继续抽象 Api,封装网络请求了。

“停车停车!等等,我 TM 好懵,这是什么操作,方向盘我焊死了,你不说清楚一个都别想走”。

别急别急,为了不打断思绪,liveData方法的讲解我放到后面再说,我们先继续封装。

初步封装成型

开始之前,我们要先准备好东西

定义一个 RequestStatus,用于区分网络请求状态

enum class RequestStatus { START, SUCCESS, COMPLETE, ERROR }

四个状态,对应着网络请求的开始、成功、完成、失败

定义 ResultData,用于封装网络数据

data class ResultData<T>(val requestStatus: RequestStatus, val data: T? val error: Throwable? = null) { companion object { fun <T> start(): ResultData<T> { return ResultData(RequestStatus.START, null, null) } fun <T> success(data: T?, isCache: Boolean = false): ResultData<T> { return ResultData(RequestStatus.SUCCESS, data, null) } fun <T> complete(data: T?): ResultData<T> { return ResultData(RequestStatus.COMPLETE, data, null) } fun <T> error(error: Throwable?): ResultData<T> { return ResultData(RequestStatus.ERROR, null, error) } }

定义一个 ApiResponse,用于区分哪种网络状态返回的数据

internal sealed class ApiResponse<T> { companion object { fun <T> create(error: Throwable): ApiErrorResponse<T> { return ApiErrorResponse(error) } fun <T> create(body: T?): ApiResponse<T> { return if (body == null) { ApiEmptyResponse() } else { ApiSuccessResponse(body) } } } } internal class ApiEmptyResponse<T> : ApiResponse<T>() internal data class ApiSuccessResponse<T>(val body: T) : ApiResponse<T>() internal data class ApiErrorResponse<T>(val throwable: Throwable) : ApiResponse<T>()

接着定义一个RequestAction类,用于 DSL,包装需要操作的方法。

open class RequestAction<ResponseType> { var api: (suspend () -> ResponseType)? = null fun api(block: suspend () -> ResponseType) { this.api = block } }

api 我们肯定是要动态传递进去的,不能直接写死对吧。那么我们祭出 DSL 大法。

为了讲解简便,暂且我们先只定义一个 api 相关的方法,其他操作后续在加上。

万事俱备,开始整!

`/**

  • DSL网络请求
    */
    inline fun CoroutineScope.requestLiveData(
    dsl: RequestAction.() -> Unit
    ): LiveData<ResultData> {
    val action = RequestAction2().apply(dsl)
    return liveData(this.coroutineContext) {
    // 通知网络请求开始
    emit(ResultData.start())
    val apiResponse = try {
    // 获取网络请求数据
    val resultBean = action.api?.invoke()
    ApiResponse.create(resultBean)
    } catch (e: Throwable) {
    ApiResponse.create(e)
    }
    // 根据 ApiResponse 类型,处理对于事物
    val result = when (apiResponse) {
    is ApiEmptyResponse -> {
    null
    }
    is ApiSuccessResponse -> {
    apiResponse.body.apply {
    // 提交成功的数据给LiveData
    emit(ResultData.success(this))
    }
    }
    is ApiErrorResponse -> {
    // 提交错误的数据给LiveData
    emit(ResultData.error(apiResponse.throwable))
    null
    }
    }
    // 提交成功的信息
    emit(ResultData.complete(result))
    }
    }`

可以发现,所有的逻辑,都是顺序执行!简单清晰易懂,不需要处理线程转换。通过emit就可以把数据提交给 LiveData

ViewModel 里的使用

其实和 Google 的 demo 里差不多,没太大本质变化

`class MainViewModel : ViewModel() {
private val newsApi = getRetrofit().create(NewsApi::class.java)
private val _newsLiveData = MediatorLiveData<ResultData>()

最后

现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水!

为什么某些人会一直比你优秀,是因为他本身就很优秀还一直在持续努力变得更优秀,而你是不是还在满足于现状内心在窃喜!

Android架构师之路很漫长,一起共勉吧!
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

  • 19
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值