首页 > 解决方案 > 如何在后台线程中打开 URL,以便我可以解析 XML 并获取值

问题描述

我想用 XmlPullParser 从 URL 解析 XML,然后在 UI 上显示结果(目前正在学习使用 Compose)。

我掌握了 XmlPullParser 的基础知识,但我不明白如何在另一个线程中访问 URL 并取回值。我正在努力理解协程并失败了。

主要活动

import ....

class MainActivity : ComponentActivity() {


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyApp {
                MyScreenContent(test())
            }
        }

    }
       //I want this to run async and return the parsed results
//................................................................................
    fun test():List<String> {
        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()
            }

            games?.forEach { it ->
                it.name?.let { it1 -> names.add(it1) }
                Log.d("bgg", "game" + it.name)
            }


            Log.d("bgg", "second" + names.toString())
        return names
        }




    @Composable
    fun MyApp(content: @Composable () -> Unit) {
        SecontComposeTutorialTheme {
            Surface(color = Color.Yellow) {
                content()
            }

        }
    }


    @Composable
    fun Greeting(name: String) {

        Text(text = name, modifier = Modifier.padding(all = 24.dp))
    }

    @Composable
    fun NameList(names: List<String>, modifier: Modifier = Modifier) {
        LazyColumn(modifier = modifier) {
            items(items = names) { name ->
                Greeting(name = name)
                Divider(color = Color.Black)
            }

        }
    }

    @Composable
    fun MyScreenContent(names: List<String>) {
        val counterState = remember { mutableStateOf(0) }

        Column(modifier = Modifier.fillMaxHeight()) {
            NameList(names = names, Modifier.weight(1f))

            Counter(
                count = counterState.value,
                updateCount = { newCount ->
                    counterState.value = newCount
                }
            )
        }
    }

    @Composable
    fun Counter(count: Int, updateCount: (Int) -> Unit) {
        Button(
            onClick = {
                updateCount(count + 1)
                getBggGame()
            },
            colors = ButtonDefaults.buttonColors(
                backgroundColor = if (count > 5) Color.Green else Color.White
            )
        ) {
            Text(text = "I've beek clicked on $count times")
        }
    }

   

    @Preview(showBackground = true)
    @Composable
    fun DefaultPreview() {
        MyApp {
            //  MyScreenContent()
        }
    }
}

XmlPullParserHandler(工作)


import android.util.Log
import org.xmlpull.v1.XmlPullParserException
import org.xmlpull.v1.XmlPullParser
import org.xmlpull.v1.XmlPullParserFactory
import java.io.IOException
import java.io.InputStream
import java.net.HttpURLConnection


class XmlPullParserHandler {
    private val collection = ArrayList<Game>()
    private var game: Game? = null
    private var text: String? = null

    fun parse(inputStream: InputStream): List<Game> {
        try {
            val factory = XmlPullParserFactory.newInstance()
            factory.isNamespaceAware = true
            val parser = factory.newPullParser()
            parser.setInput(inputStream, null)
            var eventType = parser.eventType
            while (eventType != XmlPullParser.END_DOCUMENT) {
                val tagname = parser.name


                when (eventType) {
                    XmlPullParser.START_TAG -> if (tagname.equals("item", ignoreCase = true)) {
                        // create a new instance of game
                        game = Game()
                }
                    XmlPullParser.TEXT -> text = parser.text
                    XmlPullParser.END_TAG -> if (tagname.equals("item", ignoreCase = true)) {
                        // add game object to list
                        game?.let { collection.add(it) }
                    } else if (tagname.equals("id", ignoreCase = true)) {
                        game!!.id = Integer.parseInt(text)
                    } else if (tagname.equals("name", ignoreCase = true)) {
                        game!!.name = text
                    }
                    else -> {
                    }
                }
                eventType = parser.next()
            }


        } catch (e: XmlPullParserException) {
            e.printStackTrace()
        } catch (e: IOException) {
            e.printStackTrace()
        }
        return collection
    }
}

更新.............................................

如果我使用以下内容,我会得到“android.os.NetworkOnMainThreadException”

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        lifecycleScope.launch {
            setContent {
                MyApp {
                    MyScreenContent(test())
                }
            }
        }

    }

    fun test():List<String> {
        val names = mutableListOf<String>()
        val user = "Uset"
        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()
            }

            games?.forEach { it ->
                it.name?.let { it1 -> names.add(it1) }
                Log.d("bgg", "game" + it.name)
            }


            Log.d("bgg", "second" + names.toString())
        return names
        }
....

如果我用它标记测试函数suspend告诉我它的冗余,并且我不能从非挂起或协程函数调用它

标签: androidkotlinkotlin-coroutines

解决方案


您必须更改为挂起并在协程范围内调用它

suspend fun test():List<String> {...
       setContent {
            MyApp {
                lifeCycleScope.launch {
                    val list = test()
                    MyScreenContent(list)
                }
            }
        }

您正在使用 Jetpack compose,所以我不确定您是否可以使用它lifeCycleScope,但请尝试移动它:

lifeCycleScope.launch {
    setContent {...
}

否则问题可能是设计错误。


推荐阅读