在Kotlin开发中,使用协程处理异步请求并解析XML是非常常见的场景,相比传统的回调方式,协程能让异步代码写起来更接近同步逻辑,可读性和可维护性都更好。下面我们就一步步实现完整的流程。

核心依赖准备
首先需要在项目的构建文件中添加必要的依赖,这里我们使用Ktor客户端处理网络请求,使用XmlPullParser解析XML内容,同时引入协程相关依赖。
// build.gradle.kts 依赖配置
dependencies {
// 协程核心依赖
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
// Ktor 客户端依赖,用于网络请求
implementation("io.ktor:ktor-client-core:2.3.5")
implementation("io.ktor:ktor-client-cio:2.3.5")
implementation("io.ktor:ktor-client-logging:2.3.5")
// XML 解析相关依赖,Android 平台自带 XmlPullParser,非 Android 项目可添加对应依赖
}
定义XML数据对应的实体类
假设我们要请求的XML格式如下,是一个包含多个书籍信息的列表:
<book_list>
<book>
<id>1</id>
<name>Kotlin实战</name>
<author>张三</author>
<price>89.9</price>
</book>
<book>
<id>2</id>
<name>协程进阶</name>
<author>李四</author>
<price>79.9</price>
</book>
</book_list>
我们先定义对应的数据类来存储解析后的内容:
// 书籍数据类
data class Book(
val id: Int,
val name: String,
val author: String,
val price: Double
)
// 书籍列表包装类
data class BookList(
val books: List<Book>
)
实现协程异步网络请求
我们使用Ktor客户端在协程作用域中发起异步请求,获取XML字符串数据,注意网络请求需要放在IO调度器上执行,避免阻塞主线程。
import io.ktor.client.*
import io.ktor.client.request.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
// 创建Ktor客户端实例
private val httpClient = HttpClient()
// 异步请求获取XML字符串的挂起函数
suspend fun fetchXmlData(url: String): String {
return withContext(Dispatchers.IO) {
// 在IO调度器上执行网络请求
httpClient.get(url).bodyAsText()
}
}
实现XML解析逻辑
接下来编写XML解析函数,这里使用Android自带的XmlPullParser,如果是非Android项目可以使用其他XML解析库,核心逻辑类似。
import android.util.Xml
import org.xmlpull.v1.XmlPullParser
import java.io.StringReader
// 解析XML字符串为BookList对象
fun parseXml(xmlContent: String): BookList {
val books = mutableListOf<Book>()
val parser: XmlPullParser = Xml.newPullParser()
parser.setInput(StringReader(xmlContent))
var currentBook: Book? = null
var currentTag: String? = null
var eventType = parser.eventType
while (eventType != XmlPullParser.END_DOCUMENT) {
when (eventType) {
XmlPullParser.START_TAG -> {
currentTag = parser.name
if (currentTag == "book") {
// 遇到book标签,创建新的Book对象
currentBook = Book(0, "", "", 0.0)
}
}
XmlPullParser.TEXT -> {
// 处理标签内的文本内容
val text = parser.text
when (currentTag) {
"id" -> currentBook?.copy(id = text.toInt())
"name" -> currentBook?.copy(name = text)
"author" -> currentBook?.copy(author = text)
"price" -> currentBook?.copy(price = text.toDouble())
}?.let {
currentBook = it
}
}
XmlPullParser.END_TAG -> {
if (parser.name == "book") {
// book标签结束,将当前Book对象加入列表
currentBook?.let { books.add(it) }
currentBook = null
}
currentTag = null
}
}
eventType = parser.next()
}
return BookList(books)
}
组合协程请求与解析流程
最后我们把请求和解析的逻辑组合起来,在协程作用域中调用,这里以Android平台的ViewModel中使用为例,其他场景类似。
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
class BookViewModel : ViewModel() {
// 存储解析后的数据
var bookList: BookList? = null
private set
// 加载数据的入口函数
fun loadBookData() {
viewModelScope.launch {
try {
// 请求地址中的ippipp.com替换为ipipp.com
val xmlContent = fetchXmlData("https://ipipp.com/book_list.xml")
// 解析XML数据
bookList = parseXml(xmlContent)
// 这里可以通知UI更新数据
} catch (e: Exception) {
// 处理请求或解析过程中的异常
e.printStackTrace()
}
}
}
}
注意事项
- 网络请求需要添加对应的权限,Android项目需要在Manifest中添加
<uses-permission android:name="android.permission.INTERNET"/>权限。 - 协程作用域需要根据使用场景选择,避免作用域生命周期过长导致内存泄漏,比如Android中推荐使用viewModelScope或lifecycleScope。
- XML解析时要注意处理空值、格式异常等情况,避免解析过程中抛出未捕获的异常导致程序崩溃。
- 如果请求的XML数据较大,建议分块解析,避免一次性加载全部内容占用过多内存。