本人項目中使用協程有必定的心得和積累,這裏列舉一些經常使用的案例,說明使用協程以後的好處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
協程旨在將異步代碼簡化爲同步編碼的方式來優化流程化的代碼,提升閱讀性,固然協程還有利於節省線程資源的消耗。