多线程和回调入门指南
使用集合让一切井井有条
根据您的偏好保存内容并对其进行分类。
“使用 Kotlin 开发 Android 应用”课程假定您熟悉多线程的概念和术语。本页用于对此前的学习内容进行简要说明和回顾。
移动设备具有处理器,而现在大多数设备都具有多个硬件处理器,各个处理器同时运行进程。我们称之为“多进程”。
为了更高效地使用处理器,操作系统可以使应用在一个进程内创建多个执行线程。我们称之为“多线程”。

您可以把这想象成同时阅读多本图书,每阅读完一章节后就切换到另一本,最后读完所有图书,但您不可能在同一时刻阅读多本图书。
管理所有这些线程需要一些基础架构。

调度程序会考虑优先顺序等问题,并确保所有线程都能够运行并完成。它不会允许任何一本书永远放在书架上攒灰尘。但是,如果某本书的内容过长,或者可以放到后面阅读,则可能要过一段时间才会送到您手中。
调度程序会设置线程,也就是说,它会向您发送需要阅读的图书,并指定进行阅读的上下文。您可以将上下文想象成一个独立的专用阅读室。有些上下文最适合界面操作,而有些上下文则专门用于处理输入/输出操作。
唯一需要知道的是,面向用户的应用通常有一个在前台运行的主线程。主线程可以调度在后台运行的其他线程。
在 Android 中,主线程是一个处理所有界面更新的线程。此主线程(也称为界面线程)也是调用所有点击处理程序以及其他界面和生命周期回调的线程。界面线程是默认线程。除非应用显式切换线程或使用在其他线程上运行的类,否则应用的所有操作都在主线程上执行。
这会带来潜在的挑战。界面线程必须顺畅运行才能确保出色的用户体验。为了避免用户在使用您的应用时感觉到任何卡顿,主线程必须每 16 毫秒或更频繁地更新界面,或者以大约每秒 60 帧的频率更新界面。在这种速度下,人类感受到的帧变化是完全流畅的。要达到这种效果,就需要在很短的时间内处理大量的帧。因此,在 Android 上,避免阻塞界面线程是非常必要的。在这种情况下,阻塞意味着界面线程在等待数据库更新等操作完成时完全不执行任何操作。

许多常见任务耗时超过 16 毫秒,例如从互联网获取数据、读取大型文件或向数据库写入数据。因此,通过调用代码来执行与主线程类似的任务可能会导致应用暂停、卡顿甚至冻结。如果您阻塞主线程太久,应用甚至可能会崩溃并显示一个“应用无响应”(ANR) 对话框。
回调
对于从主线程之外完成工作,您有几种选择。
在不阻塞主线程的情况下执行长时间运行的任务的一种模式是回调。通过使用回调,您可以在后台线程上启动长时间运行的任务。任务完成后,系统会调用回调(以参数形式提供),以便将结果告知主线程上的代码。
回调是一种很好的模式,但也存在缺点。过多使用回调的代码可能会变得难以读取和推演。因为虽然代码看起来很有序,但回调代码会在未来某个时间异步运行。此外,回调也不允许使用某些语言功能,例如异常。
协程
在 Kotlin 中,协程是用于顺畅、高效地处理长时间运行的任务的解决方案。Kotlin 协程使您能够将基于回调的代码转换为依序代码。顺序编写的代码通常更易于阅读,甚至可以使用异常等语言功能。最后,协程和回调执行的是完全相同的操作:等待长时间运行的任务获得结果,然后继续执行任务。
本页面上的内容和代码示例受内容许可部分所述许可的限制。Java 和 OpenJDK 是 Oracle 和/或其关联公司的注册商标。
最后更新时间 (UTC):2025-08-19。
[null,null,["最后更新时间 (UTC):2025-08-19。"],[],[],null,["# Multi-threading & callbacks primer\n\nThe\n[Developing Android Apps in Kotlin course](https://codelabs.developers.google.com/codelabs/kotlin-android-training-welcome/index.html?index=..%2F..index#0)\nassumes that you are familiar with the concept and terminology of\nmulti-threading. This page is a high-level introduction and refresher.\n\nMobile devices have processors, and these days, most devices have multiple\nhardware processors that each run processes concurrently. This is called\n*multiprocessing*.\n\nTo use processors more efficiently, the operating system can enable an\napplication to create more than one thread of execution within a process. This\nis called *multi-threading*.\n\nYou can think of it as reading multiple books at the same time, switching\nbetween books after each chapter, eventually finishing all books, but you can't\nread more than one book at the exact same time.\n\nIt takes a bit of infrastructure to manage all those threads.\n\nThe *scheduler* takes into account things such as priorities, and makes sure all\nthe threads get to run and finish. No book is allowed to sit in the shelf\nforever and gather dust, but if a book is very long, or can wait, it may take a\nwhile before it gets sent your way.\n\nThe *Dispatcher* sets up threads, that is, it sends you books that you need to\nread, and specifies a **context** for that to happen in. You can think of the\ncontext as a separate, specialized reading room. Some contexts are best for user\ninterface operations, and some are specialized to deal with input/output\noperations.\n\nThe only other thing to know is that a user-facing application usually has a\n*main thread* that runs in the foreground and can dispatch other threads that\nmay run in the background.\n\nOn Android, the main thread is a single thread that handles all updates to the\nUI. This main thread, also called the *UI thread*, is also the thread that calls\nall click handlers and other UI and lifecycle callbacks. The UI thread is the\ndefault thread. Unless your app explicitly switch threads or uses a class that\nruns on a different thread, everything your app does is on the main thread.\n\nThis creates a potential challenge. The UI thread has to run smoothly to\nguarantee a great user experience. For your app to display to the user without\nany visible pauses, the main thread has to update the screen every 16 ms or more\noften, or at about 60 frames per second. At this speed, humans perceive the\nchange of frames as completely smooth. That's a lot of frames and little time.\nTherefore, on Android it's essential to avoid blocking the UI thread. *Blocking*\nin this context means the UI thread is not doing anything at all while it waits\nfor something like a database to finish updating.\n\nMany common tasks take longer than 16 milliseconds, such as fetching data from\nthe internet, reading a large file, or writing data to a database. Therefore,\ncalling code to perform tasks like those from the main thread can cause the app\nto pause, stutter, or even freeze. And if you block the main thread for too\nlong, the app may even crash and present an \"application not responding\" (ANR)\ndialog.\n\nCallbacks\n---------\n\nYou have several options for how to get work done off of from the main thread.\n\nOne pattern for performing long-running tasks without blocking the main thread\nis *[callbacks](https://en.wikipedia.org/wiki/Callback_(computer_programming))*.\nBy using callbacks, you can start long-running tasks on a background thread.\nWhen the task completes, the callback, supplied as an argument, is called to\ninform your code of the result on the main thread.\n\nCallbacks are a great pattern, but they have a few drawbacks. Code that heavily\nuses callbacks can become hard to read and harder to reason about. Because while\nthe code looks sequential, the callback code will run at some asynchronous time\nin the future. In addition, callbacks don't allow the use of some language\nfeatures, such as exceptions.\n\nCoroutines\n----------\n\nIn Kotlin,\n*[coroutines](https://kotlinlang.org/docs/reference/coroutines-overview.html)*\nare the solution for handling long-running tasks elegantly and efficiently.\nKotlin coroutines let you convert callback-based code to sequential code. Code\nwritten sequentially is typically easier to read, and can even use language\nfeatures such as exceptions. In the end, coroutines and callbacks do exactly the\nsame thing: wait until a result is available from a long-running task and\ncontinue execution."]]