面试题答案
一键面试架构设计
- 结构化并发:使用
CoroutineScope
及其相关函数,如launch
和async
来创建协程,这样在外部CoroutineScope
取消时,内部创建的所有协程都会自动取消。例如,在一个服务类中定义一个CoroutineScope
,并在其init
块中启动需要的协程:
class MyService {
private val coroutineScope = CoroutineScope(Job() + Dispatchers.Default)
init {
coroutineScope.launch {
// 执行网络请求或文件读写等操作
}
}
fun stop() {
coroutineScope.cancel()
}
}
- 作用域管理:将不同功能的协程放在不同的
CoroutineScope
下管理。例如,网络请求相关协程放在一个NetworkScope
,文件读写相关协程放在FileScope
。这样可以根据需求分别取消特定功能的协程,而不影响其他部分。 - 资源管理抽象:将网络请求和文件读写等资源操作封装成独立的函数或类。在这些函数或类中,使用
try - finally
块来确保资源在协程取消或异常时正确关闭。例如,对于文件读写:
fun writeToFile(filePath: String, content: String) = withContext(Dispatchers.IO) {
File(filePath).bufferedWriter().use { writer ->
writer.write(content)
}
}
这里 use
函数会在块结束(无论是正常结束还是因异常结束)时自动关闭 BufferedWriter
。
代码实现
- 协程取消处理:
- 在执行网络请求时,使用支持取消的网络库,如
OkHttp
配合Okio
。OkHttp
的Call
对象可以通过cancel
方法取消请求。在协程中,当协程被取消时,手动取消Call
对象。例如:
- 在执行网络请求时,使用支持取消的网络库,如
import okhttp3.OkHttpClient
import okhttp3.Request
val client = OkHttpClient()
suspend fun makeNetworkRequest(url: String): String = withContext(Dispatchers.IO) {
val request = Request.Builder().url(url).build()
val call = client.newCall(request)
try {
call.execute().use { response ->
response.body?.string() ?: ""
}
} catch (e: Exception) {
if (!coroutineContext.isCancelled) {
throw e
}
""
} finally {
call.cancel()
}
}
- 对于文件读写,如前面提到的使用 `use` 函数来确保文件资源在协程取消或异常时关闭。
2. 异常处理:
- 在协程构建器(如 launch
和 async
)中使用 try - catch
块捕获异常。对于不同类型的异常,可以进行不同的处理。例如:
coroutineScope.launch {
try {
val result = makeNetworkRequest("http://example.com")
writeToFile("output.txt", result)
} catch (e: IOException) {
// 处理网络或文件读写的 I/O 异常
Log.e("MyService", "I/O error: ${e.message}")
} catch (e: Exception) {
// 处理其他未预期的异常
Log.e("MyService", "Unexpected error: ${e.message}")
}
}
- 对于 `async` 启动的协程,可以通过 `await` 来获取结果并处理可能抛出的异常:
val deferred = coroutineScope.async { makeNetworkRequest("http://example.com") }
try {
val result = deferred.await()
// 处理结果
} catch (e: Exception) {
// 处理异常
}
测试验证
- 单元测试:
- 使用
Mockk
或Mockito
等框架模拟网络请求和文件读写操作。例如,使用Mockk
模拟OkHttp
的Call
对象:
- 使用
import io.mockk.MockKAnnotations
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.impl.annotations.MockK
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runBlockingTest
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
@ExperimentalCoroutinesApi
class MyServiceTest {
@MockK
lateinit var mockCall: Call
@BeforeEach
fun setUp() {
MockKAnnotations.init(this)
}
@Test
fun `test makeNetworkRequest cancels call on cancellation`() = runBlockingTest {
coEvery { mockCall.execute() } returns mockResponse()
val request = Request.Builder().url("http://example.com").build()
val call = client.newCall(request)
makeNetworkRequest("http://example.com")
coVerify { call.cancel() }
}
private fun mockResponse(): Response = Response.Builder()
.request(Request.Builder().url("http://example.com").build())
.protocol(Protocol.HTTP_1_1)
.code(200)
.message("OK")
.body(ResponseBody.create(MediaType.parse("text/plain"), "Mock response"))
.build()
}
- 测试异常处理逻辑,确保异常被正确捕获和处理。例如,测试 `writeToFile` 函数在文件写入失败时的异常处理:
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runBlockingTest
import org.junit.jupiter.api.Test
import java.io.File
import java.io.IOException
import kotlin.test.assertFailsWith
@ExperimentalCoroutinesApi
class MyServiceTest {
@Test
fun `test writeToFile throws IOException on error`() = runBlockingTest {
assertFailsWith<IOException> {
writeToFile("/invalid/path/output.txt", "content")
}
}
}
- 集成测试:
- 测试整个协程流程,包括网络请求、文件读写以及协程取消和异常处理的交互。可以使用
TestDispatcher
来控制协程的执行和取消时机。例如:
- 测试整个协程流程,包括网络请求、文件读写以及协程取消和异常处理的交互。可以使用
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.TestCoroutineDispatcher
import kotlinx.coroutines.test.runBlockingTest
import org.junit.jupiter.api.Test
@ExperimentalCoroutinesApi
class MyServiceIntegrationTest {
private val testDispatcher = TestCoroutineDispatcher()
@Test
fun `test coroutine cancellation in complex operation`() = runBlockingTest(testDispatcher) {
val scope = CoroutineScope(testDispatcher)
val job = scope.launch {
try {
val result = makeNetworkRequest("http://example.com")
writeToFile("output.txt", result)
} catch (e: Exception) {
// 异常处理
}
}
testDispatcher.advanceUntilIdle()
job.cancel()
testDispatcher.advanceUntilIdle()
// 验证网络请求和文件操作是否正确取消或处理
}
}
通过以上架构设计、代码实现和测试验证,可以有效设计一套高效的协程取消与异常处理策略,避免资源泄漏和数据不一致等问题。