在新版的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
是怎么来的?
lifecycleScope
是androidx.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>()
最后
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
…(img-s70R747G-1715802305405)]
[外链图片转存中…(img-5neqv2du-1715802305406)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!