深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
如果你需要运行的某个操作的生命周期长于app进程(例如向服务器发送日志),那么,请使用WorkManager。WorkManager是用于预期在未来某个时间点执行的关键操作的库。
只要app进程还活着,那么协程就可以一直运行。对于那些需要在当前进程生命周期内有效,并且在用户杀掉app时可以取消的操作,就使用协程(例如,发起一个网络请求获取新闻列表数据)。
那些在协程中不应该取消的操作
假如,我们的应用中有一个ViewModel和一个Repository,其逻辑如下:
class MyViewModel(private val repo: Repository) : ViewModel() {
fun callRepo() {
viewModelScope.launch {
repo.doWork()
}
}
}
class Repository(private val ioDispatcher: CoroutineDispatcher) {
suspend fun doWork() {
withContext(ioDispatcher) {
doSomeOtherWork()
veryImportantOperation() // 这个操作不应该被取消,它非常重要
}
}
}
我们不希望veryImportantOperation()被viewModelScope控制,因为它可以在任何时候被取消。我们希望该操作比viewModelScope生命周期更长。我们怎么才能做到这一点?
为此,请在Application类中创建自己的Scope,并在由它启动的协程中调用这些重要的操作。哪些类需要用到该Scope,直接从Application中取就行了。
与我们稍后将看到的其他解决方案(如GlobalScope)相比,创建自己的CoroutineScope的好处是你可以根据需要对其进行配置。比如:你可以配置一个CoroutineExceptionHandler,将自己的线程池用作Dispatcher等,将所有常见的配置放在它的CoroutineContext中,非常方便。
你可以将其称为applicationScope,并且它必须包含一个SupervisorJob()以便协程中的异常不会在层次结构中传播(如本系列的第3篇文章中所示)。
class MyApplication : Application() {
// No need to cancel this scope as it'll be torn down with the process
//不需要取消该Scope,因为它会随着进程死亡而终止。
val applicationScope = CoroutineScope(SupervisorJob() + otherConfig)
}
我们不需要取消该scope,因为我们希望只要应用程序进程还活着,它就保持活跃状态,所以我们不持有对SupervisorJob的引用。我们可以使用这个scope来运行协程,这些协程通常需要一个比调用处(比如ViewModel、Activity、Fragment等)更长的生命周期。
对于不应取消的操作,请从Application中创建CoroutineScope,然后用该CoroutineScope创建协程来调用它们。
每当你创建一个新的Repository实例时,请传入我们在上面创建的applicationScope。
使用哪个协程构造器?launch or async?
根据veryImportantOperation()的行为,你需要根据需要使用launch或async启动一个新的协程:
- 如果你需要返回结果,那么使用async并调用await等待它完成
- 如果没有,请使用launch,等待它完成可以使用join。如本系列第3篇文章所示,你必须在launch中手动处理异常
下面是你将使用launch启动协程的方式:
class Repository(
private val externalScope: CoroutineScope,
private val ioDispatcher: CoroutineDispatcher
) {
suspend fun doWork() {
withContext(ioDispatcher) {
doSomeOtherWork()
externalScope.launch {
//如果这里可能会抛异常,那么请用try.catch把这里包起来,或者定义一个CoroutineExceptionHandler在externalScope的CoroutineContext中
veryImportantOperation()
}.join()
}
}
}
或者你使用async:
class Repository(
private val externalScope: CoroutineScope,
private val ioDispatcher: CoroutineDispatcher
) {
suspend fun doWork(): Any { // Use a specific type in Result
withContext(ioDispatcher) {
doSomeOtherWork()
return externalScope.async {
//调用await时会暴露异常,异常将在调用doWork的协程中传播。如果调用doWork处的协程已经cancel,把me该异常将被忽略。
veryImportantOperation()
}.await()
}
}
}
在ViewModel中用viewModelScope调用了上面的doWork后,在任何情况下,都不会影响externalScope的执行,即使viewModelScope被破坏。此外,doWork()在veryImportantOperation()完成之前不会返回,就像任何其他suspend函数调用一样。
能不能稍微简单一点?
另一种使用方式是用withContext,然后将veryImportantOperation()包在externalScope的context中:
class Repository(
private val externalScope: CoroutineScope,
private val ioDispatcher: CoroutineDispatcher
) {
suspend fun doWork() {
withContext(ioDispatcher) {
doSomeOtherWork()
withContext(externalScope.coroutineContext) {
veryImportantOperation()
}
}
}
}
然而,使用这种方式有些地方需要注意一下:
- 如果调用doWork的协程在执行veryImportantOperation()时被取消,它将一直执行到下一个退出节点,而不是在veryImportantOperation()执行完成之后
- 当在withContext中使用context时,externalScope中的CoroutineExceptionHandler就不起作用了,异常将被重新抛出
替代方案
其实还有一些其他的方式可以让我们使用协程来实现这一行为。不过,这些解决方案不是在任何条件下都能有条理地实现。下面就让我们看看一些替代方案,以及为何适用或者不适用,何时使用或者不使用它们。
❌ GlobalScope
这里有几个原因,为什么你不应该使用GlobalScope:
- 诱导我们写出硬编码值:直接使用GlobalScope可能会让我们倾向于写出硬编码的Dispatchers,这是一种很差的实践方式。
- 它使测试变得非常困难:由于你的代码将在不受控制的scope中执行,因此你将无法管理由它启动的协程的执行
- 你不能像我们对applicationScope所做的那样,为作用域中的所有协程都建立一个通用的CoroutineContext传递给GlobalScope启动所有的协程。
深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!**
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新