说到网络请求就要说到我们的网红库 Retrofit,好多项目中都是用RxJava+Retrofit来进行网络请求,自从开始使用协程,也放弃了使用RxJava,在Retrofit 2.6 之前。想用协程配合Retrofit来进行网络请求,我们的请求结果还要做一次转换,对此呢,我们Android界的大咖 JakeWharton还专门写了个库 retrofit2-kotlin-coroutines-adapter 来做转换,有兴趣的可以看一下。不过,Retrofit 2.6 之后,直接对kotlin 的协程做了支持,也不需要用到这个库了。我们来看一下实际代码,依然使用鸿大大的WanAndroid API来做例子。 比如我们要获取Banner图片 我们的 XXService:

/**

  • 玩安卓轮播图
    */
    @GET(“banner/json”)
    suspend fun getBanner(): BaseResult<List>

和之前我们写的有什么区别呢:

  1. 前面多了suspend关键字,带有这个关键字的函数,只有在协程中才能调用,在普通函数调用会报错的,编译也过不了
  2. 返回结果只直接写对应的Bean就好了,不需要固定类型来包装

下边的用法是在ViewModel中来使用的,如果想在Activity或者Fragment中使用,是一样的,只不过启动协程的时候写法有些不同。 下面在我们的VIewModel中:

private val repository by lazy {
RetrofitClient.getInstance().create(HomeService::class.java)
}

fun getBanner() {
viewModelScope.launch {
val result = repository.getBanner()
if (result.errorCode == 0) {
LogUtils.d(result.data)
}
}
}

这样一个简单的网络请求就完成,viewModelScope.launch {} 这个就是在ViewModel中启动一个协程,他会在ViewModel销毁的时候,自动取消他自己和在他内部启动的所有协程 相对于RxJava来说,我们每次都要关心生命周期防止内存泄露,是不是加方便些呢,这样我们不用关心内存泄露的问题了。所以我们要启动子协程,都要写在他内部,除非有特殊需求,比如页面销毁了,要做些其他工作。否则都尽量在他内部启动。
好了,我们再看上面的代码,会发现有个问题,**viewModelScope.launch {}**是直接启动在主线程的,所以协程也会运行在主线程中,那我们怎么能让网络请求去影响到UI呢,绝对不能忍。我们可以在启动一个子协程让他运行在IO线程上。修改如下:

viewModelScope.launch {
val result = withContext(Dispatchers.IO) { repository.getBanner() }
if (result.errorCode == 0) {
LogUtils.d(result.data)
}
}

这下就正常了,是不是相当方便,代码也清晰了很多,既然我们都要在 viewModelScope.launch {} 中启动协程 我们就把他再封装一下做一优化吧,顺便加上错误处理,我们在BaseViewModel中加入方法:

// 之后我们 全部在 launchUI 中启动协程
fun launchUI(block: suspend CoroutineScope.() -> Unit) {
viewModelScope.launch { block() }
}
//…
/**

  • 错误处理
    **/
    fun launch(
    block: suspend CoroutineScope.() -> Unit,
    error: suspend CoroutineScope.(Throwable) -> Unit = {},
    complete: suspend CoroutineScope.() -> Unit = {}
    ) {
    launchUI {
    try {
    block()
    } catch (e: Throwable) {
    error(e)
    } finally {
    complete()
    }
    }
    }

那我们的VIewModel中的getBanner方法这样写就好了:

fun getBanner() {
launch({
val result = repository.getBanner()
if (result.errorCode == 0) {
LogUtils.d(result.data)
}
})

// 如果要处理error,如下
/launch({
val result = repository.getBanner()
if (result.errorCode == 0) {LogUtils.d(result.data)}
}, {
//处理error
LogUtils.d(it.message)
})
/
}

又有小伙伴说了,那我想把code不等于0的时候全抛出错误,统一处理怎么办? 那我们就再封装一下,在BaseView中加入: 我们把统一异常处理先抽出来:

/**

  • 异常统一处理
    */
    private suspend fun handleException(
    block: suspend CoroutineScope.() -> BaseResult,
    success: suspend CoroutineScope.(BaseResult) -> Unit,
    error: suspend CoroutineScope.(ResponseThrowable) -> Unit,
    complete: suspend CoroutineScope.() -> Unit
    ) {
    coroutineScope {
    try {
    success(block())
    } catch (e: Throwable) {
    error(ExceptionHandle.handleException(e))
    } finally {
    complete()
    }
    }
    }

然后再写一个 executeResponse 方法来过滤:

/**

  • 请求结果过滤
    */
    private suspend fun executeResponse(
    response: BaseResult,
    success: suspend CoroutineScope.(T) -> Unit
    ) {
    coroutineScope {
    if (response.errorCode == 0 ) success(response.data)
    else throw ResponseThrowable(response.errorCode, response.errorMsg)
    }
    }

最后我们再写一个 launchOnlyresult 方法把他们结合起来:

fun launchOnlyresult(
block: suspend CoroutineScope.() -> BaseResult,
success: (T) -> Unit,
error: (ResponseThrowable) -> Unit = { },
complete: () -> Unit = {}
) {
launchUI {
handleException(
{ withContext(Dispatchers.IO) { block() } },
{ res ->
executeResponse(res) { success(it) }
},
{
error(it)
},
{
complete()
}
)
}
}

异常类的代码就不贴了,没什么好说的,末尾会给Demo地址,在里面看吧,现在我们获取Banner数据就变成这样了:

fun getBanner() {
launchOnlyresult({ repository.getBanner() }, {
LogUtils.d(it) // it是Banner 数据
})
// 处理Error
/launchOnlyresult({ repository.getBanner() }, {
mBanners.value = it
},{
LogUtils.d(it.errMsg)
})
/
}

我们的一个请求已经可以简单成这个样子了,相比于用RxJava的方式是不是更舒服呢。说到这里有的兄弟可能就说了,单个网络请求确实很简单,但是如果多个呢?还有些请求要依赖其他请求的结果呢?我们在业务逻辑越来越复杂,RxJava有多种操作符来使用,你这个要怎么搞?
接下来另一个人物要登场了,带着这些问题我们再来说下协程的另一个东西 Flow 异步流

3.2 Flow

带着上面的问题我们看下Flow 能干什么,看着名字可能有些陌生,但是我们了解之后肯定又会非常熟悉。他翻译成中文是 意思,我们在协程中,做异步可以返回一个值,当我们想返回多个值的时候,Flow就开始展现他的作用了,我们看下具体使用场景: 我们看玩安卓的 导航数据项目列表数据 两个接口,获取项目列表的时候需要依赖导航数据接口里边的 id,我们来用Flow实现 首先是Servie:

/**

  • 导航数据
    */
    @GET(“project/tree/json”)
    suspend fun naviJson(): BaseResult<List>

/**

  • 项目列表
  • @param page 页码,从0开始
    */
    @GET(“project/list/{page}/json”)
    suspend fun getProjectList(@Path(“page”) page: Int, @Query(“cid”) cid: Int): BaseResult

ViewModel中的实现:

@ExperimentalCoroutinesApi
@FlowPreview
fun getFirstData() {
launchUI {
flow { emit(repository.getNaviJson()) }
.flatMapConcat {
return@flatMapConcat if (it.isSuccess()) {
// 业务操作 …
// LogUtils.d(it) // it 是BaseResult<List>
// …
flow { emit(repository.getProjectList(page, it.data[0].id)) }
} else throw ResponseThrowable(it.errorCode, it.errorMsg)
}.onStart{
// 会在 emit 发射之前调用
}
.flowOn(Dispatchers.IO) // 这个是指烦气发射的所在协程
.onCompletion {
// 流执行完毕会调用
}
.catch {
// 遇到错误时会调用
}
.collect {
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

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

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

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

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

Android开发除了flutter还有什么是必须掌握的吗?

相信大多数从事Android开发的朋友们越来越发现,找工作越来越难了,面试的要求越来越高了

除了基础扎实的java知识,数据结构算法,设计模式还要求会底层源码,NDK技术,性能调优,还有会些小程序和跨平台,比如说flutter,以思维脑图的方式展示在下图;

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

越来越高了

除了基础扎实的java知识,数据结构算法,设计模式还要求会底层源码,NDK技术,性能调优,还有会些小程序和跨平台,比如说flutter,以思维脑图的方式展示在下图;

[外链图片转存中…(img-on9Xsnll-1712774708860)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐