学习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>
View Code
复制代码

 

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()方法开始解析。

 

posted @   PeacefulGemini  阅读(551)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
回顶部
点击右上角即可分享
微信分享提示