📜  Kotlin 协程中的异常处理

📅  最后修改于: 2022-05-13 01:54:54.499000             🧑  作者: Mango

Kotlin 协程中的异常处理

协程是我们在 Android 中使用 Kotlin 进行异步编程的一种新方式。在开发生产就绪应用程序时,我们希望确保正确处理所有异常,以便用户在使用我们的应用程序时获得愉快的体验。在本文中,我们将讨论在使用 Kotlin 协程时如何正确处理 Android 项目中的异常。如果你想学习如何使用 Kotlin 协程,可以从这里开始。本文将分为以下几个部分:

  1. 有哪些例外?
  2. 我们一般如何处理异常?
  3. 我们如何有效地处理 Kotlin Coroutines 中的异常?

有哪些例外?

异常是在程序运行或执行时发生的意外事件。执行被异常中断,应用程序的预期流程没有执行。这就是为什么为了执行应用程序的正确流程,我们必须在代码中处理异常。

我们一般如何处理异常?

try-catch 块是在 Kotlin 中处理异常的一般方法。在 try 块中,我们编写可能会抛出异常的代码,如果产生异常,则在 catch 块中捕获该异常。让我们用一个例子来说明:

Kotlin
try {
    val gfgAnswer = 5 / 0
    val addition = 2 + 5
    Log.d("GfGMainActivity", gfgAnswer.toString())
    Log.d("GfGMainActivity", addition.toString())
} catch (e: Exception) {
    Log.e("GfGMainActivity", e.toString())
}


Kotlin
interface GfgService {
    @GET("courses")
    suspend fun getCourses(): List
    @GET("more-courses")
    suspend fun getMoreCourses(): List
    @GET("error")
    suspend fun getCoursesWithError(): List
}


Kotlin
class TryCatchViewModel(
    private val gfgUser: GfgUser,
    private val gfgCoursedb: DatabaseHelper
) : ViewModel() {
  
    private val gfg = MutableLiveData>>()
  
    fun fetchGfg() {
        viewModelScope.launch {
            gfg.postValue(Resource.loading(null))
            try {
                val gfgFromApi = gfgUser.getGfg()
                gfg.postValue(Resource.success(gfgFromApi))
            } catch (e: Exception) {
                gfg.postValue(Resource.error("Something Went Wrong", null))
            }
        }
    }
  
    fun getGfg(): LiveData>> {
        return gfg
    }
}


Kotlin
fun gfgGfgPro() {
    viewModelScope.launch {
        gfgPro.postValue(Resource.loading(null))
        try {
            val moreGfgProFromApi = apiHelper.getGfgProWithError()
            val gfgProFromApi = apiHelper.getGfgPro()
              
            val allGfgProFromApi = mutableListOf()
            allGfgProFromApi.addAll(gfgProFromApi)
            allGfgProFromApi.addAll(moreGfgProFromApi)
  
            gfgPro.postValue(Resource.success(allGfgProFromApi))
        } catch (e: Exception) {
            gfgPro.postValue(Resource.error("Aw Snap!", null))
        }
    }
}


Kotlin
fun fetchGfgPro() {
    viewModelScope.launch {
        gfgPro.postValue(Resource.loading(null))
        try {
            val moreGfgProFromApi = try {
                apiHelper.getGfgProWithError()
            } catch (e: Exception) {
                emptyList()
            }
            val gfgProFromApi = try {
                apiHelper.getGfgPro()
            } catch (e: Exception) {
                emptyList()
            }
  
            val allGfgProFromApi = mutableListOf()
            allGfgProFromApi.addAll(gfgProFromApi)
            allGfgProFromApi.addAll(moreGfgProFromApi)
  
            gfgPro.postValue(Resource.success(allGfgProFromApi))
        } catch (e: Exception) {
            gfgPro.postValue(Resource.error("Aw Snap", null))
        }
    }
}


Kotlin
class ExceptionHandlerViewModel(
    private val gfgServerAPI: GfgServerAPI,
    private val gfgHelperDB: DatabaseHelper
) : ViewModel() {
  
    private val gfgUsers = MutableLiveData>>()
  
    private val exceptionHandler = CoroutineExceptionHandler { _, exception ->
        gfgUsers.postValue(Resource.error("Aw Snap!", null))
    }
    fun fetchGfgUsers() {
        viewModelScope.launch(exceptionHandler) {
            gfgUsers.postValue(Resource.loading(null))
            val gfgUsersFromApi = gfgServerAPI.getGfgUsers()
            gfgUsers.postValue(Resource.success(gfgUsersFromApi))
        }
    }
    fun getGfgUsers(): LiveData>> {
        return gfgUsers
    }
}


Kotlin
private fun fetchGfgUsers() {
    viewModelScope.launch {
        gfgUsers.postValue(Resource.loading(null))
        try {
                val gfgUsersWithErrorFromGfgServerDeferred = async { gfgServerHelper.getGfgUsersWithError() }
                val moreGfgUsersFromGfgServerDeferred = async { gfgServerHelper.getMoreGfgUsers() }
                val gfgUsersWithErrorFromGfgServer = gfgUsersWithErrorFromGfgServerDeferred.await()
                val moreGfgUsersFromGfgServer = moreGfgUsersFromGfgServerDeferred.await()
                val allGfgUsersFromGfgServer = mutableListOf()
                allGfgUsersFromGfgServer.addAll(gfgUsersWithErrorFromGfgServer)
                allGfgUsersFromGfgServer.addAll(moreGfgUsersFromGfgServer)
  
                gfgUsers.postValue(Resource.success(allGfgUsersFromGfgServer))
  
        } catch (e: Exception) {
            gfgUsers.postValue(Resource.error(Aw Snap!", null))
        }
    }
}


在前面的代码中,我们尝试将 5 除以 0,同时将两个数字 2,5 相加。然后应该在 Logcat 中打印解决方案。当我们运行应用程序时,我们应该首先获取解决方案变量中的值,然后将总和分配给附加变量。稍后将在 Log 语句中打印这些值。在这种情况下,不会打印解和加法变量,但 catch 块中的 Log 语句带有算术异常。这样做的原因是没有数字可以被0除。所以,当我们得到异常时,你可以看到第一行以下没有任何步骤,它直接进入了catch块。前面的代码演示了异常是如何发生的以及我们如何处理它。

我们如何有效地处理 Kotlin Coroutines 中的异常?

现在我们将看看如何使用 Kotlin Coroutines 在我们的项目中有效地处理异常。有几种处理异常的方法。

通用方法

使用 SupervisorScope 和 CoroutineExceptionHandler。为了进一步说明这一点,请考虑检索用户列表。我们会有一个界面:

科特林

interface GfgService {
    @GET("courses")
    suspend fun getCourses(): List
    @GET("more-courses")
    suspend fun getMoreCourses(): List
    @GET("error")
    suspend fun getCoursesWithError(): List
}

我们在这里有三个不同的挂起函数,可以用来获取用户列表。如果仔细观察,您会注意到只有前两个函数 getUsers() 和 getMoreUsers() 会返回一个列表,而第三个函数getUserWithError() 会抛出异常。

gfg.HttpException: HTTP 404 Not Found

为了清楚起见,我们特意设计了 getUserWithError() 来抛出异常。现在,让我们回顾一下如何使用 Kotlin 协程正确处理代码中的异常。

1. 通用方法

考虑以下场景:我们有一个 ViewModel,TryCatchViewModel(存在于项目中),我想在 ViewModel 中执行我的 API 调用。

科特林

class TryCatchViewModel(
    private val gfgUser: GfgUser,
    private val gfgCoursedb: DatabaseHelper
) : ViewModel() {
  
    private val gfg = MutableLiveData>>()
  
    fun fetchGfg() {
        viewModelScope.launch {
            gfg.postValue(Resource.loading(null))
            try {
                val gfgFromApi = gfgUser.getGfg()
                gfg.postValue(Resource.success(gfgFromApi))
            } catch (e: Exception) {
                gfg.postValue(Resource.error("Something Went Wrong", null))
            }
        }
    }
  
    fun getGfg(): LiveData>> {
        return gfg
    }
}

无一例外,这将返回我活动中的用户列表。假设我们向 fetchUsers()函数添加了一个异常。我们将代码更改如下:

科特林

fun gfgGfgPro() {
    viewModelScope.launch {
        gfgPro.postValue(Resource.loading(null))
        try {
            val moreGfgProFromApi = apiHelper.getGfgProWithError()
            val gfgProFromApi = apiHelper.getGfgPro()
              
            val allGfgProFromApi = mutableListOf()
            allGfgProFromApi.addAll(gfgProFromApi)
            allGfgProFromApi.addAll(moreGfgProFromApi)
  
            gfgPro.postValue(Resource.success(allGfgProFromApi))
        } catch (e: Exception) {
            gfgPro.postValue(Resource.error("Aw Snap!", null))
        }
    }
}

无一例外,这将返回我活动中的用户列表。假设我们向 fetchUsers()函数添加了一个异常。我们将代码更改如下:

科特林

fun fetchGfgPro() {
    viewModelScope.launch {
        gfgPro.postValue(Resource.loading(null))
        try {
            val moreGfgProFromApi = try {
                apiHelper.getGfgProWithError()
            } catch (e: Exception) {
                emptyList()
            }
            val gfgProFromApi = try {
                apiHelper.getGfgPro()
            } catch (e: Exception) {
                emptyList()
            }
  
            val allGfgProFromApi = mutableListOf()
            allGfgProFromApi.addAll(gfgProFromApi)
            allGfgProFromApi.addAll(moreGfgProFromApi)
  
            gfgPro.postValue(Resource.success(allGfgProFromApi))
        } catch (e: Exception) {
            gfgPro.postValue(Resource.error("Aw Snap", null))
        }
    }
}

在这种情况下,我们为两个 API 调用添加了一个单独的异常,因此如果发生异常,则会将一个空列表分配给变量并继续执行。

这是以顺序方式执行任务的示例。

2. CoroutineExceptionHandler 的使用

在前面的示例中,您可以看到我们将代码包含在 try-catch 异常中。但是,在使用协程时,我们可以使用名为 CoroutineExceptionHandler 的全局协程异常处理程序来处理异常。

科特林

class ExceptionHandlerViewModel(
    private val gfgServerAPI: GfgServerAPI,
    private val gfgHelperDB: DatabaseHelper
) : ViewModel() {
  
    private val gfgUsers = MutableLiveData>>()
  
    private val exceptionHandler = CoroutineExceptionHandler { _, exception ->
        gfgUsers.postValue(Resource.error("Aw Snap!", null))
    }
    fun fetchGfgUsers() {
        viewModelScope.launch(exceptionHandler) {
            gfgUsers.postValue(Resource.loading(null))
            val gfgUsersFromApi = gfgServerAPI.getGfgUsers()
            gfgUsers.postValue(Resource.success(gfgUsersFromApi))
        }
    }
    fun getGfgUsers(): LiveData>> {
        return gfgUsers
    }
}

注意我们这里没有使用 try-catch 块,异常将由 CoroutineExceptionHandler 处理,它充当协程的全局异常处理程序。

3. 使用 SupervisorScope

我们不希望任务的执行因为异常而终止。但是,到目前为止,我们已经看到,每当遇到异常时,我们的执行都会失败并且任务会终止。我们已经看到了如何保持任务在顺序执行中运行;在本节中,我们将看到如何保持任务在并行执行中运行。因此,在我们开始使用 supervisorScope 之前,让我们首先了解并行执行的问题。假设我们执行并行执行,例如:

科特林

private fun fetchGfgUsers() {
    viewModelScope.launch {
        gfgUsers.postValue(Resource.loading(null))
        try {
                val gfgUsersWithErrorFromGfgServerDeferred = async { gfgServerHelper.getGfgUsersWithError() }
                val moreGfgUsersFromGfgServerDeferred = async { gfgServerHelper.getMoreGfgUsers() }
                val gfgUsersWithErrorFromGfgServer = gfgUsersWithErrorFromGfgServerDeferred.await()
                val moreGfgUsersFromGfgServer = moreGfgUsersFromGfgServerDeferred.await()
                val allGfgUsersFromGfgServer = mutableListOf()
                allGfgUsersFromGfgServer.addAll(gfgUsersWithErrorFromGfgServer)
                allGfgUsersFromGfgServer.addAll(moreGfgUsersFromGfgServer)
  
                gfgUsers.postValue(Resource.success(allGfgUsersFromGfgServer))
  
        } catch (e: Exception) {
            gfgUsers.postValue(Resource.error(Aw Snap!", null))
        }
    }
}

在这种情况下调用 getUsersWithError() 时会抛出异常,导致我们的 android 应用程序崩溃并终止我们的任务执行。在这个执行中,我们在 coroutineScope 内执行并行执行,它位于 try 块内。我们会从 getUsersWithError() 收到一个异常,一旦发生异常,执行将停止,执行将继续到 catch 块。

因此,我们可以在任务中使用 supervisorScope 来克服执行失败。在 supervisorScope 中,我们有两个作业同时运行,其中一个会抛出异常但任务仍然会完成。在这种情况下,我们使用异步,它返回一个延迟,稍后将传递结果。所以,当我们使用 await() 获取结果时,我们使用 try-catch 块作为表达式,这样如果发生异常,返回一个空列表,但执行完成,我们得到一个用户列表。

结论

  1. 在不使用异步的同时,我们可以使用 try-catch 或 CoroutineExceptionHandler 来根据我们的用例实现我们想要的任何东西。
  2. 在使用 async 时,除了 try-catch 之外,我们还有两个选择:coroutineScope 和 supervisorScope。
  3. 使用异步时,如果您想在其中一个或多个任务失败时继续执行其他任务,则除了顶级 try-catch 之外,对每个任务使用 supervisorScope 和单独的 try-catch。
  4. 如果您不想继续执行其他任务(如果其中任何任务失败),请使用 coroutineScope 与顶级 try-catch 和异步。