# try catch encapsulate #### 以往在執行可能會出現例外錯誤的程式碼,最常使用的是try catch ```kotlin= try { val response = call() if (response.isSuccessful) { val body = response.body() Timber.d("body = $body") if (body != null) return Resource.success(body) } return error(" ${response.code()} , ${response.message()}") } catch (e: Exception) { return error(e.message ?: e.toString()) } ``` #### kotlin的標準函式庫Result幫我們把它封裝了 ##### 使用runcaching,他回傳一個Result,底下調用了Result的onSuccess和onFailure方法,你可以在其block再定義分別要執行什麼action ##### 相關連結可以參考[Roman Elizarov的github](https://github.com/Kotlin/KEEP/blob/master/proposals/stdlib/result.md) ```kotlin= lateinit var response: Response<T> kotlin.runCatching { response = call() }.onSuccess { if (response.isSuccessful) { val body = response.body() if (body != null) return Resource.success(body) } }.onFailure { return error(it.message ?: it.toString()) } return error(" ${response.code()} , ${response.message()}") ``` ### coroutine Exception handle #### 在coroutine builder ,如launch || sync,在這兩個block裡面所產生的exception並不會被throw出來,所以你在launch內使用try catch是沒有效果的 * 下面例子 它雖然有catch到exception,但程式還是crash了。 * coroutine的特性是發生exception它並不會throw出來,而是向上廣播直到root cotoutine,再經由CoroutineExceptionHandler處理 ```kotlin= fun main() { runBlocking { val job1 = GlobalScope.launch() { //root coroutine val job2 = launch { // child coroutine throw (Exception("throw error")) } try { job2.join() } catch (e: Exception) { println("catch exception , ${e.message}") } } job1.join() } } ``` 輸出 ==catch exception , StandaloneCoroutine is cancelling Exception in thread "DefaultDispatcher-worker-2" java.lang.Exception: throw error== * 所以我們在root coroutine 加入CoroutineExceptionHandler處理child coroutine丟出來的exception,程式就不會噴掉了 * 它會等所有child job都被終結才會執行exceptionHandler。 * 加入CoroutineExceptionHandler只能root coroutine (GlobalScope) or top-level coroutine使用(viewModelScope),目前只知道這兩個... * 雖然exceptionHandler可以處理所有的exception,但顯得不夠靈活 ```kotlin= fun main() { runBlocking { val exceptionHandler = CoroutineExceptionHandler { _, Throwable -> println("throwable = ${Throwable.message}") } val job1 = GlobalScope.launch(exceptionHandler) { val job2 = launch { throw (Exception("throw error")) } try { job2.join() } catch (e: Exception) { println("catch exception , ${e.message}") } } job1.join() } } ``` 輸出 ==catch exception , StandaloneCoroutine is cancelling throwable = throw error== --- ## 取代CoroutineExceptionHandler ### coroutineScope builder * 它有三個特點 1. 繼承呼叫者的coroutine context & 支援structured concurrency 2. child job發生exception時,不會廣播,取而代之的是把exception丟出去(好像是kotlin & Java的設計機制:呼叫一個function,當function內部發生exception,根據這個機制會把exception re-throw,所以呼叫方就會收到exception再作相對應處理) 3. 當child job其中一個發生exception,會終止其他的job * 下面例子 1. fun main()可以像是viewModelScope.launch去呼叫suspend fun 2. 執行childjob(),在第二個job丟出exception,就終止下面的其他job 3. 而main就可以catch到這個exception ```kotlin= fun main() = runBlocking { try { childjob() }catch (e:Exception){ println("catch exception ${e.message}") } //這個寫法也可以 kotlin.runCatching { childjob() } .onSuccess { println("job execute success") } .onFailure { println("job fail with ${it.message}") } } //------------------ } suspend fun childjob(){ coroutineScope { launch { println("job 1: ${Thread.currentThread().name}") } launch { throw RuntimeException("throw error job1") println("job 2: ${Thread.currentThread().name}") } } println("main job: ${Thread.currentThread().name}") } ``` 輸出 ==job 1: main catch exception throw error job1== ### supervisorScope builder * 它繼承了coroutineScope builder的前兩個特點 1. 繼承呼叫者的coroutine context & 支援structured concurrency 2. child job發生exception時,不會廣播,取而代之的是把exception丟出去 * 額外兩個特點 3. 當它的child job其中一個發生exception時,不會終結掉其他的job 4. 在coroutineScope底下所建立的coroutine會成為top-level coroutine(代表你可以調用==CoroutineExceptionHandler==) * **但是它並不會把exception丟回給呼叫方,需要在child job帶入CoroutineExceptionHandler處理exception** ```kotlin= fun main() { runBlocking { kotlin.runCatching { childjobWithsupervisorScope() } .onSuccess { println("job execute success") } .onFailure { println("job fail with ${it.message}") } } } suspend fun childjobWithsupervisorScope(){ supervisorScope { val exceptionHandler = CoroutineExceptionHandler { _, Throwable -> println("throwable = ${Throwable.message}") } val job1 = launch { println("this is job1") } val job2 = launch(exceptionHandler) { throw RuntimeException("job2 throw error") println("this is job2") } val job3 = launch { println("this is job3") } println("final") } } ``` 輸出 ==final this is job1 throwable = job2 throw error this is job3 job execute success== ## CancellationException * 在coroutine中使用try catch需要特別注意cancellationException * 下面的例子,使用delay表示網路請求,等待回傳資料的時間,因為delay是suspend fun,所以在500ms的時候就會執行job cancel了,但是輸出卻看到job繼續在執行 * 因為catch捕捉了所有exception,破壞了structured concurrency,所以job沒有被終止 ```kotlin= fun main() = runBlocking { val job = launch { try { println("Performing network request in Coroutine") delay(1000) } catch (e: Exception) { println("Handled exception in Coroutine") } println("Coroutine still running ... ") } delay(500) job.cancel() } ``` 輸出 ==Performing network request in Coroutine Handled exception in Coroutine Coroutine still running ...== * 多加一個判斷式.如果是CancellationException就把它丟出來,就可以正常取消job了,使用上要多注意 ```kotlin= fun main() = runBlocking { val job = launch { try { println("Performing network request in Coroutine") delay(1000) } catch (e: Exception) { if (e is CancellationException) { throw e } println("Handled exception in Coroutine") } println("Coroutine still running ... ") } delay(500) job.cancel() } ``` 輸出 ==Performing network request in Coroutine== 參考資料 [kotlin官方](https://kotlinlang.org/docs/exception-handling.html#cancellation-and-exceptions) [blog](https://www.netguru.com/codestories/exceptions-in-kotlin-coroutines) [Roman github](https://github.com/Kotlin/KEEP/blob/master/proposals/stdlib/result.md) [知乎-coroutine常見錯誤](https://zhuanlan.zhihu.com/p/347784556) ###### tags `exception handle` `kotlin` `Android`
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up