现在 Retrofit 和Room 现在都已经支持协程。但我们知道网络请求和数据库操作都是耗时的,那是不是我们在每次调用 Retrofit 发起网络请求或 Room 调用数据库的 suspend 函数时都要切换到后台线程,以免阻塞 UI 线程,造成 UI 冻结并ANR。
答案是不需要
,我们不需要在后台线程调用 Retrofit 和 Room 的挂起函数,这片文章将为你解释原因。
每一个 Android 程序猿可能都经历过,在写网络请求时抛出 NetworkOnMainThreadException
异常而导致程序奔溃;在主线程执行耗时操作(如数据库调用),造成界面卡顿甚至 ANR。从而养成了一个基本素养:不能在主线程执行耗时操作
。
近年来,随着RxJava等异步库等流行,执行网络请求或数据库操最常见的方式是使用 RxJava。但是,在执行不应该在主线程上执行的操作时,还是需要来明确指定处理线程。
所以我们习惯了在执行IO操作事关心线程,以至于我们认为在协程中也必须关心它,但是为什么不需要呢?
因为,Kotlin 协程(在这篇博文中定义)的约定之一就是:
挂起函数不会阻塞调用者线程。
所以suspend
函数的维护者,在我们的例子中是 Retrofit 或 Room 库,必须确保它没有阻塞调用它的线程。每当库执行阻塞操作时,必须有一些内部机制切换到后台线程。
不过要注意:这只是一个约定,并不能保证 suspen 函数实际上不会阻塞调用线程。关于协程的一个常见误解是,仅仅通过定义一个suspend
函数就能使其自动非阻塞。这是错误的,只需在suspend
函数中执行一个Thread.sleep()或一个耗时的计算,您就会看到它照样阻塞了调用者线程。
幸运的是,Retrofit 和 Room 符合这个约定。因此,我们在主线程上调用它们的suspend
函数是安全的,因为执行 IO 操作时,主线程没有被挂起函数阻塞线程。这个概念有时被称为“main-safe”。但是,如果您使用其他库,则必须验证它们是否符合这个约定。