1. Callback
在最初的学习使用中,Callback
异步方法是Retrofit
最基本的使用方式,如下:
接口:
interface DemoService {
@POST("oauth/login")
@FormUrlEncoded
fun login(@Field("name") name: String, @Field("pwd") pwd: String): Call<String>
}
使用:
val retrofit = Retrofit.Builder()
.baseUrl("https://baidu.com")
.client(okHttpClient.build())
.build()
val api = retrofit.create(DemoService::class.java)
val loginService = api.login("1", "1")
loginService.enqueue(object : Callback<String> {
override fun onFailure(call: Call<String>, t: Throwable) {
}
override fun onResponse(call: Call<String>, response: Response<String>) {
}
})
这里不再细说。
在关闭网络请求的时候,需要在onDestroy
中调用cancel
方法:
override fun onDestroy() {
super.onDestroy()
loginService.cancel()
}
这种方式,容易导致忘记调用cancel
方法,而且网络操作和关闭请求的操作是分开的,不利于管理。
这当然不是优雅的方法。随着Rx的火爆,我们项目的网络请求方式,也逐渐转为了Rx的方式
2. RxJava
此种使用方式,百度一下,到处都是教程讲解,可见此种方式起码是大家较为认可的一种方案。
在Rx的使用中,我们也尝试了各种各样的封装方式,例如自定义Subscriber
,将onNext
、onCompleted、
onError进行拆分组合,满足不同的需求。
首先在Retrofit
里添加Rx转换器RxJava2CallAdapterFactory.create()
:
addCallAdapterFactory(RxJava2CallAdapterFactory.create())
RxJava的使用方式大体如下,先将接口的Call
改为Observable
:
interface DemoService {
@POST("oauth/login")
@FormUrlEncoded
fun login(@Field("name") name: String, @Field("pwd") pwd: String): Observable<String>
}
使用:(配合RxAndroid绑定声明周期)
api.login("1","1")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) //RxAndroid
.subscribe(object :Observer<String> {
override fun onSubscribe(d: Disposable) {
}
override fun onComplete() {
}
override fun onNext(t: String) {
}
override fun onError(e: Throwable) {
}
})
这种使用方式确实方便了不少,响应式编程的思想也很优秀,一切皆为事件流。通过RxAndroid
来切换UI线程和绑定页面生命周期,在页面关闭的时候,自动切断向下传递的事件流。
RxJava
最大的风险即在于内存泄露,而RxAndroid
确实规避了一定的泄露风险。 并且通过查看RxJava2CallAdapterFactory
的源码,发现也确实调用了cancel
方法,嗯……貌似不错呢。 但总是觉得RxJava过于庞大,有些大材小用。
3. LiveData
随着项目的的推进和Google全家桶的发布。一个轻量化版本的RxJava
进入到了我们视线,那就是LiveData
,LiveData
借鉴了很多RxJava
的的设计思想,也是属于响应式编程的范畴。LiveData
的最大优势即在于响应Acitivty
的生命周期,不用像RxJava
再去绑定声明周期。
同样的,我们首先需要添加LiveDataCallAdapterFactory (链接里是google官方提供的写法,可直接拷贝到项目中),用于把retrofit的Callback
转换为LiveData
:
addCallAdapterFactory(LiveDataCallAdapterFactory.create())
接口改为:
interface DemoService {
@POST("oauth/login")
@FormUrlEncoded
fun login(@Field("name") name: String, @Field("pwd") pwd: String): LiveData<String>
}
调用:
api.login("1", "1").observe(this, Observer {string ->
})
以上就是最基础的使用方式,在项目中使用时候,通常会自定义Observer
,用来将各种数据进行区分。
在上面调用的observe
方法中,我们传递了一个this
,这个this
指的是声明周期,一般我们在AppCompatActivity
中使用时,直接传递其本身就可以了。
下面简单跳转源码进行说明下。通过查看源码可以发现:
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer)
其this
本身是传递的LifecycleOwner
。
那么我们在一层层跳转AppCompatActivity
,会发现AppCompatActivity
是继承于SupportActivity
的父类:
public class SupportActivity extends Activity implements LifecycleOwner, Component
其本身对LifecycleOwner
接口进行了实现。也就是说,除非特殊要求,一般我们只需要传递其本身就可以了。LiveData
会自动处理数据流的监听和解除绑定。
通常来说:在onCreate
中对数据进行一次性的绑定,后面就不需要再次绑定了。
当生命周期走到onStart
和onResume
的时候,LiveData
会自动接收事件流;
当页面处于不活动的时候,将会暂停接收事件流,页面恢复时恢复数据接收。(例如A跳转到B,那么A将会暂停接收。当从B回到A以后,将恢复数据流接收)
当页面onDestroy
时候,会自动删除观察者,从而中断事件流。
可以看出LiveData
作为官方套件,使用简单,生命周期的响应也是很智能的,一般都不需要额外处理了。
(更高级的用法,可以参考官方Demo,可以对数据库缓存等待都进行一整套的响应式封装,非常nice。建议学习下官方的封装思想,就算不用,也是对自己大有裨益)
4. Kotlin协程
上面说了那么多,这里步入了正题。大家仔细观察下会发现,上面均是使用的Retrofit
的enqueue
异步方法,再使用Callback
进行的网络回调,就算是RxJava和Livedata的转换器,内部其实也是使用的Callback
。在此之前,Retrofit
的作者也写了一个协程的转换器,地址在这,但内部依然使用的是Callback
,本质均为一样。(目前该库才被废弃,其实我也觉得这样使用协程就没意义了,Retrofit
在最新的2.6.0版本,直接支持了kotlin协程的suspend
挂起函数),
之前了解Retrofit
的小伙伴应该知道,Retrofit
是有同步和异步两种调用方式的。
void enqueue(Callback<T> callback);
上面这就是异步调用方式,传入一个Callback
,这也是我们最最最常用到的方式。
Response<T> execute() throws IOException;
上面这种是同步调用方法,会阻塞线程,返回的直接就是网络数据Response
,很少使用。
后来我就在思考,能不能结合kotlin的协程,抛弃Callback
,直接使用Retrofit
的同步方法,把异步当同步写,代码顺序书写,逻辑清晰,效率高,同步的写法就更加方便对象的管理。
说干就干。
首先写一个协程的扩展方法:
val api = ……
fun <ResultType> CoroutineScope.retrofit() {
this.launch(Dispatchers.Main) {
val work = async(Dispatchers.IO) {
try {
api.execute() // 调用同步方法
} catch (e: ConnectException) {
e.logE()
println("网络连接出错")
null
} catch (e: IOException) {
println("未知网络错误")
null
}
}
work.invokeOnCompletion { _ ->
// 协程关闭时,取消任务
if (work.isCancelled) {
api.cancel() // 调用 Retrofit 的 cancel 方法关闭网络
}
}
val response = work.await() // 等待io任务执行完毕返回数据后,再继续后面的代码
response?.let {
if (response.isSuccessful) {
println(response.body()) //网络请求成功,获取到的数据
} else {
// 处理 HTTP code
when (response.code()) {
401 -> {
}
500 -> {
println("内部服务器错误")
}
}
println(response.errorBody()) //网络请求失败,获取到的数据
}
}
}
}
上面就是核心代码,主要的意思都写了注释。整个工作流程是出于ui协程中,所以可以随意操作UI控件,接着在io线程中去同步调用网络请求,并且等待io线程的执行完毕,接着再拿到结果进行处理,整个流程都是基于同步代码的书写方式,一步一个流程,没有回掉而导致的代码割裂感。那么继续,我们想办法把获取的数据返回出去。
这里我们采用DSL方法,首先自定义一个类:
class RetrofitCoroutineDsl<ResultType> {
var api: (Call<ResultType>)? = null
internal var onSuccess: ((ResultType?) -> Unit)? = null
private set
internal var onComplete: (() -> Unit)? = null
private set
internal var onFailed: ((error: String?, code, Int) -> Unit)? = null
private set
var showFailedMsg = false
internal fun clean() {
onSuccess = null
onComplete = null
onFailed = null
}
fun onSuccess(block: (ResultType?) -> Unit) {
this.onSuccess = block
}
fun onComplete(block: () -> Unit) {
this.onComplete = block
}
fun onFailed(block: (error: String?, code, Int) -> Unit) {
this.onFailed = block
}
}
此类对外暴露了三个方法:onSuccess
,onComplete
,onFailed
,用于分类返回数据。
接着,我们对我们的核心代码进行改造,将方法进行传递:
fun <ResultType> CoroutineScope.retrofit(
dsl: RetrofitCoroutineDsl<ResultType>.() -> Unit //传递方法,需要哪个,传递哪个
) {
this.launch(Dispatchers.Main) {
val retrofitCoroutine = RetrofitCoroutineDsl<ResultType>()
retrofitCoroutine.dsl()
retrofitCoroutine.api?.let { it ->
val work = async(Dispatchers.IO) { // io线程执行
try {
it.execute()
} catch (e: ConnectException) {
e.logE()
retrofitCoroutine.onFailed?.invoke("网络连接出错", -100)
null
} catch (e: IOException) {
retrofitCoroutine.onFailed?.invoke("未知网络错误", -1)
null
}
}
work.invokeOnCompletion { _ ->
// 协程关闭时,取消任务
if (work.isCancelled) {
it.cancel()
retrofitCoroutine.clean()
}
}
val response = work.await()
retrofitCoroutine.onComplete?.invoke()
response?.let {
if (response.isSuccessful) {
retrofitCoroutine.onSuccess?.invoke(response.body())
} else {
// 处理 HTTP code
when (response.code()) {
401 -> {
}
500 -> {
}
}
retrofitCoroutine.onFailed?.invoke(response.errorBody(), response.code())
}
}
}
}
}
这里使用DSL传递方法,可以更具需要传递的,例如只需要onSuccess
,那就只传递这一个方法,不必三个都传递,按需使用。
使用方式:
首先需要按照kotlin的官方文档来改造下activity:
abstract class BaseActivity : AppCompatActivity(), CoroutineScope {
private lateinit var job: Job // 定义job
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job // Activity的协程
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
job = Job()
}
override fun onDestroy() {
super.onDestroy()
job.cancel() // 关闭页面后,结束所有协程任务
}
}
Activity
实现CoroutineScope
接口,就能直接根据当前的context
获取协程使用。
接下来就是真正的使用,在任意位置即可调用此扩展方法:
retrofit<String> {
api = api.login("1","1")
onComplete {
}
onSuccess { str ->
}
onFailed { error, code ->
}
}
在有的时候,我们只需要处理onSuccess
的情况,并不关心其他两个。那么直接写:
retrofit<String> {
api = api.login("1","1")
onSuccess { str ->
}
}
需要哪个写哪个,代码非常整洁。
可以看出,我们不需要单独再对网络请求进行生命周期的绑定,在页面被销毁的时候,job
也就被关闭了,当协程被关闭后,会执行调用 Retrofit 的 cancel 方法关闭网络。
5. 小节
协程的开销是小于Thread
多线程的,响应速度很快,非常适合轻量化的工作流程。对于协程的使用,还有带我更深入的思考和学习。协程并不是Thread
的替代品,还是多异步任务多一个补充,我们不能按照惯性思维去理解协程,而是要多从其本身特性入手,开发出它更安逸的使用方式。 而且随着Retrofit 2.6.0
的发布,自带了新的协程方案,如下:
@GET("users/{id}")
suspend fun user(@Path("id") long id): User
增加了suspend
挂起函数的支持,可见协程的应用会越来越受欢迎。
上面所说的所有网络处理方法,不论是Rx
还是LiveData
,都是很好的封装方式,技术没有好坏之分。我的协程封装方式,也许也不是最好的,但是我们不能缺乏思考、探索、实践三要素,去想去做。
最好的答案,永远都是自己给出的。
第一次写这种类型的文章记录,流程化比较严重,记录不严谨,各位见谅。谢谢大家的阅读
最后
有小伙伴私信问Compose的问题,好不好用啊,现在要不要学啊?
其实答案很简单,自从谷歌2019年公布了声明式UI框架Jetpack Compose后,两年多的时间,各种大力宣传,和大量资源的倾斜,API功能都趋于稳定了。
至于好不好用,各种用过的同行都是持肯定态度的。优势大概就是这四点:
强大的工具和直观的Kotlin API
简化并加速了Android上的UI开发
可以帮助开发者用更少更直观的代码创建View
有更强大的功能,以及还能提高开发速度
这么大的优势,毋庸置疑,肯定是要学的嘛,而且越快掌握越好。别等刀架到脖子上了,才去练金钟罩。
至于怎么快速上手,可以给大家免费分享一份**《Jetpack Compose 完全开发手册》**,手把手教大家从入门到精通。
第一章 初识 Jetpack Compose
-
为什么我们需要一个新的UI 工具?
-
Jetpack Compose的着重点
加速开发
强大的UI工具
直观的Kotlin API
- API 设计
- Compose API 的原则
一切都是函数
顶层函数(Top-level function)
组合优于继承
信任单一来源
- 深入了解Compose
Core
Foundation
Material
- 插槽API
第二章 Jetpack Compose构建Android UI
- Android Jetpack Compose 最全上手指南
Jetpack Compose 环境准备和Hello World
布局
使用Material design 设计
Compose 布局实时预览
……
- 深入详解 Jetpack Compose | 优化 UI 构建
Compose 所解决的问题
Composable 函数剖析
声明式 UI
组合 vs 继承
封装
重组
……
- 深入详解 Jetpack Compose | 实现原理
@Composable 注解意味着什么?
执行模式
Positional Memoization (位置记忆化)
存储参数
重组
……
第三章 Jetpack Compose 项目实战演练(附Demo)
- Jetpack Compose应用1
开始前的准备
创建DEMO
遇到的问题
- Jetpack Compose应用2
- Jetpack Compose应用做一个倒计时器
数据结构
倒计时功能
状态模式
Compose 布局
绘制时钟
- 用Jetpack Compose写一个玩安卓App
准备工作
引入依赖
新建 Activity
创建 Compose
PlayTheme
画页面
底部导航栏
管理状态
添加页面
- 用Compose Android 写一个天气应用
开篇
画页面
画背景
画内容
……
- 用Compose快速打造一个“电影App”
成品
实现方案
实战
不足
……
由于篇幅限制,文档的详解资料太全面,细节内容太多,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!
有需要的小伙伴,可以微信扫下方二维码免费领取