Kotlin Coroutine 協程代碼使用案例

本人項目中使用協程有必定的心得和積累,這裏列舉一些經常使用的案例,說明使用協程以後的好處java

官方Activity案例

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

  • Activity 自己是一個 CoroutineScope,能夠方便 launch/async 等等代碼的調用
  • 協程內的運行的代碼在 Activity 關閉後會被取消,不會引發沒必要要的問題
  • 協程默認狀況下的代碼都運行在主線程,能夠更新UI,由 Dispatchers.Main 決定的

Retrofit2

Retrofit2 在新版本中直接支持了聲明 suspend 方法,若是使用過都會以爲比較爽,相對於 callback 和 RxJava 的方式要輕鬆許多安全

簡單 Api

聲明一個有 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

異步方法轉 suspend

仿官方 delay 函數

假設咱們再 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 方法,這樣就可使用再協程內了

OkHttp 實例

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

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 方法,再利用 $completionresume 來返回結果, 若是這個 $completion 是一個 CancellableContinuation 的話,還能夠利用 invokeOnCancellation 來取消當前的 Call

最後

協程旨在將異步代碼簡化爲同步編碼的方式來優化流程化的代碼,提升閱讀性,固然協程還有利於節省線程資源的消耗。

相關文章
相關標籤/搜索