https://www.youtube.com/watch?v=CuWJBcOuNHI
学习视频
1. 在service创建自己的servicescope的记得加上supervisorJob,防止coroutine中一个任务失败了,导致其他的同一个scope的任务也一起cancel了,所以加上supervisorJob
supervisorJob()
private val serviceScope = CoroutineScope(Dispatchers.Default+ SupervisorJob())
supervisorJob 的子任务如果失败了,各自不受到影响
但是如果supervisorjob有父任务,父任务取消或者失败,那么supervisorJob和所有的子任务也会取消,如果子任务重出现了异常,除了cancellation异常之外,那么父job也会被取消
如果没有supervisorJob(),会导致serviceScope重的任务,如果有一个取消或者失败了,其他的任务也失败了,所以加上supervisorJob之后,可以保持任务之间的相对独立。
2.另外一个,看下下面 的代码:
suspend fun createFile(context:Context){
val file = File(context.filesDir,"my_text.txt")
try {
writeLines(file)
}finally {
deletefile(file)
}
}
private suspend fun writeLines(file: File){
withContext(Dispatchers.IO){
repeat(100_000){
file.appendText("this is a line")
ensureActive()//如果取消了,则抛出异常
}
}
}
private suspend fun deletefile(file: File){
withContext(Dispatchers.IO){
file.delete()
}
}
我们在activity中启动createFile,
lifecycleScope.launch {
val testJob = launch { createFile(applicationContext) }
delay(50L)
testJob.cancel()
}
我们启动 createFile 任务,任务向文件写入10万行的数据,需要耗费比较多的时间,然后50毫秒后,我们取消了测试任务,因为有writeline中有 ensureActive,即任务被取消的时候,会抛出cancellation异常,那么writeline因异常终止,然后到了finally,正常来说,finally中的 deletefile应该执行,但是deletefile在同一个launch scope,所以delete中的任务,也没有执行。
writeline异常后,Finnish中的所有suspend的方法都不再执行
为了让delete确保执行,可以加上withContext(NonCancellable)
所以在Finnaly中可以谨慎的使用
try{
}finnaly{
withContext(NonCancellable){ //确保deleteFile不会被取消
deleteFile(file)
}
}
2.异常被吃掉了
suspend fun fetchNetwork(client: HttpClient) {
while (true) {
try {
println("fetch.....start")
client.get(urlString = "https://www.baidu.com/")
println("fetch.....end")
delay(30000)
} catch (e: Exception) {
e.printStackTrace()
println("exception $e")
}
}
}
我们在viewmodel中启动循环获取
class Secrets3Viewmodel : ViewModel() {
init {
viewModelScope.launch {
fetchNetwork(HttpClient.client)
}
}
}
当界面离开,无论代码运行到delay,还是client。get,他们都有active检查,这时候因为界面退出,viewmodelscope取消,fetchNetwork抛出了cancellation excetion,,但是cancellation exception被catch被吃掉了,这个时候,coroutine没有收到取消异常,继续执行,while循环只是跳过了suspend方法,其他的打印仍在继续
导致coroutine没有收到,导致while true中的方法一直在运行
修改:
suspend fun fetchNetwork(client: HttpClient) {
while (true) {
try {
println("fetch.....start")
client.get(urlString = "https://www.baidu.com/")
println("fetch.....end")
delay(30000)
} catch (e: Exception) {
if(e is CancellationException) throw e
e.printStackTrace()
println("exception $e")
}
}
}
或者
suspend fun fetchNetwork(client: HttpClient) {
while (true) {
try {
println("fetch.....start")
client.get(urlString = "https://www.baidu.com/")
println("fetch.....end")
delay(30000)
} catch (e: Exception) {
// if(e is CancellationException) throw
coroutineContext.ensureActive() //继续抛出CancellationException
e.printStackTrace()
println("exception $e")
}
}
}
都可以继续抛出CancellationException异常,这样while循环才会退出来
4. 框架类的,定义dispatcher provider,提供一个dispatcher 的provider,便于在测试中制定 dispatcher
package com.plcoding.ktoruploadfilewithprogressbar
import android.content.Context
import android.net.Uri
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class ImageReader(private val context: Context,val dispatcherProvider: DispatcherProvider) {
suspend fun readImages(uri: Uri): ByteArray {
return withContext(dispatcherProvider.io) {
context.contentResolver.openInputStream(uri)?.use {
it.readBytes() //文件比较小的,全部读取
} ?: byteArrayOf()
}
}
}
interface DispatcherProvider {
val main: CoroutineDispatcher
val io: CoroutineDispatcher
val default: CoroutineDispatcher
}
class DefalutDispatcherProvider : DispatcherProvider {
override val main: CoroutineDispatcher
get() = Dispatchers.Main
override val io: CoroutineDispatcher
get() = Dispatchers.IO
override val default: CoroutineDispatcher
get() = Dispatchers.Default
}
class TestDispatcherProvider(private val testDispatcher: CoroutineDispatcher) : DispatcherProvider {
override val main: CoroutineDispatcher
get() = testDispatcher
override val io: CoroutineDispatcher
get() = testDispatcher
override val default: CoroutineDispatcher
get() = testDispatcher
}
5. 在读取大文件或者长时间的操作中,记得要 ensureactive确保任务没有被取消,否则ensureactive可以保证抛出cancellation exception,防止一直运行无用的任务,也可以使用yield,切换
suspend fun readImages(uri: Uri): Serializable {
return withContext(dispatcherProvider.io) {
context.contentResolver.openInputStream(uri)?.use { inputStream ->
// it.readBytes() //文件比较小的,全部读取
ByteArrayOutputStream().use { outputStream ->
var bytes = inputStream.read()
while (bytes != -1) {
outputStream.write(bytes)
bytes = inputStream.read()
//yield() 两个选一个就可以了,当任务取消了,代码到这里,就会抛出异常
// ensureActive()
}
outputStream.toByteArray()
}
} ?: byteArrayOf()
}
}