android 全局范围与协同程序范围与生命周期范围

qaxu7uf2  于 2023-01-15  发布在  Android
关注(0)|答案(4)|浏览(137)

我已经习惯了使用AsyncTask,由于它的简单性,我对它非常了解。但是Coroutines让我很困惑。你能用一种简单的方式给我解释一下下面每一个的区别和用途吗?

  1. GlobalScope.launch(Dispatchers.IO) {}
  2. GlobalScope.launch{}
  3. CoroutineScope(Dispatchers.IO).launch{}
  4. lifecycleScope.launch(Dispatchers.IO){}
  5. lifecycleScope.launch{}
yhived7q

yhived7q1#

首先,让我们从定义开始,如果你需要一个关于协同程序和协同程序流的教程或游戏场,你可以看看我创建的这个tutorial/playground
Scope是用于启动只包含一个对象CoroutineContext的协程的对象

public interface CoroutineScope {
    /**
     * The context of this scope.
     * Context is encapsulated by the scope and used for implementation of coroutine builders that are extensions on the scope.
     * Accessing this property in general code is not recommended for any purposes except accessing the [Job] instance for advanced usages.
     *
     * By convention, should contain an instance of a [job][Job] to enforce structured concurrency.
     */
    public val coroutineContext: CoroutineContext
}

协程上下文是一组规则和配置,定义了协程将如何执行。在幕后,它是一种Map,具有一组可能的键和值。
协程上下文是不可变的,但是您可以使用加号运算符向上下文添加元素,就像向集合添加元素一样,生成一个新的上下文示例
定义协程行为的一组元素是:

  • CoroutineDispatcher -将工作分派到适当的线程。
  • 作业-控制协程的生命周期。
  • 协同程序的名称,用于调试。
  • CoroutineExceptionHandler -处理未捕获的异常
    调度程序调度程序确定应使用哪个线程池。调度程序类也是CoroutineContext,可以将其添加到CoroutineContext
    *调度程序。默认值:CPU密集型的工作,如排序大列表,做复杂的计算和类似的。JVM上的共享线程池支持它。
    *调度员。IO:联网或从文件中阅读。简而言之-任何输入和输出,如名称所述
    *调度程序。主要:用于在Android主线程或UI线程中执行UI相关事件的强制调度程序。

例如,在RecyclerView中显示列表、更新视图等。
你可以查看Android的官方文件,了解更多关于调度员的信息。
[编辑]尽管官方文件声明
这个调度程序被优化为在主线程之外执行磁盘或网络I/O。示例包括使用Room组件、阅读或写入文件以及运行任何网络操作。

Marko Topolnic的答复

IO在一个特殊的、灵活的线程池中运行协程,它只是在你被迫使用一个遗留的、阻塞的IO API来阻塞它的调用线程时作为一种解决方案而存在。
也可能是对的。

作业协同程序本身由作业表示。作业是协同程序的句柄。对于您创建的每个协同程序(通过启动或异步),它返回一个作业示例,该示例唯一标识协同程序并管理其生命周期。您还可以将作业传递给协同程序作用域以保留其生命周期的句柄。

它负责协同程序的生命周期、取消和父子关系,可以从当前协同程序的上下文中检索当前作业:作业可以经历一组状态:新建、活动、正在完成、已完成、正在取消和已取消。虽然我们无法访问状态本身,但我们可以访问作业的属性:活动、取消和完成。

CoroutineScope它被定义为一个简单的工厂函数,该函数将CoroutineContext s作为参数,以在组合的CoroutineContext周围创建 Package 器,如

public fun CoroutineScope(context: CoroutineContext): CoroutineScope =
    ContextScope(if (context[Job] != null) context else context + Job())

internal class ContextScope(context: CoroutineContext) : CoroutineScope {
    override val coroutineContext: CoroutineContext = context
    // CoroutineScope is used intentionally for user-friendly representation
    override fun toString(): String = "CoroutineScope(coroutineContext=$coroutineContext)"
}

并且如果提供的上下文还没有X1 M3 N1 X元素,则创建X1 M3 N1 X元素。
我们来看一下GlobalScope源代码

/**
 * A global [CoroutineScope] not bound to any job.
 *
 * Global scope is used to launch top-level coroutines which are operating on the whole application lifetime
 * and are not cancelled prematurely.
 * Another use of the global scope is operators running in [Dispatchers.Unconfined], which don't have any job associated with them.
 *
 * Application code usually should use an application-defined [CoroutineScope]. Using
 * [async][CoroutineScope.async] or [launch][CoroutineScope.launch]
 * on the instance of [GlobalScope] is highly discouraged.
 *
 * Usage of this interface may look like this:
 *
 * ```
 * fun ReceiveChannel<Int>.sqrt(): ReceiveChannel<Double> = GlobalScope.produce(Dispatchers.Unconfined) {
 *     for (number in this) {
 *         send(Math.sqrt(number))
 *     }
 * }
 * ```
 */
public object GlobalScope : CoroutineScope {
    /**
     * Returns [EmptyCoroutineContext].
     */
    override val coroutineContext: CoroutineContext
        get() = EmptyCoroutineContext
}

如您所见,它扩展了CoroutineScope
1-GlobalScope.launch(Dispatchers.IO) {} GlobalScope只要您的应用程序处于活动状态就处于活动状态,如果您在此范围内进行一些计数并旋转您的设备,它将继续执行任务/过程。

GlobalScope.launch(Dispatchers.IO) {}

只要您的应用处于活动状态就运行,但由于使用Dispatchers.IO,因此在IO线程中运行
2-GlobalScope.launch{}与第一个相同,但默认情况下,如果您没有任何上下文,启动使用EmptyCoroutineContext,它使用Dispatchers。默认,因此唯一的区别是线程与第一个。
3-CoroutineScope(Dispatchers.IO).launch{}这个与第一个相同,只是语法不同。
4-lifecycleScope.launch(Dispatchers.IO){}lifecycleScopeLifeCycleOwner的扩展,绑定到活动或片段的生命周期,当活动或片段被销毁时,作用域被取消。

/**
 * [CoroutineScope] tied to this [LifecycleOwner]'s [Lifecycle].
 *
 * This scope will be cancelled when the [Lifecycle] is destroyed.
 *
 * This scope is bound to
 * [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate].
 */
val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope
    get() = lifecycle.coroutineScope

您还可以将其用作

class Activity3CoroutineLifecycle : AppCompatActivity(), CoroutineScope {

    private lateinit var job: Job

    override val coroutineContext: CoroutineContext
        get() = job + Dispatchers.Main + CoroutineName("🙄 Activity Scope") + CoroutineExceptionHandler { coroutineContext, throwable ->
            println("🤬 Exception $throwable in context:$coroutineContext")
        }

    private val dataBinding by lazy {
        Activity3CoroutineLifecycleBinding.inflate(layoutInflater)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(dataBinding.root)
    
        job = Job()

        dataBinding. button.setOnClickListener {

            // This scope lives as long as Application is alive
            GlobalScope.launch {
                for (i in 0..300) {
                    println("🤪 Global Progress: $i in thread: ${Thread.currentThread().name}, scope: $this")
                    delay(300)
                }
            }

            // This scope is canceled whenever this Activity's onDestroy method is called
            launch {
                for (i in 0..300) {
                    println("😍 Activity Scope Progress: $i in thread: ${Thread.currentThread().name}, scope: $this")
                    withContext(Dispatchers.Main) {
                        dataBinding.tvResult.text = "😍 Activity Scope Progress: $i in thread: ${Thread.currentThread().name}, scope: $this"
                    }
                    delay(300)
                }
            }
        }

    }

    override fun onDestroy() {
        super.onDestroy()
        job.cancel()
    }

}
sg2wtvxw

sg2wtvxw2#

靶区; DR

1.* * 全局范围.启动(调度程序. IO):在Dispatchers.IO上启动顶级协同程序。协同程序未绑定并一直运行,直到完成或取消。通常不鼓励这样做,因为程序员必须维护对join()cancel()的引用。
1.* * 全球范围.启动
:同上,但GlobalScope在未指定时使用Dispatchers.Default。通常不建议使用。
1.* * 协同程序范围(调度程序. IO).启动**:创建一个使用Dispatchers.IO的协程作用域,除非在协程生成器中指定了调度程序,即launch
1.* * 协同程序范围(调度程序. IO).启动(调度程序.主):额外的好处是,使用与上面相同的协程作用域(如果作用域示例相同!),但是对于这个协程,用Dispatchers.Main覆盖Dispatcher.IO
1.* * 生命周期范围.启动(调度程序. IO)
:在AndroidX提供的lifecycleScope内启动协同程序。一旦生命周期失效(即用户导航离开片段),协同程序将被取消。使用Dispatchers.IO作为线程池。
1.* * 生命周期范围.启动**:同上,但如果未指定,则使用Dispatchers.Main

解释

    • 协程作用域**促进了结构化并发,因此您可以在同一个作用域中启动多个协程并取消该作用域(这反过来取消了该范围内的所有协程)。相反,GlobalScope协程类似于线程,在这里你需要保持一个对join()cancel()的引用。
    • CoroutineDispatcher**告诉协程构建器(在我们的例子中是launch {})要使用哪个线程池。
  • Dispatchers.Default-使用与CPU内核数相等的线程池。应用于CPU限制的工作负载。
  • Dispatchers.IO-使用64个线程的池。非常适合IO受限的工作负载,其中线程通常处于等待状态;可能用于网络请求或磁盘读/写。
  • Dispatchers.Main(仅限安卓系统):使用主线程来执行协程。非常适合更新UI元素。

示例

我已经写了一个小的演示片段与6个功能对应上述6个场景。如果你运行下面的片段在Android设备;打开碎片并留下碎片;你会注意到只有GlobalScope协程仍然有效。当生命周期无效时,生命周期协程会被lifecycleScope取消。另一方面,在onPause()调用时,我们会显式地取消CoroutineScope协程。

class DemoFragment : Fragment() {

    private val coroutineScope = CoroutineScope(Dispatchers.IO)

    init {
        printGlobalScopeWithIO()
        printGlobalScope()
        printCoroutineScope()
        printCoroutineScopeWithMain()
        printLifecycleScope()
        printLifecycleScopeWithIO()
    }

    override fun onPause() {
        super.onPause()
        coroutineScope.cancel()
    }

    private fun printGlobalScopeWithIO() = GlobalScope.launch(Dispatchers.IO) {
        while (isActive) {
            delay(1000)
            Log.d("CoroutineDemo", "[GlobalScope-IO] I'm alive on thread ${Thread.currentThread().name}!")
        }
    }

    private fun printGlobalScope() = GlobalScope.launch {
        while (isActive) {
            delay(1000)
            Log.d("CoroutineDemo", "[GlobalScope] I'm alive on ${Thread.currentThread().name}!")
        }
    }
    
    private fun printCoroutineScope() = coroutineScope.launch {
        while (isActive) {
            delay(1000)
            Log.d("CoroutineDemo", "[CoroutineScope] I'm alive on ${Thread.currentThread().name}!")
        }
        Log.d("CoroutineDemo", "[CoroutineScope] I'm exiting!")
    }

    private fun printCoroutineScopeWithMain() = coroutineScope.launch(Dispatchers.Main) {
        while (isActive) {
            delay(1000)
            Log.d("CoroutineDemo", "[CoroutineScope-Main] I'm alive on ${Thread.currentThread().name}!")
        }
        Log.d("CoroutineDemo", "[CoroutineScope-Main] I'm exiting!")
    }

    private fun printLifecycleScopeWithIO() = lifecycleScope.launch(Dispatchers.IO) {
        while (isActive) {
            delay(1000)
            Log.d("CoroutineDemo", "[LifecycleScope-IO] I'm alive on ${Thread.currentThread().name}!")
        }
        Log.d("CoroutineDemo", "[LifecycleScope-IO]  I'm exiting!")
    }

    private fun printLifecycleScope() = lifecycleScope.launch {
        while (isActive) {
            delay(1000)
            Log.d("CoroutineDemo", "[LifecycleScope] I'm alive on ${Thread.currentThread().name}!")
        }
        Log.d("CoroutineDemo", "[LifecycleScope] I'm exiting!")
    }

}
yhxst69z

yhxst69z3#

我会把你的清单沿着三个部分:

  1. GlobalScope对比CoroutineScope()对比lifecycleScope
  2. Dispatchers.IO与继承的(隐式)调度程序
    1.将作用域vs.中的调度程序指定为launch的参数

1.范围选择

Kotlin对协程的理解很大一部分是“结构化并发”,这意味着所有的协程都被组织成一个层次结构,遵循它们的依赖关系。如果你正在启动一些后台工作,我们假设你希望它的结果在某个时候出现,而当前的“工作单元”仍然是活动的,也就是说,用户还没有离开它,也不再关心它的结果。
在Android上,您可以使用lifecycleScope,它会自动跟随用户在UI Activity之间的导航,因此您应该将其用作后台工作的父级,用户可以看到后台工作的结果。
你也可能有一些一次性的工作,你只需要最终完成,但用户不会等待它的结果。为此,你应该使用Android的WorkManager或类似的功能,即使用户切换到另一个应用程序,也可以安全地继续。这些通常是同步你的本地状态与服务器端保持的状态的任务。
在这幅图中,GlobalScope基本上是结构化并发的一个逃生出口,它允许你满足提供作用域的形式,但是破坏了它应该实现的所有机制。GlobalScope永远不能被取消,并且它没有父对象。
编写CoroutineScope(...).launch是错误的,因为你创建了一个没有父对象的作用域对象,而父对象很快就会忘记,因此无法取消它,这类似于使用GlobalScope,但更加笨拙。

2.调度程序的选择

协程调度器决定你的协程可以在哪些线程上运行。在Android上,有三个调度器你应该关心:

  1. Main在单个GUI线程上运行所有内容,它应该是您的主要选择。
  2. IO在一个特殊的、灵活的线程池上运行协程。它仅在您被迫使用会阻塞其调用线程的旧式阻塞IO API时作为一种变通方案存在。
  3. Default也使用线程池,但大小固定,等于CPU核心数。将其用于计算密集型工作,这些工作需要的时间长到足以导致GUI出现故障(例如,图像压缩/解压缩)。

3.指定调度程序的位置

首先,您应该知道您正在使用的协程作用域中指定的调度器。GlobalScope没有指定任何调度器,因此一般默认值是Default调度器。lifecycleScope指定Main调度器。
我们已经解释过,不应该使用CoroutineScope构造函数创建ad-hoc作用域,因此指定显式调度程序的适当位置是作为launch的参数。
在技术细节上,当你写someScope.launch(someDispatcher)时,someDispatcher参数实际上是一个成熟的协程上下文对象,它碰巧有一个元素,调度器。你启动的协程通过组合协程作用域中的一个和你作为参数提供的一个为自己创建一个新的上下文。它为自己创建一个新的Job并将其添加到上下文中。该作业是在上下文中继承的作业的子作业。

omvjsjqw

omvjsjqw4#

你应该知道,如果你想启动suspend函数,你需要在CoroutineScope中执行它。每个CoroutineScope都有CoroutineContext。其中CoroutineContext是一个可以包含Dispatcher的Map(将工作分派给适当的线程),Job(控制协程的生命周期)、CoroutineExceptionHandler(处理未捕获的异常)、CoroutineName(协程的名称,用于调试)。

  1. GlobalScope.launch(Dispatchers.IO) {}-GlobalScope.launch创建全局协程,并用于不应取消的操作,但更好的替代方法是在Application类中创建一个自定义作用域,并将其注入到需要它的类中。这样做的好处是,您可以使用CoroutineExceptionHandler或替换CoroutineDispatcher进行测试。
  2. GlobalScope.launch{}-与GlobalScope.launch(Dispatchers.IO) {}相同,但在Dispatchers.Default上运行coroutinesDispatchers.Default是默认的Dispatcher,在其上下文中未指定调度程序时使用。
  3. CoroutineScope(Dispatchers.IO).launch{}-它使用一个参数创建作用域,并在IO线程上在其中启动新的coroutine。将与启动对象一起销毁。但如果要正确结束工作,应手动为CoroutineScope调用.cancel()
  4. lifecycleScope.launch(Dispatchers.IO){}-LifecycleLifecycleOwner中可用的现有范围(ActivityFragment),并在您的项目中附带依赖项androidx.lifecycle:lifecycle-runtime-ktx:*。使用它,您可以摆脱手动创建CoroutineScope。它将在Dispatchers.IO中运行您的作业,而不会阻塞MainThread,并确保当您的lifecycle被销毁时,您的作业将被取消。
  5. lifecycleScope.launch{}-与lifecycleScope.launch(Dispatchers.IO){}相同,它使用默认Dispatchers.Main参数为您创建CoroutinesScope,并在Dispatcher.Main中运行coroutines,这意味着您可以使用UI

相关问题