Kotlin-Coroutines 备忘单

一文掌握Kotlin:Coroutine协程从基础到实战 关于线程、协程两者的对比,可以简要叙述如下: 线程:线程由操作系统调度,线程切换或线程阻塞由操作系统和CPU调度实现; 协程:协程运行于操作系统的用户态,其依赖于线程来实现,通过用户态程序控制,尽量`减少或避免因线程阻塞造成的操作系统与CPU开销`。 与线程相比不同点在于,`协程挂起时不需要阻塞其运行的线程`。`协成挂起`期间,其对应的`线程可以被分配其他协程任务来执行`,待`该协程任务挂起结束再次开始时,将该协成再次交由某个线程来继续执行`(挂起期间,`类似于将该协程任务添加到了某个任务队列中`) 阅读详情

这是什么?

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

主线程->主线程用于处理您的 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{…}.

Kotlin 协程(Coroutines)详解 阅读详情

相关推荐

Grok Bot安卓开放预注册,用Kotlin实现AI对话客户端

AI对话助手正从网页工具演变为移动端常驻应用,这种形态迁移对开发者意味着新的技术挑战。理解流式对话在弱网下的稳定性、会话本地缓存与断线恢复机制,是构建安卓AI Bot应用的基础。基于SSE协议的大模型接口接入、Kotlin协程与StateFlow驱动的消息渲染,构成了此类应用的典型技术架构。从预注册机制看产品验证逻辑,用极简代码实现完整的对话链路,可以帮助开发者快速掌握移动端接入大模型API的核心方法。无论你的目标是参与Grok Bot预注册,还是自主开发一款AI闲聊助手,理解这套工程范式都能让你更从容地应

weixin_34405557的博客 401

Kotlin Coroutines

什么是协程,谈谈你对协程的认识 协程是一段可执行的任务,可挂起/恢复执行,概念上语言无关,通常实现是用户态的协作式调度,从具体实现上来说,可以分为两大类:有栈协程和无栈协程 有栈协程:协程切换会保存完成上下文,可以在其任意嵌套函数中被挂起 无栈协程:通过状态机维护代码运行状态,协程切换的本质是指令指针寄存器的改变 Coroutine Scope CoroutineScope public interface CoroutineScope { /** * The conte.

汪子涵的博客 926

matinal:S4 HANA 详解ACDOCA与BSEG的区别都在这了

当你在Coding Block中添加自定义字段的时候

matinal 當冬夜漸暖 。公众号:matinal 4590

Kotlin Coroutines(协程)讲解

Coroutine任何类都可以继承作为一个作用域。你需要做的唯一一件事就是重写这个属性。在此之前,你需要明确两个重要的概念dispatcher和Job。不知道你是否还记得,一个上下文(context)可以是多个上下文的组合。组合的上下文需要是不同的类型。一个dispatcher: 用于指定协程默认使用的dispatcher;一个job: 用于在任何需要的时候取消协程;操作符号用于组合上下文。如果两种不同类型的上下文相组合,会生成一个组合的上下文(),这个新的上下文会同时拥有被组合上下文的特性。

2501_90243449的博客 1451

CAS5和CAS6自定义异常提示消息

CAS自定义异常提示消息

weixin_42949219的博客 979

病理组学分析系列教程1:使用Python-histolab进行病理图像预处理,tiles切片自动生成

病理图像tiles自动生成,WSI图像预处理

qq_43449643的博客 1742

Kotlin Coroutines(协程)

Kotlin Coroutines(协程) 原文链接:https://blog.dreamtobe.cn/kotlin-coroutines/ 发表2017-08-20 在前面的文章,我们提到了kotlin的基本语法、为什么选用kotlin、根据《Effective Java》Kotlin语法层面的优化、kotlin单元测试编写以及kotlin对包大小影响、kotlin与Java对...

Android小码家 6347

STM32 ADC精准采集12V电池电压:从分压电路到软件校准全解析

本文详细解析了STM32 ADC精准采集12V电池电压的全过程,涵盖分压电路设计、电阻选型、ADC配置优化及软件校准方法。针对电池电压监测中的噪声、温漂等挑战,提供了硬件滤波与软件过采样等实战技巧,确保测量精度达到1%以内,满足嵌入式设备对电源管理的严苛要求。

tech5的博客 1015

Kotlin备忘录—简介和配置大全

Kotlin 是一门现代、简洁且安全的编程语言,由 JetBrains(做 IntelliJ IDEA 那家公司)设计开发。跟 Java 是“亲兄弟”Kotlin 能直接调用所有的 Java 代码,也能被 Java 调用。这意味着你可以用 Kotlin 写 Android 或后端程序,现有的 Java 代码库完全不用改。写得更少,做得更多同样的业务逻辑,Kotlin 的代码量通常只有 Java 的一半甚至更少。比如定义变量不用写那么多样板代码,类型推断让编译器帮你猜类型。

Tim_Cookerr的博客 244

(一篇即成功)Ubuntu下搭建qt环境和安装Qt Creator

只需一篇就成功搭建Ubuntu下的QT环境 你还在等什么

m0_73450461的博客 1万+

基于ESP32-C3的物联网硬件设计:从免费工具链到PCB打样全流程实战

在物联网和嵌入式开发领域,硬件设计是连接软件逻辑与物理世界的关键环节。其核心原理在于通过电路设计将微控制器、传感器、通信模块等电子元件有机整合,实现特定功能。掌握硬件设计能力的技术价值在于能够自主实现产品原型,优化成本与性能,并深入理解系统底层运作。常见的应用场景包括智能家居节点、环境监测设备、工业传感终端等。本文以热门的ESP32-C3 RISC-V物联网芯片和广泛使用的KiCad免费EDA工具为例,详细解析了从核心器件选型、射频电路布局、PCB设计规则到Gerber文件生产的完整流程,并分享了在嘉立创平

weixin_30918633的博客 334

Kotlin协程的迷惑

Kotlin作为谷歌强力推广的Android编程语言,是运行在jvm上的,在很多地方可以看做java语言披了一层语法糖,很多地方其实和java是相通的,包括各种库的使用。不过倒是有一个新东西:协程,这个对于java程序员来说可能就比较陌生。我也是研究了一下协程,作为一个备忘。 1. 关键概念 1.1什么是协程? 协程是轻量级的线程 这是官方文档给的定义。虽然它和线程的关系看起来很像线程和进程的...

zuguorui的专栏 999

KEPServerEX 6 之 EX 6.11.718.0 发布说明

KEPServerEX 6.11.718.0 发布说明

-雲飛兄-博客 2882

SeriesGuide:你的终极影视追踪助手,免费开源的高效追剧神器

你是否曾因错过心爱剧集的最新更新而懊恼?在影视内容爆炸的时代,如何高效管理数十部电视剧和电影的观看进度?SeriesGuide正是为解决这一痛点而生的Android应用——一款功能强大、完全免费的开源影视追踪工具,让你从此告别追剧混乱,享受井然有序的观影体验。 ## 为什么你需要SeriesGuide? 想象一下这样的场景:你同时追着5部电视剧,每周都有不同的更新日,还要关注几部期待已久的电影

gitblog_00053的博客 495

学Simulink——基于软开关与高频变换的整流器系统场景实例:移相全桥ZVS整流器在轻载条件下的效率优化

本文以Simulink为平台,详细介绍了移相全桥ZVS整流器在轻载条件下的建模与优化方法。通过六个步骤系统讲解了从项目创建到仿真运行的全过程,重点分析了移相全桥拓扑结构、ZVS检测逻辑设计及轻载特性。结果显示,传统移相全桥在轻载时ZVS失效导致效率下降(<88%)。针对此问题,提出了动态频率调整、不对称半桥模式等优化策略。该案例为理解软开关技术、提升电源效率提供了实用建模方法和优化思路。

xiaoheshang_123的博客 118

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

零基础Android开发入门:从环境搭建到第一个App

移动应用开发已成为当今技术领域的重要方向,其中Android平台凭借其开放性和设备普及率占据主导地位。开发环境搭建是入门的第一步,Android Studio作为官方IDE,集成了代码编辑、调试和性能分析工具,支持Kotlin和Java两种编程语言。通过创建包含Activity和XML布局的基础项目,开发者可以快速理解MVC架构模式。典型的功能实现如按钮点击事件处理,涉及界面设计与逻辑代码的协作,这是理解Android基础组件的最佳实践。对于初学者而言,掌握资源引用方式(R类)和基础调试技巧(Logcat)

weixin_30271335的博客 281

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

Kotlin Coroutines(协程)讲解,腾讯T2大牛亲自讲解

Coroutine最后为了帮助大家深刻理解Android相关知识点的原理以及面试相关知识,这里放上相关的我搜集整理的24套腾讯、字节跳动、阿里、百度2019-2021面试真题解析,我把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包知识脉络 + 诸多细节。还有高级架构技术进阶脑图、Android开发面试专题资料帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

2301_78398209的博客 831

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

关于Kotlin Coroutines你可能会犯的 7 个错误

在我看来,Kotlin Coroutines(协程) 大大简化了同步和异步代码。但是,我发现了许多开发者在使用协程时会犯一些通用性的错误。

金戈鐡馬 607

Kotlin学习之-6.4 Coroutines

Kotlin学习之-6.4 Coroutines 在Kotlin V1.1中Coroutines 还处在实验阶段 有些接口会做一些耗时的操作例如网络IO请求,文件IO, CPU或者GUP密集的工作等,并且要求调用者阻塞知道操作完成。Coroutines提供了一种能够替换避免阻塞线程的方法并且代价更小、控制性更好的操作:suspension of a coroutineCoroutines通过把复

farmer_cc的专栏 2951

Kotlin协程知识点概述

Kotlin 是⼀⻔仅在标准库中提供最基本底层 API 以便各种其他库能够利⽤协程的语⾔。与许多其他具有类似功能的语⾔不同,async 与 await 在 Kotlin 中并不是关键字,甚⾄都不是标准库的⼀部分。此外,Kotlin 的 挂起函数概念为异步操作提供了⽐ future 与 promise 更安全、更不易出错的抽象。kotlinx.coroutines 是由 JetBrains 开发的功能丰富的协程库。它包含本指南中涵盖的很多启⽤⾼级协程的原语,包括 launch 、async 等等。

wangyongyao1989的博客 1056

Kotlin异步流

异步流 挂起函数异步返回一个值, 而 Kotlin 的异步流返回多个值, 与 RxJava 中的流类似. 序列 (Sequences) 首先介绍一个数据类型, 序列(Sequences)表示一个延迟计算( lazily evaluated )的集合. 这里延迟即意味着异步, 或者惰性求值. 要取得它的值, 迭代它即可, 它有返回一个迭代器 (Iterator) 的方法, 所以可以在迭代器上进行各种方便的操作, 如filter,take,map等. 因为它是惰性求值的, 所以这个集合的元素可能是无限..

王温暖的博客 1464

Kotlin 协程一 —— 协程 Coroutine

Go、Python 等很多变成语言在语言层面上都实现协程,java 也有三方库实现协程,只是不常用, Kotlin 在语言层面上实现协程,对比 java, 主要还是用来解决异步任务线程切换的痛点。 协程基于线程,但相对于线程轻量很多,可理解为在用户层模拟线程操作; 每创建一个协程,都有一个内核态线程动态绑定,用户态下实现调度、切换,真正执行任务的还是内核线程。 线程的上下文切换都需要内核参与,而协程的上下文切换,完全由用户去控制,避免了大量的中断参与,减少了线程上下文切换与调度消耗的资源。

金戈鐡馬 5485

11-Kotlin高阶特性-协程

协程是轻量级的线程,由 Kotlin 运行时管理,而非操作系统。特性线程协程资源消耗较重(每个线程占用 MB 级内存)极轻(可启动数十万个)切换开销操作系统上下文切换用户态挂起/恢复,开销极小并发模型抢占式协作式(通过挂起点主动让出)核心概念:协程是轻量级并发框架,通过suspend实现非阻塞挂起,灵活切换线程,以同步写法实现异步逻辑。基本使用launch启动无返回值的协程,async返回可等待的结果,桥接普通代码。结构化并发。

weixin_36848576的博客 647

Kotlin学习笔记20——协程(Coroutines)

Kotlin中的Coroutines(协程)什么是协程?为什么要使用协程?协程创建launch方式创建runBlocking方式创建async方式创建withContext方式创建挂起函数取消和超时 什么是协程? 协程可以看成轻量级线程,通过挂起和恢复的机制进行协程任务调度,本质上是在线程上进行任务调度。而协程和线程的关系大概可以类比成线程和进程的关系。进程可以包含多个线程,而线程也能包含多个协程,但是线程执行的时候是无序的,协程则是按顺序执行。 为什么要使用协程? 一般需要使用协程的场景都可以使用协程,比

abs625的博客 857

Android 开发中如何使用Coroutines

本文全面介绍了Android平台下Kotlin协程的使用方法。主要内容包括:1) 协程基础配置和核心概念(作用域、调度器、挂起函数);2) Android内置作用域(viewModelScope/lifecycleScope)的标准用法;3) 5个核心API(launch/withContext/async等)的实战应用;4) 协程与Retrofit3的整合方案,替代RxJava3实现更简洁的异步网络请求;5) 异常处理方案和8个常见避坑要点。文章强调协程作为Android官方推荐的异步方案,相比Threa

fuge2008的博客 1379

Kotlin-协程Coroutines-基本了解

Kotlin - Coroutines

huyuchaoheaven的专栏 2181
上一篇: Flutter:如何使用 ListView.separated Widget 在 listView 中添加分隔线
下一篇: Jetpack Compose:简单的 RSS Feed Reader
不秃头de程序猿
博客等级 码龄5年 4089粉丝 1478原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值