本人项目中使用协程有必定的心得和积累,这里列举一些经常使用的案例,说明使用协程以后的好处java
abstract class BaseCoroutineActivity : Activity(), CoroutineScope {
protected lateinit var job: Job
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
job = Job()
}
override fun onBeforeDestroy() {
super.onBeforeDestroy()
job.cancel()
}
}
复制代码
全部 Activity 继承这个类就能够得到 Activity 生命周期管理下的协程 Scope,有几个特色:api
Retrofit2 在新版本中直接支持了声明 suspend 方法,若是使用过都会以为比较爽,相对于 callback 和 RxJava 的方式要轻松许多安全
声明一个有 suspend 方法的 Api:网络
interface UserApi {
@GET("/user/list/{page}")
suspend fun fetchPage(page: Int): List<User>
}
复制代码
在 Activity 调用这个 Api:异步
class ExampleActivity : BaseCoroutineActivity() {
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onCreate(savedInstanceState, persistentState)
launch {
val api: UserApi = Retrofit.create(UserApi::class.java)
val userList = api.fetchPage(0)
someAdapter.setData(userList)
someAdapter.notifyDataSetChanged()
}
}
}
复制代码
fetchPage
是个挂起方法,即有挂起点,在没有返回结果回来以前 Activity finish 了,因为协程被取消了,挂起后的代码再也不执行,不会引发一些例如NPE的问题,而且按理Retrofit会监控当前 suspend 方法对应协程的 job 运行状况而去取消 Call,节省网络资源的消耗async
这里比起 callback 和 RxJava 要爽不少ide
使用 callback 和 RxJava 的方式在作分页逻辑时或多或少会引发一些难看的代码,可是使用协程不会,并且一看就明白函数
分页服务端通常会下发数据同时下发一个 hasMore 表示还有没有下一页工具
data class UserPage(
val hasMore: Boolean,
val list: List<User>
)
interface UserApi {
@GET("/user/list/{page}")
suspend fun fetchPage(page: Int): UserPage
}
复制代码
launch {
val api: UserApi = Retrofit.create(UserApi::class.java)
var page = 0
val allUsers = mutableListOf<User>()
do {
val userPage = api.fetchPage(page)
allUsers.addAll(userPage.list)
page ++
} while(userPage.hasMore)
someAdapter.setData(allUsers)
someAdapter.notifyDataSetChanged()
}
复制代码
这样的代码,彻底不须要注释也能看懂post
假设咱们再 Activity 中声明这些方法,毕竟须要一个线程环境,这里用 Activity 的 UI 线程,目的是理解方法自己
private val mHandler: Handler = Handler()
suspend fun delay(timeMills: Long) {
suspendCancellableCoroutine<Unit> { continuation ->
// 利用 Handler 作一个延迟
val callback: () -> Unit = {
continuation.resumeWith(Result.success(Unit))
}
mHandler.postDelayed(callback, timeMills)
// 注意协程的取消逻辑,随时会被外部取消
continuation.invokeOnCancellation {
mHandler.removeCallbacks(callback)
}
}
}
复制代码
本质上,上面的代码就是将 Handler 的 postDelayed 的 callback 形式转换为 suspend 方法,这样就可使用再协程内了
class OkHttpUtil(private val httpClient: OkHttpClient) {
private suspend fun download(url: String): InputStream {
// 挂起协程
return suspendCancellableCoroutine {
val call = download(url) { error, stream ->
// 有结果了
if (error != null) {
it.resumeWith(Result.failure(error))
} else {
it.resumeWith(Result.success(stream!!))
}
}
it.invokeOnCancellation {
call.safeCancel() // 这里是安全cancel掉
}
}
}
// 这里是日常的 callback 方式的请求代码
private fun download(url: String, callback: (error: Throwable?, stream: InputStream?) -> Unit) : Call {
val request = Request.Builder().url(url)
val call = httpClient.newCall(request.build())
call.enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
callback(e, null)
}
override fun onResponse(call: Call, response: Response) {
if (!response.isSuccessful) {
callback(IOException("Response with ${response.code()} ${response.message()}"), null)
return
}
try {
callback(null, response.body().byteStream())
} catch (e: Throwable) {
callback(e, null)
}
}
})
return call
}
}
复制代码
使用代码:
launch {
val okHttpClient = ...
val util = OkHttpUtil(okHttpClient)
val stream = util.download("...some url")
val file = File("... some file path")
FileUtils.streamToFile(stream, file)
}
复制代码
即利用挂起协程(suspendCancellableCoroutine)和 恢复协程(resumeWith)能够实现任意的异步代码转换成 suspend 方法,更有利于咱们写外部逻辑
Java 里面并无 suspend 关键字,那么 kotlin 的 suspend 反编译后是什么呢
这里咱们利用 AS 的工具来探索一下
首先,先任意新建 kotlin 文件写一个 suspend 函数
suspend fun test(arg: Any): Any {
return Any()
}
复制代码
使用下面的步骤获得反编译后的 Java 代码
以下
public final class Test2Kt {
@Nullable
public static final Object test(@NotNull Object arg, @NotNull Continuation $completion) {
return new Object();
}
}
复制代码
能够看到,Java 方法比 Kotlin 的方法要多一个参数:Continuation $completion
那么 Retrofit 建立动态代理拦截方法执行时便是利用这个参数来判断一个方法是否是 suspend
方法,再利用 $completion
的 resume
来返回结果, 若是这个 $completion
是一个 CancellableContinuation 的话,还能够利用 invokeOnCancellation
来取消当前的 Call
协程旨在将异步代码简化为同步编码的方式来优化流程化的代码,提升阅读性,固然协程还有利于节省线程资源的消耗。