Retrofit支持Coroutines
Retrofit 自从更新到了 2.6.0 版本,内置了对 Kotlin Coroutines 的支持,进一步简化了使用 Retrofit 和协程来进行网络请求的过程,这已经是2019年的事情。</br>
现在2020年已经到了一半了,目前版本已经到了2.9.0了,顺带一提Okhttp3也到了4.7.2
一般的网络请求需要什么
在Word文档里,大致画一下图:
从图片可以看出我们其实不仅需要除妖处理数据返回正确与否,其实还需各种状态去判断,当前请求的情况,所以我们需要引入状态这个因素进入网络请求框架。
Lifecycle中的LifecycleCoroutineScop
这个类需要引入:androidx.lifecycle:lifecycle-runtime-ktx: lastest version
截至本文章发布,我找到的最新版本lastest version: 2.3.0-alpha03
</br>
源码:
/**
* [CoroutineScope] tied to this [LifecycleOwner]'s [Lifecycle].
*
* This scope will be cancelled when the [Lifecycle] is destroyed.
*
* This scope is bound to
* [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate].
*/
val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope
get() = lifecycle.coroutineScope
/**
* [CoroutineScope] tied to this [Lifecycle].
*
* This scope will be cancelled when the [Lifecycle] is destroyed.
*
* This scope is bound to
* [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate]
*/
val Lifecycle.coroutineScope: LifecycleCoroutineScope
get() {
while (true) {
val existing = mInternalScopeRef.get() as LifecycleCoroutineScopeImpl?
if (existing != null) {
return existing
}
val newScope = LifecycleCoroutineScopeImpl(
this,
SupervisorJob() + Dispatchers.Main.immediate
)
if (mInternalScopeRef.compareAndSet(null, newScope)) {
newScope.register()
return newScope
}
}
}
先不看代码,我们看一下那个注释,看到熟悉的CoroutineScope
对~!协程
ViewModel
那边也有一个,有兴趣的同学可以去看看。再看看LifecycleCoroutineScope
这个类:
/**
* [CoroutineScope] tied to a [Lifecycle] and
* [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate]
*
* This scope will be cancelled when the [Lifecycle] is destroyed.
*
* This scope provides specialised versions of `launch`: [launchWhenCreated], [launchWhenStarted],
* [launchWhenResumed]
*/
abstract class LifecycleCoroutineScope internal constructor() : CoroutineScope {
internal abstract val lifecycle: Lifecycle
/**
* Launches and runs the given block when the [Lifecycle] controlling this
* [LifecycleCoroutineScope] is at least in [Lifecycle.State.CREATED] state.
*
* The returned [Job] will be cancelled when the [Lifecycle] is destroyed.
* @see Lifecycle.whenCreated
* @see Lifecycle.coroutineScope
*/
fun launchWhenCreated(block: suspend CoroutineScope.() -> Unit): Job = launch {
lifecycle.whenCreated(block)
}
/**
* Launches and runs the given block when the [Lifecycle] controlling this
* [LifecycleCoroutineScope] is at least in [Lifecycle.State.STARTED] state.
*
* The returned [Job] will be cancelled when the [Lifecycle] is destroyed.
* @see Lifecycle.whenStarted
* @see Lifecycle.coroutineScope
*/
fun launchWhenStarted(block: suspend CoroutineScope.() -> Unit): Job = launch {
lifecycle.whenStarted(block)
}
/**
* Launches and runs the given block when the [Lifecycle] controlling this
* [LifecycleCoroutineScope] is at least in [Lifecycle.State.RESUMED] state.
*
* The returned [Job] will be cancelled when the [Lifecycle] is destroyed.
* @see Lifecycle.whenResumed
* @see Lifecycle.coroutineScope
*/
fun launchWhenResumed(block: suspend CoroutineScope.() -> Unit): Job = launch {
lifecycle.whenResumed(block)
}
}
然后我们关注到一点,我们都希望网络请求能在Activity
or Fragment
的destroy
方法中自己结束,看看注释The returned [Job] will be cancelled when the [Lifecycle] is destroyed.
,帮我们完成了。其中launchWhenCreated
和launchWhenStarted
是我们关注的方法,这个state
在这里:
/**
* Created state for a LifecycleOwner. For an {@link android.app.Activity}, this state
* is reached in two cases:
* <ul>
* <li>after {@link android.app.Activity#onCreate(android.os.Bundle) onCreate} call;
* <li><b>right before</b> {@link android.app.Activity#onStop() onStop} call.
* </ul>
*/
CREATED,
/**
* Started state for a LifecycleOwner. For an {@link android.app.Activity}, this state
* is reached in two cases:
* <ul>
* <li>after {@link android.app.Activity#onStart() onStart} call;
* <li><b>right before</b> {@link android.app.Activity#onPause() onPause} call.
* </ul>
*/
STARTED,
可以根据个人需求选择,我的选择的方案是launchWhenCreated
进行后续开发。
封装网络请求
在开展封装前,我们先定义一个接口
interface JsonResult {
fun isLegal() : Boolean
}
isLegal()
用于判断网络接口返回是否为正确的返回内容,这么做的好处是方便适配多变的接口返回的数据结构。</br>
以下开展以高德地图的天气接口作为参考:
新建一个基础类继承这接口
open class JsonData : JsonResult {
var status = ""
override fun isLegal(): Boolean = status == "1"
}
然后我们再新建一个类去继承这个基础类
data class Weather(@SerializedName("lives")
val lives: ArrayList<LivesItem>,
@SerializedName("count")
val count: String = "",
@SerializedName("infocode")
val infocode: String = "",
@SerializedName("info")
val info: String = "") : JsonData()
这个类基础类和后面这个解析类需要根据你接口进行调整,高德地图的接口返回是这样的:
{"status":"1","count":"1","info":"OK","infocode":"10000","lives":[{"province":"广东","city":"白云区","adcode":"440111","weather":"雨","temperature":"23","winddirection":"西","windpower":"≤3","humidity":"98","reporttime":"2020-06-09 22:52:20"}]}
基本的信息都知道了,由于开发现在普遍都开始利用了LiveData进行分发的,那我们需要进行请求过程中各个状态进行处理,先看代码:
class KResult<T>(var status:String, var response: T? = null){
companion object{
const val Loading = "Loading"
const val Success = "Success"
const val Failure = "Failure"
const val OutTime = "OutTime"
}
private var loading:(()->Unit)? = null
fun whenLoading(loading:()->Unit){
this.loading = loading
}
private var success:(T?.()->Unit)? = null
fun whenSuccess(success:(T?.()->Unit)){
this.success = success
}
private var failed:(T?.()->Unit)? = null
fun whenFailed(failed:(T?.()->Unit)){
this.failed = failed
}
private var outTime:(()->Unit)? = null
fun whenOutTime(outTime:()->Unit){
this.outTime = outTime
}
fun whenStatus(result: KResult<T>.()->Unit){
result.invoke(this)
when(status){
Loading ->{
loading?.invoke()
}
Success ->{
success?.invoke(response)
}
Failure ->{
failed?.invoke(response)
}
OutTime ->{
outTime?.invoke()
}
}
}
}
所以结合以上这些,我们就能把网络封装写成如下:
class CallResult<T: JsonResult> constructor(private var owner: LifecycleOwner?, callResult: CallResult<T>.()->Unit) {
init {
callResult()
}
private var data : MutableLiveData<KResult<T>>? = null
var repeatWhenOutTime = 0
private var tryCount = 0
fun post(data : MutableLiveData<KResult<T>>){
this.data = data
}
fun hold(result: suspend () -> Response<T>){
var response: Response<T>?
var netJob: Job? = null
var call : KResult<T>
owner?.apply {
netJob = lifecycleScope.launchWhenCreated {
call = KResult(KResult.Loading)
makeCall(call)
onLoading?.invoke()
response = tryRepeat(result)
if (response != null) {
response?.run {
if(code() != 200){
call = KResult(KResult.OutTime)
makeCall(call)
loadingOutTime?.invoke(call)
netJob?.cancel()
}else{
build(response)
}
}
} else {
call = KResult(KResult.OutTime)
makeCall(call)
loadingOutTime?.invoke(call)
netJob?.cancel()
}
}
}
}
private suspend fun tryRepeat(result: suspend () -> Response<T>) : Response<T>?{
return try {
val output = withTimeoutOrNull(10000){
withContext(Dispatchers.IO){
result.invoke()
}
}
Logger.eLog("CallResult","output : $output tryCount : $tryCount")
return if(output == null && tryCount < repeatWhenOutTime){
tryCount ++
tryRepeat(result)
}else{
output
}
}catch (e:java.lang.Exception){
e.printStackTrace()
null
}
}
private fun build(response: Response<T>?) {
response?.apply {
val call : KResult<T>
try {
if (body() == null) {
call = KResult(KResult.Failure, null)
makeCall(call)
onError?.invoke(call)
} else {
if (body()!!.isLegal()) { // 正确返回成功
call = KResult(KResult.Success, body())
makeCall(call)
onSuccess?.invoke(call)
} else {
call = KResult(KResult.Failure, body())
makeCall(call)
onError?.invoke(call)
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
private fun makeCall(call: KResult<T>) : KResult<T> {
data?.postValue(call)
return call
}
private var onLoading: (() -> Unit)? = null
fun loading(onLoading: (() -> Unit)): CallResult<T> {
this.onLoading = onLoading
return this
}
private var loadingOutTime: ((result: KResult<T>) -> Unit)? = null
fun outTime(loadingOutTime: ((result: KResult<T>) -> Unit)) : CallResult<T> {
this.loadingOutTime = loadingOutTime
return this
}
private var onSuccess: ((result: KResult<T>) -> Unit)? = null
private var onError: ((result: KResult<T>) -> Unit)? = null
fun success(onSuccess: ((result: KResult<T>) -> Unit)): CallResult<T> {
this.onSuccess = onSuccess
return this
}
fun error(onError: ((result: KResult<T>) -> Unit)): CallResult<T> {
this.onError = onError
return this
}
}
其中利用到DSL语法,待会举例子,首先关注点:
lifecycleScope.launchWhenCreated 中调用这个withContext(Dispatchers.Main)
根据源码:
suspend fun <T> Lifecycle.whenStateAtLeast(
minState: Lifecycle.State,
block: suspend CoroutineScope.() -> T
) = withContext(Dispatchers.Main.immediate) {
val job = coroutineContext[Job] ?: error("when[State] methods should have a parent job")
val dispatcher = PausingDispatcher()
val controller =
LifecycleController(this@whenStateAtLeast, minState, dispatcher.dispatchQueue, job)
try {
withContext(dispatcher, block)
} finally {
controller.finish()
}
}
一、这里使用了withContext(Dispatchers.Main.immediate)
,所以我这里withContext(Dispatchers.Main)
有没有必要这么写呢?两个区别在哪里?留个问题给大家思考</br>
二、第二个是withTimeoutOrNull
这个方法,源码注释
Runs a given suspending block of code inside
a coroutine with a specified [timeout][timeMillis]
and returns `null` if this timeout was exceeded.
简单理解就是超时时候返回结果null,方便我们判断是否超时。
如何使用
Retrofit对应的接口,加上suspend
@GET("/v3/weather/weatherInfo")
suspend fun getWeather(@Query("key") key:String,@Query("city") city:String) : Response<Weather>
在执行网络请求的地方,如下:
fun getWeather(code:String,call:MutableLiveData<KResult<Weather>>){
CallResult<Weather>(owner){
post(call)
hold { main.getWeather("XXXXXXXXXXXXX",code) }
}
}
在接受Weather对应LiveData的地方如下:
weather.observe(this, Observer {
it?.apply {
whenStatus {
whenLoading {
//处理加载
}
whenSuccess {
//处理成功 这里是this不是it 对应 Weather?
}
whenFailed {
//处理失败
}
whenOutTime {
//处理超时
}
}
}
})
总结
Kotlin的协程Coroutines,并非很深的内容,属于对线程池的进一步封装实现调度等等
可以看到很明显的三个优点:</br>
一、很容易离开主线程,参考RxJava,对比下协程可以让开发者只需要更少的代码就完成这个工作,而且没有累人的回调处理。
二、样板代码最少。协程完全活用了 Kotlin 语言的能力,包括 suspend 方法。让编写线程任务过程就和编写普通的代码块差不多。
三、当一个操作不再需要被执行时或者超时或者受LifeOwner的不同状态影响,协程会自动取消它。