Dan*_*n 0 12 android kotlin kotlinx.coroutines
我正在用 Kotlin for Android 组装一个简单的演示应用程序,它使用 Jsoup 检索网页的标题。我正在使用Dispatchers.Main作为上下文进行网络调用。
我的协同程序的理解是,如果我叫launch上Dispatchers.Main它并在主线程运行,但暂停执行,从而不会阻塞线程。
我的理解android.os.NetworkOnMainThreadException是它存在是因为网络操作很重,在主线程上运行时会阻塞它。
所以我的问题是,鉴于协程不会阻塞它运行的线程,它NetworkOnMainThreadException真的有效吗?下面是一些示例代码,它在 处抛出给定的异常Jsoup.connect(url).get():
class MainActivity : AppCompatActivity() {
val job = Job()
val mainScope = CoroutineScope(Dispatchers.Main + job)
// called from onCreate()
private fun printTitle() {
mainScope.launch {
val url ="https://kotlinlang.org"
val document = Jsoup.connect(url).get()
Log.d("MainActivity", document.title())
// ... update UI with title
}
}
}
Run Code Online (Sandbox Code Playgroud)
我知道我可以简单地使用Dispatchers.IO上下文运行它并将这个结果提供给主/UI 线程,但这似乎避开了协程的一些效用。
作为参考,我使用的是 Kotlin 1.3。
ian*_*ake 11
我对协程的理解是,如果我在 Dispatchers.Main 上调用 launch 它确实在主线程上运行,但会暂停执行以免阻塞线程。
挂起执行以便不阻塞线程的唯一点是标记为的方法suspend- 即挂起方法。
由于Jsoup.connect(url).get()不是挂起方法,它会阻塞当前线程。当您使用 时Dispatchers.Main,当前线程是主线程,您的网络操作直接在主线程上运行,导致NetworkOnMainThreadException.
像您的get()方法这样的阻塞工作可以通过将其包装在withContext()中来暂停,这是一种暂停方法并确保Dispatchers.Main在方法运行时不会被阻塞。
mainScope.launch {
val url ="https://kotlinlang.org"
val document = withContext(Dispatchers.IO) {
Jsoup.connect(url).get()
}
Log.d("MainActivity", document.title())
// ... update UI with title
}
Run Code Online (Sandbox Code Playgroud)
协程挂起并不是一个可以神奇地“解锁”现有阻塞网络调用的功能。它严格来说是一个协作功能,需要代码显式调用suspendCancellableCoroutine. 因为您正在使用一些预先存在的阻塞 IO API,所以协程会阻塞其调用线程。
要真正利用可挂起代码的强大功能,您必须使用非阻塞 IO API,它允许您发出请求并提供 API 在结果准备就绪时调用的回调。例如:
NonBlockingHttp.sendRequest("https://example.org/document",
onSuccess = { println("Received document $it") },
onFailure = { Log.e("Failed to fetch the document", it) }
)
Run Code Online (Sandbox Code Playgroud)
使用这种 API,无论您是否使用协程,线程都不会被阻塞。然而,与阻塞API相比,它的使用相当笨重和混乱。这就是协程可以帮助您的:它们允许您继续以与阻塞完全相同的形式编写代码,但事实并非如此。要获得它,您必须首先编写一个suspend fun将您拥有的 API 转换为协程挂起的 API:
suspend fun fetchDocument(url: String): String = suspendCancellableCoroutine { cont ->
NonBlockingHttp.sendRequest(url,
onSuccess = { cont.resume(it) },
onFailure = { cont.resumeWithException(it) }
)
}
Run Code Online (Sandbox Code Playgroud)
现在你的调用代码又回到了这里:
try {
val document = fetchDocument("https://example.org/document")
println("Received document $document")
} catch (e: Exception) {
Log.e("Failed to fetch the document", e)
}
Run Code Online (Sandbox Code Playgroud)
相反,如果您愿意保留阻塞网络 IO,这意味着您需要为每个并发网络调用一个专用线程,那么如果没有协程,您就必须使用诸如异步任务、Anko 等之类的东西。这些方法还bg需要您可以提供回调,因此协程可以再次帮助您保持自然的编程模型。核心协程库已经附带了您需要的所有部分:
Dispatchers.IO)withContext语,它允许您的协程从一个线程跳转到另一个线程,然后再返回使用这些工具您可以简单地编写
try {
val document = withContext(Dispatchers.IO) {
JSoup.connect("https://example.org/document").get()
}
println("Received document $it")
} catch (e: Exception) {
Log.e("Failed to fetch the document")
}
Run Code Online (Sandbox Code Playgroud)
当你的协程到达 JSoup 调用时,它将释放 UI 线程并在 IO 线程池中的线程上执行此行。当它解除阻塞并获取结果时,协程将跳回 UI 线程。
| 归档时间: |
|
| 查看次数: |
4369 次 |
| 最近记录: |