学习Android之解析XML格式数据
解析XML格式数据
解析XML格式的数据有很多种,这里学习比较常用的两种:Pull解析和SAX解析。
比如目前在本地服务器中有如下内容的get_data.xml文件:

<apps> <app> <id>1</id> <name>shufu</name> <version>1.0</version> </app> <app> <id>2</id> <name>pangzi</name> <version>2.0</version> </app> <app> <id>3</id> <name>john</name> <version>3.0</version> </app> </apps>
Pull解析
修改MainActivity中的代码,如下所示:
private fun sendRequestWithOkHttp() { thread { try { val client = OkHttpClient() val request = Request.Builder() .url("https//10.0.2.2/get_data.xml") .build() val response = client.newCall(request).execute() val responseData = response.body?.string() if(responseData != null) { parseXMLWithPull(responseData) } } catch (e: Exception){ e.printStackTrace() } } } private fun parseXMLWithPull(xmlData: String) { try { val factory = XmlPullParserFactory.newInstance() val xmlPullParser = factory.newPullParser() xmlPullParser.setInput(StringReader(xmlData)) var eventType = xmlPullParser.eventType var id = "" var name = "" var version = "" while (eventType != XmlPullParser.END_DOCUMENT) { val nodeName = xmlPullParser.name when (eventType) { // 开始解析某个节点 XmlPullParser.START_TAG -> { when (nodeName) { "id" -> id = xmlPullParser.nextText() "name" -> name = xmlPullParser.nextText() "version" -> version = xmlPullParser.nextText() } } // 完成解析某个节点 XmlPullParser.END_TAG -> { if ("app" == nodeName) { Log.d("MainActivity", "id is $id") Log.d("MainActivity", "name is $name") Log.d("MainActivity", "version is $version") } } } eventType = xmlPullParser.next() } } catch (e: Exception) { e.printStackTrace() } }
在parseXMLWithPull()方法中,首先要创建一个XmlPullParserFactory的实例,并借助它得到XmlPullParser对象,然后调用XmlPullParser的setInput()方法将服务器返回的XML数据设置进去,然后就可以开始解析了。
解析过程:通过getEventType()可以得到当前的解析事件,然后在一个while循环中不断地进行解析,如果当前的解析事件不等于XmlPullParser.END_DOCUMENT,说明解析工作还没完成,调用next()方法后可以获取下一个解析事件。在while循环中,通过getName()方法得到当前节点的名字。如果发现节点名等于id、name或version,就调用nextText()方法来获取节点内具体的内容,每当解析完一个app节点,就将获取到的内容打印出来。
对了,从Android 9.0系统开始,应用程序默认只允许使用HTTPS类型的网络请求,HTTP类型的网络请求因为有安全隐患默认不再支持。如果需要让程序使用HTTP,还需要进行以下配置:
在res目录下新建一个Directory,名为xml,然后在xml目录下新建一个File,名为network_config.xml,编写如下代码:
<?xml version ="1.0" encoding ="utf-8"?> <network-security-config> <base-config cleartextTrafficPermitted="true"> <trust-anchors> <certificates src="system" /> </trust-anchors> </base-config> </network-security-config>
这段配置文件的意思就是允许我们以明文的方式在网络上传输数据,而HTTP使用的就是明文传输方式。
接下来修改AndroidManifest.xml中的代码来启用我们刚才创建的配置文件:
<application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.HelloWorld" android:networkSecurityConfig="@xml/network_config"> ...... </application>
SAX解析
SAX解析的用法虽然比Pull解析要复杂一些,但是在语义方面更加清楚。
使用SAX解析,一般会新建一个类继承DefaultHandler,并重写父类的5个方法,如下:
class MyHandler : DefaultHandler() { override fun startDocument() { } override fun startElement(uri: String?, localName: String?, qName: String?, attributes: Attributes?) { } override fun characters(ch: CharArray?, start: Int, length: Int) { } override fun endElement(uri: String?, localName: String?, qName: String?) { } override fun endDocument() { } }
startDocument()方法会在开始XML解析的时候调用;
startElement()方法会在开始解析某个节点的时候调用;
characters()方法会在获取节点中内容的时候调用;
endElement()方法会在完成解析某个节点的时候调用;
endDocument()方法会在完成整个XML解析的时候调用。
其中,startElement()、characters()和endElement()这三个方法是有参数的,从XML中解析出的数据会以参数的形式传入这些方法中。
注意:在获取节点中的内容时,characters()方法可能会被调用多次,一些换行符也被当作内容解析出来,需要对这种情况在代码中做好处理。
现在尝试使用SAX解析的方式完成上面Pull解析的同样功能:
class MyHandler : DefaultHandler() { private var nodeName = "" private lateinit var id: StringBuilder private lateinit var name: StringBuilder private lateinit var version: StringBuilder override fun startDocument() { id = StringBuilder() name = StringBuilder() version = StringBuilder() } override fun startElement(uri: String, localName: String, qName: String, attributes: Attributes) { // 记录当前节点名 nodeName = localName Log.d("MyHandler", "uri is $uri") Log.d("MyHandler", "localName is $localName") Log.d("MyHandler", "qName is $qName") Log.d("MyHandler", "attributes is $attributes") } override fun characters(ch: CharArray, start: Int, length: Int) { // 根据当前节点名判断将内容添加到哪一个StringBuilder对象中 when (nodeName) { "id" -> id.append(ch, start, length) "name" -> name.append(ch, start, length) "version" -> version.append(ch, start, length) } } override fun endElement(uri: String, localName: String, qName: String) { if("app" == localName) { Log.d("MyHandler", "id is ${id.toString().trim()}") Log.d("MyHandler", "name is ${name.toString().trim()}") Log.d("MyHandler", "version is ${version.toString().trim()}") // 最后要将StringBuilder清空 id.setLength(0) name.setLength(0) version.setLength(0) } } override fun endDocument() { } }
首先需要在startDocument()方法中对id、name、version节点初始化;
每当开始解析某个节点的时候,startElement()方法就会得到调用,其中localName参数记录着当前节点的名字;
接着在解析节点中具体内容的时候就会调用characters()方法,根据当前节点名进行判断,将解析出的内容添加到哪一个StringBuilder对象中;
最后在endElement()方法中进行判断,如果app节点已经解析完成,就打印出内容,然后将StringBuilder清空,以免影响下一次内容的读取。
接下来需要去修改MainActivity中的代码,如下所示:
private fun parseXMLWithSAX(xmlData: String) { try { val factory = SAXParserFactory.newInstance() val xmlReader = factory.newSAXParser().xmlReader val handler = MyHandler() // 将MyHandler的实例设置到XMLReader中 xmlReader.contentHandler = handler // 开始执行解析 xmlReader.parse(InputSource(StringReader(xmlData))) } catch (e: java.lang.Exception) { e.printStackTrace() } }
在得到了服务器返回的数据后,通过调用parseXMLWithSAX()方法来解析XML数据。
在parseXMLWithSAX()方法中,首先创建一个SAXParserFactory对象,然后再获取XMLReader对象,接着将MyHandler的实例设置到XMLReader中,最后调用parse()方法开始解析。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)