【问题标题】:Kotlin coroutines crash with no helpful stacktraceKotlin 协程崩溃,没有有用的堆栈跟踪
【发布时间】:2021-05-05 14:35:12
【问题描述】:

我的 Android 应用程序崩溃了,我在 Logcat 中看到了这个堆栈跟踪。它没有告诉我是哪一行代码导致了问题。

2021-05-05 09:13:33.143 1069-1069/com.mycompany.app E/AndroidRuntime: FATAL EXCEPTION: main
  Process: com.mycompany.app, PID: 1069
  retrofit2.HttpException: HTTP 403 
    at retrofit2.KotlinExtensions$await$2$2.onResponse(KotlinExtensions.kt:53)
    at retrofit2.OkHttpCall$1.onResponse(OkHttpCall.java:161)
    at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:519)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
    at java.lang.Thread.run(Thread.java:919)

有没有办法将它映射回我的代码,以查看是哪个改造调用导致了它?我有一个代码如下的存储库:

suspend fun getSomeData(): Stuff {
   return withContext(Dispatchers.IO) {
      val body = myRetroApi.getStuff()
      ...

我是否需要包装每个 withContext 主体以确保没有 Throwables 逃逸?我认为如果有什么东西在那里引发了异常,它会记录一个错误,而不是让整个应用程序崩溃。

编辑

我在问这个问题时搞砸了,把重点放在错误的事情上。所以我要删除“改造”标签。事实证明,withContext(Dispatchers.IO) 调用确实按预期重新抛出了Exception,但是当异常返回到viewModelScope.launch 时,如果该块没有捕获它,应用程序就会崩溃。

【问题讨论】:

  • I thought that if something threw an exception there, it would log an error, not crash the entire app. 这是一个大胆的假设:)
  • 是的,我想是的,尽管它基于服务器端 Java 和后台线程的类似经验。让我吃惊的不是崩溃;更重要的是堆栈跟踪没有指向我的代码中的任何内容。
  • 对于协程,你需要提供自己的错误处理,如果你只是做一些研究,你可以找到很多这方面,人们通常使用safeApiCall 包装器来执行协程调用并返回结果
  • 好的,谢谢。堆栈跟踪根本不指向您的代码是否正常?我不习惯,但也许这是协程的标准?
  • 协程异常处理非常复杂,很难记住所有关于其行为的奇怪规则。我的建议是在可能的最低点捕获可能的异常,并使用结果包装密封类来包装它们,这是 Java 已检查异常的 Kotlin 替代方案。

标签: android kotlin kotlin-coroutines


【解决方案1】:

Retrofit 为您提供 403 Unauthorized HTTP 异常。可能是服务器没有传递任何其他错误消息,或者您需要捕获 HttpException 并检查消息。无论哪种情况,这都不是改造问题,因此它只是传递了从您调用的服务器获取的错误。

最好为 API 调用创建一个网络结果包装器和一个包装器函数来处理异常。

你可以做这样的事情。请记住,实际实施完全取决于您。然而,我建议在处理取消异常时使用 runCatching。

 sealed class NetworkResult<out T> {
    data class Success<T>(val data: T) : NetworkResult<T>()
    data class Error(val exception: Throwable, val message: String?) : NetworkResult<Nothing>()
 }
    suspend fun networkCall(): String = ""
suspend fun <T> safeApiCall(block: suspend () -> T): NetworkResult<T> {

    return runCatching {
        withContext(Dispatchers.IO) {
            block()
        }
    }.fold({
        NetworkResult.Success(it)
    }, {
        when (it) {
            is HttpException -> NetworkResult.Error(it, "Network error")
            else -> NetworkResult.Error(it, "Some other message...")
            // else -> throw it
        }
    })
}

suspend fun getData() {
    val result: NetworkResult<String> = safeApiCall {
        networkCall()
    }
    when (result) {
        is NetworkResult.Success -> {
            //Handle success
        }
        is NetworkResult.Error -> { //Handle error
        }
    }
}


runCatching 使用 Kotlin 的内置 Result 类,并且有多种处理结果的方法。这些只是其中的一部分。

    runCatching {
        //.....
    }.getOrElse { throwable ->
        //handle exception
    }

    runCatching {
        //.....
    }.getOrThrow()

    runCatching {

    }.onSuccess {

    }.onFailure {

    }

【讨论】:

    【解决方案2】:

    如果不处理异常,应用程序当然会崩溃。

    您可以添加一个 try catch 来避免这种情况:

    suspend fun getSomeData() {
       withContext(Dispatchers.IO) {
          try{
            val body = myRetroApi.getStuff()
            ...
          } catch (e : Exception){
             //your code
          }
    ...
    

    【讨论】:

    • 如何将异常从withContext 传播到getSomeData?如果这不可能,那么我想将 getSomeData 的返回类型从 T 更改为 T? 或 Result&lt;T&gt;。
    • @RobN 是的,由您决定如何表示。 Result&lt;T&gt; 之类的东西听起来是个不错的选择。这样您就不会丢失错误信息。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-06-16
    • 1970-01-01
    • 1970-01-01
    • 2015-09-11
    • 2019-07-26
    • 1970-01-01
    • 2011-07-27
    相关资源
    最近更新 更多