问题
线上有个崩溃日志,定位到的代码大致如下:
kotlin
fun question(activity: FragmentActivity) {
try {
activity.lifecycleScope.launch {
withContext(Dispatchers.IO) {
//模拟在协程中抛异常
throw IllegalStateException("Exception Occur")
}
}
} catch (e: Exception) {
e.printStackTrace()
//这里只会捕获函数本身的异常
log("捕获Exception:$e")
}
}
经过Tools -> Kotlin -> Show Kotlin Bytecode 反编译查看:
java
public final void question(@NotNull FragmentActivity activity) {
Intrinsics.checkNotNullParameter(activity, "activity");
try {
BuildersKt.launch$default((CoroutineScope)LifecycleOwnerKt.getLifecycleScope((LifecycleOwner)activity), (CoroutineContext)null, (CoroutineStart)null, new Function2((Continuation)null) {
int label;
public final Object invokeSuspend(Object $result) {
Object var2 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
switch (this.label) {
case 0:
ResultKt.throwOnFailure($result);
CoroutineContext var10000 = (CoroutineContext)Dispatchers.getIO();
Function2 var10001 = new Function2((Continuation)null) {
int label;
public final Object invokeSuspend(Object $result) {
IntrinsicsKt.getCOROUTINE_SUSPENDED();
switch (this.label) {
case 0:
ResultKt.throwOnFailure($result);
throw new IllegalStateException("Exception Occur");
default:
throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
}
}
public final Continuation create(Object value, Continuation $completion) {
return (Continuation)(new <anonymous constructor>($completion));
}
public final Object invoke(CoroutineScope p1, Continuation p2) {
return ((<undefinedtype>)this.create(p1, p2)).invokeSuspend(Unit.INSTANCE);
}
// $FF: synthetic method
// $FF: bridge method
public Object invoke(Object p1, Object p2) {
return this.invoke((CoroutineScope)p1, (Continuation)p2);
}
};
Continuation var10002 = (Continuation)this;
this.label = 1;
if (BuildersKt.withContext(var10000, var10001, var10002) == var2) {
return var2;
}
break;
case 1:
ResultKt.throwOnFailure($result);
break;
default:
throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
}
return Unit.INSTANCE;
}
public final Continuation create(Object value, Continuation $completion) {
return (Continuation)(new <anonymous constructor>($completion));
}
public final Object invoke(CoroutineScope p1, Continuation p2) {
return ((<undefinedtype>)this.create(p1, p2)).invokeSuspend(Unit.INSTANCE);
}
// $FF: synthetic method
// $FF: bridge method
public Object invoke(Object p1, Object p2) {
return this.invoke((CoroutineScope)p1, (Continuation)p2);
}
}, 3, (Object)null);
} catch (Exception e) {
e.printStackTrace();
CommonExtKt.log("捕获Exception:" + e);
}
}
上述代码是CPS变换 + Continuation续体 + 状态机之后的结果,关于协程的使用,参见:深入理解Kotlin协程
在协程外部使用了try-catch,而 try-catch无法捕获协程内部抛出的异常,是因为协程内部的异常是通过协程的异常处理机制传播的,而不是同步地抛出到调用线程。解决办法通常是在协程内部使用try-catch,或者使用协程提供了的异常处理机制(如SupervisorJob、CoroutineExceptionHandler),协程的异常是传递性的,会传播给父协程或父Job,而不是直接在调用方被捕获。
try-catch是在调用协程函数时执行的,而函数内部的suspend函数是异步的,异常发生在协程启动后,try-catch可能已经执行完毕。注意:这里协程的"异步"指的是执行流程的时序控制,而不单指线程切换。
解决方案
1、在协程内部使用try-catch
在协程内部处理:
kotlin
/**
* 方式1:try/catch放到协程内部
*/
fun solution1(activity: FragmentActivity) {
activity.lifecycleScope.launch {
try {
withContext(Dispatchers.IO) {
//模拟在协程中抛异常
throw IllegalStateException("Exception Occur")
}
} catch (e: Exception) {
e.printStackTrace()
//这里只会捕获launch函数本身的异常
log("捕获Exception:$e")
}
}
}
2、CoroutineExceptionHandler
全局异常处理器 CoroutineExceptionHandler:用于捕获未被子协程处理的异常,可以全局处理异常。
kotlin
/**
* CoroutineExceptionHandler处理异常
*/
fun solution2(activity: FragmentActivity) {
val exceptionHandler = CoroutineExceptionHandler { context, throwable ->
log("捕获Exception2:$throwable")
}
activity.lifecycleScope.launch(exceptionHandler) {
//NOTE:这里可能被调度到将来某个时间点执行
withContext(Dispatchers.IO) {
//模拟在协程中抛异常
throw IllegalStateException("Exception Occur")
}
}
}
总结:核心思想是将try-catch放在异常抛出的地方(即协程的内部),或者使用协程提供的结构化并发机制来管理异常