如何在后台线程访问URL解析XML取值并在Jetpack Compose UI展示
问题原因
- 你遇到的
NetworkOnMainThreadException是因为安卓系统禁止在主线程执行网络操作,你虽然用了lifecycleScope.launch,但协程默认调度到主线程运行,网络逻辑还是跑在主线程才触发了异常。 - 给
test加suspend修饰符提示冗余,是因为你函数内部没有调用任何挂起函数,也没有指定线程调度器,suspend关键字没有实际作用。
修正方案
第一步:修改test函数
给test加上suspend修饰,内部用withContext(Dispatchers.IO)把网络和解析逻辑切到后台线程:
suspend fun test(): List<String> = withContext(Dispatchers.IO) { val names = mutableListOf<String>() val user = "User" val url = URL("https://www.boardgamegeek.com/xmlapi/collection/$user?own=1") val http: HttpURLConnection = url.openConnection() as HttpURLConnection http.doInput = true http.connect() Log.d("bgg", "first" ) var games: List<Game>? = null try { val parser = XmlPullParserHandler() val istream = http.inputStream games = parser.parse(istream) } catch (e: IOException) { e.printStackTrace() } finally { // 用完关闭连接避免资源泄漏 http.disconnect() } games?.forEach { it.name?.let { name -> names.add(name) } Log.d("bgg", "game" + it.name) } Log.d("bgg", "second" + names.toString()) names }
第二步:修改Compose逻辑,用状态持有异步返回的结果
不要在setContent里直接调用test,而是用Compose的状态容器存列表,用LaunchedEffect触发异步请求:
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MyApp { // 用state存游戏名称列表,初始为空列表 val gameNamesState = remember { mutableStateOf(emptyList<String>()) } // 进入页面时自动发起请求 LaunchedEffect(Unit) { gameNamesState.value = test() } // 把state里的列表传给UI组件,数据更新时自动刷新UI MyScreenContent(gameNamesState.value) } } }
第三步:添加网络权限
在项目的AndroidManifest.xml根标签下添加网络访问权限,否则会请求失败:
<uses-permission android:name="android.permission.INTERNET" />
关键逻辑说明
withContext(Dispatchers.IO):会把包裹的代码块调度到IO专用的后台线程池执行,不会阻塞主线程,也不会触发主线程网络的异常。LaunchedEffect:是Compose内置的协程启动API,只会在组合进入时执行一次里面的逻辑,请求完成后更新state的值,Compose会自动检测到状态变化,触发UI重组刷新列表展示。- 如果你需要点击按钮刷新数据,可以在按钮的点击回调里用
rememberCoroutineScope启动协程调用test函数更新state即可。
内容的提问来源于stack exchange,提问作者Babanel
相关产品推荐
相关产品推荐

