Kotlin-Coroutines 备忘单

这是什么?

  • 在开始之前你必须知道主线程和后台线程之间的区别:

主线程->主线程用于处理您的 Android 应用程序中的所有这些 UI 更新。主线程也称为 UI 线程,因为它的主要工作是处理 UI 更新。

后台线程->后台线程用于处理您的 Android 应用程序中的长时间运行操作 [例如:从后端加载图像、运行任何 ML 算法、网络访问等。]。

总之,你可以这么说

后台线程将处理长时间运行的操作/任务,而主线程继续处理 UI 更新。

但是这里协程的目的是什么😒?

  • 好吧, 您可以使用协程以更好的方式处理所有这些后台任务/操作。

在进一步观看此剪辑以了解线程如何帮助任务执行之前。

  • 协程:协程是编写完全可读且可维护的异步代码的更好方法。

协程可以被想象为轻量级线程,但是几个重要的区别使得它们在现实生活中的使用与线程有很大不同。

让我们测试我们的第一个协程:

  • Note:您需要导入import kotlinx.coroutines.* 才能开始使用协程。
import kotlinx.coroutines.*

fun main()  = runBlocking { // this: CoroutineScope
    launch { // launching a new coroutine and continuing
        delay(5000L) // non-blocking delay for 5 second (default time unit is ms[milli second])
        println("Safe😷") // will be printed after delay
    }
    println("Stay") // main coroutine continues while a previous one is delayed
}
  • 运行上面的代码以获得您的第一个工作协程。

你的输出可能看起来像这样👇🏻

Stay
Safe😷

但是我们的代码是什么意思🤔?

  • launch 是协程构建器。它与其余代码同时启动一个新的协程,该协程继续独立工作。这就是为什么Stay is printed first。

  • delay是一种特殊的暂停功能。它将协程延迟特定时间[应在延迟的括号中提供特定时间(delay time in ms)]。

  • runBlocking也是一个协程构建器,它连接正则的非协程代码和大括号fun main()内的协程代码。runBlocking { … }

  • 如果您Remove or Skip runBlocking在此代码中,您将在启动调用时收到错误,因为启动仅在CoroutineScope.

现在,CoroutineScope是什么意思?

  • 作用域只是为协程提供生命周期方法,并允许我们管理协程以启动和停止它们。

In Short:scope 为协程提供生命周期方法,并允许我们启动和停止协程。

  • 每个协程构建器(如launch,async等)都是一个extension on CoroutineScope并且将其 coroutineContext 继承到, automatically generates all its elements and cancellation。

  • 我们有coroutineScope { } As WellBeside的launch和runBlocking。

但是我们如何启动Coroutines In Android🤔?

  • 在进一步Make Sure说明您已包含协程依赖项(如果未包含)之前:
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.0'
  • 我们可以通过在您的主线程或任何其他协程中分配来启动协程:GlobalScope.launch{ }
override fun onCreate(savedInstanceState: Bundle?) {
      super.onCreate(savedInstanceState)
      setContentView(R.layout.activity_main)
      GlobalScope.launch {
          //Code
      }
  }

Note:您每次都Should NOT使用GlobalScope 。

  • 让我们通过在我们的协程中分配来检查我们的协程是否工作Log:
GlobalScope.launch {
          Log.d("coroutineCheck", "Running In ${Thread.currentThread().name}")
}
  • 运行应用程序并打开 Logcat以检查结果:

  • 正如你所见Log Is Executing In Background Thread。

Note:在您的 Logcat 中,Dispatcher Worker 的数字可能不同[不要担心]。

  • 您可以从声明的协程本身启动多个协程,并且您不需要每次都在声明的范围内指定范围:

复制

 GlobalScope.launch {
            Log.d("coroutineCheck","Running In ${Thread.currentThread().name}")
            launch {
                Log.d("coroutineCheck","Running In ${Thread.currentThread().name}")
            }
            launch {
                Log.d("coroutineCheck","Running In ${Thread.currentThread().name}")
            }
        }
  • 运行应用程序并打开 Logcat以检查结果:


正如你所见Log Is Executing In Background Thread。

挂起函数

挂起函数可以执行长时间运行的操作 || 任务并将等待它完成而不阻塞整个代码。

挂起函数只能在另一个挂起函数中实现 || 协程。

delay()也是一个挂起函数,

如果你想delay()在你的代码中实现,你IDE May Show This Image在数字面板中Where You Have Implemented A Suspend Function:

此图像仅表示您正在实现暂停功能。

让我们在 Android 中构建一个暂停函数来深入了解:

  • 在创建函数之前,我们需要提到创建暂停suspend函数:
suspend fun delaying() {
      //Code
}
  • delaying()从函数返回一个字符串值:
suspend fun delaying():String{
      return "Stay Safe😷"
}
  • 让我们通过在主线程中分配来启动协程:GlobalScope.launch{ }
override fun onCreate(savedInstanceState: Bundle?) {
      super.onCreate(savedInstanceState)
      setContentView(R.layout.activity_main)
      GlobalScope.launch {
          //Code
      }
  }
  suspend fun delaying():String{
      return "Stay Safe😷"
  }
  • 现在让我们delaying()在协程中实现:
GlobalScope.launch {
delaying()
}
  • 让我们分配Log检查delaying()是否工作:
GlobalScope.launch {
            val checking=delaying()
            Log.d("suspend", "${checking} Is Running In ${Thread.currentThread().name}")
}

suspend fun delaying():String{
   delay(2000L)
   return "Stay Safe😷"
}
  • 现在运行应用程序并打开 Logcat 以检查结果:

协程上下文

  • 每个协程Launches With A Specific Context和这个Context Will Describe That In Which A Specific Thread/Coroutine Will Launch In。

  • 我们只是用来GlobalScope.launch{ }启动一个新的协程,它没有提供我们想要的完全灵活性。

  • 为了克服限制,我们有一些东西被称为Dispatchers

现在,Dispatchers是什么意思🤔?

Dispatchers 确定对应的协程使用哪个或哪些线程来执行它。

  • 所有协程构建器都喜欢launch并接受一个可选的 CoroutineContext 参数,该参数可用于显式指定新协程的调度程序。async

  • 我们有4 个调度程序用于不同的事情:

    • Dispatchers.Main

    • Dispatchers.IO

    • Dispatchers.Default

    • Dispatchers.Unconfined

  1. Dispatchers、Main: MainDispatcher 允许我们从协程控制 UI 元素,该协程将作为协程本身的主线程执行。
  • 但是我们如何实现 Main Dispatcher🤔?

  • 这很简单:

GlobalScope.launch(Dispatchers.Main) {
//Code
}
  • 在 Android Studio 中,您的代码可能如下所示👇🏼

  • 您可以使用Log检查MainDispatcher 是否在 MainDispatcher 中运行:
GlobalScope.launch(Dispatchers.Main) {
          Log.d("mainCoroutine","Running In ${Thread.currentThread().name}")
}
  • 成功启动您的应用程序后,打开 Logcat 进行澄清。

  • 正如你所看到的,它在MainDispatcher 中执行,因为我们将它声明为 Main Dispatcher。

  • 但是Control UI指定MainDispatcher🤔 后怎么办?

  • 您可以使用 Viewbinding、Synthetics、Extensions[Depriated] 或 Coroutine 中For Controlling UI的其他替代方案After Specifying Main Dispatcher。

例子:

  • 在我的 .XML 文件中,我添加了一个简单的 TextView:

  • 在.kt文件中,我指定了一块代码,它将在包括主调度器在内的协程中更改文本,以便我可以使用UI。

  • 并在成功构建后启动应用程序:


如您所见,它就像魅力一样🥳

  • 但是如果你没有指定MainDispatcher 如果你想从协程控制 UI 元素,你会在启动应用程序后在你的 Logcat 中得到这个漂亮的错误,它说:

如果您没有将其作为Main调度程序提及,则会发生此错误。因为只能从主线程控制 UI 元素。

  1. Dispatchers、IO: IODispatcher 用于控制 || Execute All Those Data Operations例如联网、在数据库中写入/添加数据、读取 || 写文件。
  • 但是我们如何实现 IO Dispatcher🤔?,很简单:
GlobalScope.launch(Dispatchers.IO) {
//Code
}
  1. Dispatchers、Default: DefaultDispatcher 可以用来Run Long Operations|| 会使主线程无响应的长任务。为避免您的应用程序无响应,您可以使用DefaultDispatcher。
  • 但是我们如何实现 Default Dispatcher🤔?,很简单:
GlobalScope.launch(Dispatchers.Default) {
//Code
}
  1. Dispatchers、Unconfined: UnconfinedDispatcher 不限于任何特定线程。换句话说,Unconfined Dispatcher 适用于既不消耗 CPU 时间也不更新任何受限于特定线程的共享数据(如 UI)的协程。
  • 但是我们如何实现无限制的 Dispatcher🤔?,很简单:
GlobalScope.launch(Dispatchers.Unconfined) {
//Code
}
  • 以防万一,如果您认为可以从同一个协程构建器(包括调度程序)启动多个协程?

答案是:

  • Yes, 这是可能的:

  • 我已经指定Log,以便我们知道,这些是否真的在协程调度程序中执行。

GlobalScope.launch {
            delay(1000L)
            launch(Dispatchers.Main) {
                Log.d("multipleLaunches", "Running In ${Thread.currentThread().name}")
            }
            delay(1000L)
            launch(Dispatchers.IO) {
                Log.d("multipleLaunches", "Running In ${Thread.currentThread().name}")
            }
            delay(1000L)
            launch(Dispatchers.Default) {
                Log.d("multipleLaunches", "Running In ${Thread.currentThread().name}")
            }
            delay(1000L)
            launch(Dispatchers.Unconfined) {
                Log.d("multipleLaunches", "Running In ${Thread.currentThread().name}")
            }
        }
  • 正如你所看到的,来自同一个协程生成器的多次启动就像 Charm 一样工作🥳:

runBlocking()

  • 你可能知道这delay()是一个将协程延迟特定时间的挂起函数。但它是Won’t Block整个线程。

  • 然而,我们知道runBlocking协程中的一些东西,当你分配它时,它会启动一个新的协程,默认情况下在主线程中启动。

  • 在进一步讨论之前,我想清除 runBlocking会阻塞整个线程但GlobalScope.launch(…){ }不会阻塞的情况。

困惑🤔?不是问题🙌,

这里有两个例子可以消除这种困惑:

1.如果如果实现delay()In GlobalScope.launch(…){…},我仍然可以从我的协程操作 UI,即使runBlocking()执行:

  • Logcat 信息:

  • 启动的活动信息+活动:

  • 正如您在 Logcat 和 Mobile 中看到的那样,它的工作方式与 Charm 一样,甚至runBlocking()在执行时也如此,这是因为我没有在runBlocking().

2.在此示例中,我将delay()在中实现runBlocking(),以便您了解runBlocking()实际的实际用途和实际用途。

  • 日志信息:

  • 上线活动信息+活动:

  • 正如您在 Logcat 和 Mobile 中看到GlobalScope.launch(…){…}的那样,主线程已被阻塞,包括(因为它也在处理主线程)一段时间,并且当该时间完成runBlocking()后立即释放该块并恢复正常状态。

我希望你的困惑runBlocking()已经消除。

  • 我们就是这样runBlocking(),但为什么有人会阻止应用程序中的 UI 更新或其他东西🤔?

  • 如果您不需要任何特定的协程行为但仍想在主线程中调用挂起函数,它可能很有用。

  • 另一个用例场景是当您使用 j-unit 进行测试以实际访问测试函数中的挂起函数时。

  • 我个人的经验runBlocking()是,您实际上可以使用协程和 runBlocking() 来检查协程中实际发生的情况。换句话说,检查协程的幕后花絮。

  • 是的,整个代码runBlocking()将与我们正常的 MainThread 流同步。

  • 正如我提到的,当你分配它时会启动一个新的协程,这意味着我们可以从Too 启动多个协程。 runBlockingrunBlocking

让我们检查一下:

  • 如您所见,我在多次启动中添加了代码,这将更改 runBlocking() 本身中的日志,并且一旦 runBlocking() 执行完成,它就会直接跳转到另一个代码块,在我们的例子中,它是GlobalScope.launch(…){…}并且应用程序将通知这些更改Logcat 和在应用程序中:
GlobalScope.launch(Dispatchers.Main) {
            Log.d("runBlocking", "Started Execution From GlobalScope")
            delay(2000L)
            textView.text = "In Global Scope"
}

runBlocking {
            launch(Dispatchers.IO) {
                Log.d("runBlocking", "Started Execution From runBlocking")
    }
            launch(Dispatchers.IO) {
                Log.d("runBlocking", "Started Execution From Another runBlocking")
    }
}
  • 并且当您尝试从 runBlocking() 实现多个协程启动时,它将同时执行所有这些启动,当您从 runBlocking() 执行来自两个启动的两个日志时,您也可以在 Logcat 中看到时序:

    协程作业

  • 每当我们启动协程时,它都会返回一个 Job。换句话说,它Returns The Work Which We Have Assigned。例如:- 加载数据、网络操作、使用 UI、一些背景资料等,

  • 这些工作 || 您在协程中分配的工作也可以保存在变量中:

val work = GlobalScope.launch(Dispatchers.Main) {
          Log.d("jobs-.join()", "Started Execution From GlobalScope Which Is In A Variable")
}
  • 在您的 Android Studio 中可能看起来像这样 || 您使用的其他 IDE:


工作:
-> 现在,如果您想主要完成这个特定变量的任务而不是其他任务,那么Jobs可以帮助您。

  • 在开始之前,我想澄清你这Jobs不是 一个函数。我们有多个暂停功能和其他代码块,可以让我们的工作变得轻松😉:

  • .join()

  • .cancel()

  • 现在,这些挂起函数和其他代码块可以通过协程构建器定义为作业。

  • 让我们深入检查一下这些功能的实际作用以及如何使用以及何时可以使用这些功能。

  • 我将使用我在上一篇博客中讨论过的runBlocking() ,以便我们了解如何以及何时可以使用这些函数。

  • runBlocking()阻塞整个线程,直到runBlocking()的执行完成。

-> 如果您不想主要执行该变量或不想执行 In runBlocking(),您也可以在协程中执行该变量:

.join():

顾名思义,我们的工作||工作可以加入一些其他的挂起函数或协程。

  • 每当我们在runBlocking().join()中有分配或其他作业时,变量的执行首先完成,然后回到我们代码的正常状态。

  • 这意味着当我们.join()将或其他作业分配给我们存储协程执行的变量时,将首先Completes First继续执行其他代码块的剩余执行。[如果你正在工作runBlocking()]

  • 如您所见,我已经使用单独的协程构建器添加了多个协程,其中我在一个名为workDemonstrate 的变量中分配了一个协程:

val work = GlobalScope.launch(Dispatchers.Main) {
            Log.d("jobs-.join()", "Started Execution From GlobalScope Which Is In A Variable")
        }
        GlobalScope.launch(Dispatchers.Default) {
            delay(2000L)
            Log.d("jobs-.join()", "Running From ${Thread.currentThread().name}")
        }
        GlobalScope.launch(Dispatchers.IO) {
            delay(2000L)
            Log.d("jobs-.join()", "Running From IO Coroutine")
        }

情况1:

  • 如果.join()执行于runBlocking():
 val work = GlobalScope.launch(Dispatchers.Main) {
            Log.d("jobs-.join()", "Started Execution From GlobalScope Which Is In A Variable")
        }
        GlobalScope.launch(Dispatchers.Main) {
            delay(2000L)
            Log.d("jobs-.join()", "Running From ${Thread.currentThread().name}")
            textView.text = "runBlocking() Execution Has Completed"
        }
        GlobalScope.launch(Dispatchers.IO) {
            delay(2000L)
            Log.d("jobs-.join()", "Running From IO Coroutine")
        }
        runBlocking {
            Log.d("jobs-.join()", "Executing From runBlocking()")
            delay(3000L)
            work.join()
            Log.d("jobs-.join()", "Executing Has Completed")
        }
  • 并确保你已经work.join()在 runBlocking() 中实现了.join()是一个挂起函数。

  • 正如您在 Logcat+Mobile Activity 中看到的那样,我们的变量首先被执行,然后其他协程开始了他们受人尊敬的工作/作业,其中变量包含在runBlocking() 中:



案例2:

  • 如果.join()执行于Coroutine:
val work = GlobalScope.launch(Dispatchers.Default) {
          Log.d("jobs-.join()", "Started Execution From GlobalScope Which Is In A Variable")
      }
      GlobalScope.launch(Dispatchers.Main) {
          delay(2000L)
          Log.d("jobs-.join()", "Running From ${Thread.currentThread().name}")
          textView.text = "Coroutine Execution Has Completed"
      }
      GlobalScope.launch(Dispatchers.IO) {
          Log.d("jobs-.join()", "Running From IO Coroutine")
          work.join()
          delay(2000L)
      }
  • 并确保你已经work.join()在协程中实现了.join()是一个挂起函数。

  • 正如您在 Logcat+Mobile Activity 中看到的那样,我们的变量与我已实现work变量的特定协程同时执行,然后其他协程也开始了他们尊重的工作/工作:


  • 并且您可以注意到在这两种情况下.join()首先执行函数。

  • 这就是.join()可以加入其他协程与该协程同时工作的全部内容|| 在 runBlocking() 中主要执行该变量。

.cancel():

顾名思义,我们的工作||工作可以取消。

  • 但是为什么有人会取消工作🤔?

  • 让我们看看这些案例:

  • 如您所见,我添加了一个repeat(…){…}块,其中我Log以 1 秒的延迟间隔添加了 5 次语句。

  • 这意味着执行将在 5 秒内完成。

val work = GlobalScope.launch(Dispatchers.Default) {
            repeat(5) {
                Log.d("jobs-.join()", "Started Execution From GlobalScope Which Is In A Variable")
                delay(1000L)
            }
        }

情况1:

  • 如果.cancel()执行于runBlocking():

  • 正如我已经提到.cancel()的将立即取消我们的工作,所以我延迟了 2 秒之后我们的工作 || 作业将被取消。

这意味着重复的块代码将被执行 2 次,然后整个工作将被取消。

val work = GlobalScope.launch(Dispatchers.Default) {
            repeat(5) {
                Log.d("jobs-.cancel()", "Started Execution From GlobalScope Which Is In A Variable")
                delay(1000L)
            }
        }
        runBlocking {
            delay(2000L)
            work.cancel()
            Log.d("jobs-.cancel()", "Work Is Cancelled")
        }
  • 并确保你已经work.cancel()在 runBlocking()中实现了.cancel()是一个挂起函数。

  • 正如您在 Logcat 中看到的那样,我们的变量被执行了 2 次,然后立即被取消:


案例2:

  • 如果.cancel()执行于Coroutine:

  • 正如我已经提到.cancel()的将立即取消我们的工作,所以我延迟了 2 秒之后我们的工作 || 作业将被取消。

这意味着重复的块代码将被执行 2 次,然后整个工作将被取消。

val work = GlobalScope.launch(Dispatchers.Default) {
            repeat(5) {
                Log.d("jobs-.cancel()", "Started Execution From GlobalScope Which Is In A Variable")
                delay(1000L)
            }
        }
        GlobalScope.launch(Dispatchers.Default) {
            delay(2000L)
            work.cancel()
            Log.d("jobs-.cancel()", "Work Is Cancelled")
        }
  • 并确保你已经work.cancel()在协程中实现了.cancel()是一个挂起函数。

  • 正如您在 Logcat 中看到的那样,我们的变量被执行了 2 次,然后立即被取消:

然而,取消协程并不容易,正如我在上面演示和取消的那样,在少数情况下,如果你没有正确处理它,它可能会导致整个代码崩溃。顺便说一句,大多数时候,你可以执行withTimeOut(…){…}而不是取消协程,这使事情变得更简单。

  • 这就是.cancel()可以取消执行的全部内容(如果您愿意)。

活动:

  • 有了.cancel()我们可以取消执行但工作 || 我们对变量执行的工作不会知道该工作 || 工作已取消,isActive()可以与if声明一起使用,如下所示:

  • 换句话说,您可以手动检查我们的协程是否处于活动状态:

val work = GlobalScope.launch(Dispatchers.Default) {
            if (isActive) {
                repeat(5) {
                    Log.d(
                        "jobs-isActive",
                        "Coroutine Is Still Active"
                    )
                    delay(1000L)
                }
            }
        }
        runBlocking {
            delay(2000L)
            work.cancel()
            Log.d("jobs-isActive","Coroutine Has Cancelled")
        }
  • 如您所见,一旦协程知道作业被取消,它将离开执行。

  • 如果你真的注意到了代码,你会注意到如果没有添加work.join但代码正在执行,因为我添加了它,这怎么可能🤔?

  • 这是因为正如我isActive在代码中添加的那样,该变量会继续工作,直到取消该特定变量为止;因为它没有被取消,这意味着它仍然可以执行。这就是即使我没有提到的原因work.join,协程也会像 Charm 一样工作 ⚡。但是一旦该变量被取消,该变量将不再处于活动状态。

withTimeOut(…){…}:

  • 如果您正在考虑“是否可以设置特定时间,并且如果该工作 || 作业没有在该特定时间内完成,则应该自动取消”而不使用 runBlocking()。

  • 答案是:

  • Yes如果那份工作有可能withTimeOut(…){…}你会给一个特定的时间|| 工作没有在特定时间内完成,它将在没有任何runBlocking()和其他东西的情况下自动取消:

GlobalScope.launch(Dispatchers.Default) {
            withTimeout(2000L) {
                repeat(5) {
                    Log.d("jobs-withTimeOut", "Coroutine Is Still Active")
                    delay(1000L)
                }
            }
        }
  • 如您所见,一旦给定时间完成,协程就会自动取消:

异步 && 等待

案例场景

  • 您可能已经知道,当您在代码中实现了相应的代码块时,它将按顺序执行。

  • 换句话说,它们将默认一个接一个地执行。

  • 但在少数情况下,您可能需要启动Execution Of Multiple Codeblocks At A TimeWhere 输出 || 结果时间可能会有所不同。

  • 在这些情况下,您需要与各自的代码块异步工作,这意味着您需要一次启动这些代码块的执行。

异步 = 一次运行多个代码块,结果 || 输出将被返回 || 稍后执行。

异步简介

  • 现在,正如我之前提到的,如果你想一次开始执行,那么你应该异步工作。

  • 在 Kotlin 中,我们有一些被称为延迟async类型的东西,我们可以从中异步构建各自的代码块。

  • async 不返回同时也是协程生成器的作业。

  • 正如您在下面看到的,它属于Deferredasync类型:


简介等待

  • 正如我上面提到的,这async将帮助您异步工作,但它也可能会停止其他执行🤷🏼‍♂️。

  • 为了避免这种情况,我们将使用await方法 with以便async剩余的代码块执行正常。

换句话说,await确保执行会更进一步,直到函数被完全执行。

用例场景

  • 如您所见,我创建了 2 个挂起函数:
private suspend fun apiCall1() {

}

private suspend fun apiCall2() {

}
  • 我将延迟 2 秒并分别在两个函数中返回一个字符串:
private suspend fun apiCall1(): String {
        delay(2000L)
        return "Html Is A Programming Language🤭"
    }

    private suspend fun apiCall2(): String {
        delay(2000L)
        return "Stay Safe😷"
    }

情况1

没有异步的代码

  • 我将通过 IO DispatcherGlobalScope使用Coroutine Builder 在 Coroutine 中实现这些挂起函数:launch
GlobalScope.launch(Dispatchers.IO) {
   Log.d("async-await", apiCall1())
   Log.d("async-await", apiCall2())
}
  • 现在,如果您在成功构建后启动应用程序,您会注意到记录我给出的那些消息需要 4 秒。

  • 这是因为在返回这些值之前apiCall1()并apiCall2()暂停函数延迟。2 Seconds Respectively

  • 如果你想测量完成那些执行所花费的时间,我们有几个课程 || 方法。在我的情况下,

  • 我将使用measureTimeMillis{…}它返回我们在该特定方法中实现的执行所花费的时间 || 毫秒格式的类。

  • 您可以measureTimeMillis{…}在变量中使用,以便获得该执行的持续时间:

val time = measureTimeMillis{
// Codeblock
}
  • 现在让我们将这些挂起函数添加到time变量中,以获得完成执行所需的时间。
GlobalScope.launch(Dispatchers.IO) {
  val time = measureTimeMillis {
              Log.d("async-await", apiCall1())
              Log.d("async-await", apiCall2())
       }
              Log.d("async-await", "Time Taken To Complete-> $time ms.")
}
  • 如您所见,完成需要 4 秒:

  • 这是因为我们不是异步执行我们的代码,所以它默认按顺序执行。

案例2

异步编码

  • 确保您已添加async{…}到异步工作。

  • 异步编码不会让您的代码块主要处理结果,它确保您的特定代码块将主要执行而不是结果|| 输出。

  • 我将在一个协程中在两个不同的变量中实现两个挂起函数以async进行演示:

GlobalScope.launch(Dispatchers.IO) {
val apiCall1 = async { apiCall1()}
val apiCall2 = async { apiCall2() }
}

现在,这是关键时刻🔥。

  • 让我们在异步工作的地方实现上面的代码块,以便我们measureTimeMillis{…}可以获得执行的持续时间。

  • 并确保您已经await()使用我们的变量实现了方法,以便剩余的代码块 [如果有的话] 执行将是正常的,没有任何问题:

GlobalScope.launch(Dispatchers.IO) {
       val apiCall1 = async { apiCall1()}
       val apiCall2 = async { apiCall2() }
       val time = measureTimeMillis {
        Log.d("async-await", apiCall1.await())
        Log.d("async-await", apiCall2.await())
}
        Log.d("async-await", "Time Taken To Complete-> $time ms.")
}
  • 如您所见,两个挂起函数是同时执行的,这意味着执行现在本身在 2 秒内完成:

  • 好吧,你可以看到它异步工作🤙🏼。

为什么不建议使用 GlobalScope

  • 当我们使用GlobalScope启动协程时,它将在顶级协程中启动,因为它是全局的,并且会一直保留到您的应用程序死掉为止。

换句话说:

  • 如果你 GlobalScope用来启动一个协程,即使你跳过了特定的活动,它也会一直存活到你的应用程序死掉 || 启动特定 Corotuine 的片段。

  • 正如您已经知道的那样,协程是轻量级的,但Will Consume Some Memory Resources在它肯定运行时仍然如此,这可能会导致您的应用程序中的内存泄漏。

解决方案

  • 您可以使用预定义的范围,例如lifecycleScope{…},如果您正在使用 ViewModel(s),您可以使用viewModelScope{…}它来启动协程并开始使用。

实际差异

让我们测试两者GlobalScope{…}并lifecycleScope{…}更好地理解

  • 我创建了两个片段并通过导航组件在两个片段之间添加了导航。

第一个片段的 XML:

第二个片段的 XML:


讲真话的时间🔥
在进一步Make Sure说明您已包含协程依赖项(如果未包含)之前:

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.0'

在 First Fragment 的 Kotlin 文件中:

在GlobalScope{…}

  • GlobalScope{…}当按下按钮时,主要协程将被启动。

  • 当按下按钮时,每次都会以第二个延迟运行无限循环。

  • 延迟 5 秒后,将启动第二个片段,如下代码所示:

  view.toSecondFragment.setOnClickListener {
       GlobalScope.launch{
         while (true) {
              delay(1000L)
              Log.d("From GlobalScope", "Global Scope Is Still Running")
     }
 }

      GlobalScope.launch(Dispatchers.Main){
         delay(5000L)
         Navigation.findNavController(view).navigate(R.id.firstFragment_to_secondFragment)
      }
}
  • 现在在成功构建后启动应用程序:

  • 正如你所看到的,即使第一个片段已经死了,我们的循环仍然继续,因为我们声明了我们的范围,GlobalScope{…}它将继续运行,直到我们的应用程序死了。

  • 这是在 Kotlin-Coroutines 中不鼓励使用 GlobalScope 的主要原因。

在lifecycleScope{…}

  • lifecycleScope{…}当按下按钮时,主要协程将被启动。

  • 当按下按钮时,每次都会以第二个延迟运行无限循环。

  • 延迟 5 秒后,将启动第二个 Fragment。

正如您viewLifecycleOwner在启动协程之前提到的代码中所见lifecycleScope{…}

  • 这是因为我正在处理片段。viewLifecycleOwner当 Fragment 有它的 UI 时添加 ( onCreateView() , onDestroyView() )这是添加到 Fragment 的整体生命周期( onCreate() , onDestroy() )。

  • 以防万一,如果您正在从事活动,则无需提及viewLifecycleOwner。

view.toSecondFragment.setOnClickListener {
            viewLifecycleOwner.lifecycleScope.launch{
                while (true) {
                    delay(1000L)
                    Log.d("From LifeCycleScope", "LifeCycleScope Is Still Running")
                }
            }
           viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main){
                delay(5000L)
                Navigation.findNavController(view).navigate(R.id.firstFragment_to_secondFragment)
            }
        }
  • 现在在成功构建后启动应用程序:

  • 如您所见,一旦片段卡住,协程执行就停止了。

同样意味着viewModelScope{…}同样,它也将执行与 相同lifecycleScope{…}。但是viewModelScope{…}当你使用 ViewModel(s) 时你会用到。

结论

  • 当您希望操作GlobalScope{…}运行直到应用程序死机时使用,如果不是,您绝对应该使用lifecycleScope{…}.

  • 如果您正在使用 ViewModel(s),您可以使用viewModelScope{…}.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值