Android 格式数据解析

一般我们会在网络上传输一些格式化后的数据,这种数据会有一定的语法结构规则和语义,当另一方收到数据消息之后,就可以按照相同的结构规则进行解析,从而取出想要的那部分内容。随便传递一段文本肯定是不行的,因为另一方根本就不知道这段文本的用途是什么。

在网络上传输数据时最常用的格式有两种:XML 和 JSON。

为了方便测试,首先在一个服务器上提供一段 XML 文本,然后在程序里去访问它,再对得到的 XML 文本进行解析。我使用的是 Mac 系统,自带了 Apache 服务器,可在这里查看配置方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 测试文件:get_data.xml
<apps>
<app>
<id>1</id>
<name>Google Maps</name>
<version>1.0</version>
</app>
<app>
<id>2</id>
<name>Chrome</name>
<version>2.1</version>
</app>
<app>
<id>3</id>
<name>Google Play</name>
<version>2.3</version>
</app>
</apps>

解析 XML 格式数据

常用的解析 XML 格式数据的方式有两种:Pull 解析和 SAX 解析。

Pull 解析方式

示例(Kotlin):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
class  NetworkActivity : BaseActivity() {

private val tag = "TAG_NetworkActivity"

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_network)

initView()
}

private fun initView() {
btnPull.setOnClickListener{
pullXMLWithOkHttp()
}
}

private fun pullXMLWithOkHttp() {
thread {
try {
val client = OkHttpClient()
val request = Request.Builder()
// 指定访问的服务器地址是计算机本机
// 127.0.0.1 指的是计算机
// 10.0.2.2 指的是模拟器,对于模拟器来说就是计算机本机的 IP 地址
// .url("http://127.0.0.1/get_data.xml")
.url("http://10.0.2.2/get_data.xml")
.build()
val response = client.newCall(request).execute()
val responseData = response.body?.string()
if (responseData != null){
// 查看服务器返回的数据
showResponse(responseData)
// 解析服务器返回的数据
parseXMLWithPull(responseData)
}
}catch (e:Exception){
e.printStackTrace()
}
}
}

private fun parseXMLWithPull(xmlData: String) {
try {
// 创建一个 XmlPullParserFactory 实例
val factory = XmlPullParserFactory.newInstance()
// 借助 XmlPullParserFactory 实例得到 XmlPullParser 对象
val xmlPullParser = factory.newPullParser()
// 将服务器返回的 XML 数据设置进去
xmlPullParser.setInput(StringReader(xmlData))
// 开始解析
// 通过 getEventType() 可以得到当前的解析事件
var eventType = xmlPullParser.eventType
var id = ""
var name = ""
var version = ""
// 在 while 循环中不断解析
while (eventType != XmlPullParser.END_DOCUMENT){
// 得到当前节点的名字
val nodeName = xmlPullParser.name
when(eventType){
// 开始解析某个节点
XmlPullParser.START_TAG -> {
// 如果发现节点名等于 id、name 或 version,就调用 nextText() 获取节点内具体的内容。
when(nodeName){
"id" -> id = xmlPullParser.nextText()
"name" -> name = xmlPullParser.nextText()
"version" -> version = xmlPullParser.nextText()
}
}
// 完成解析某个节点
XmlPullParser.END_TAG -> {
// 每当解析完一个 app 节点,就将获取到的内容打印出来
if ("app" == nodeName){
Log.e(tag,"id is $id")
Log.e(tag,"name is $name")
Log.e(tag,"version is $version")
}
}
}
eventType = xmlPullParser.next()
}
}catch (e:Exception){
e.printStackTrace()
}
}

private fun showResponse(response: String) {
// runOnUiThread() 其实就是对异步消息处理机制进行了一层封装,内部通过 Handler 实现。
runOnUiThread{
// 在这里进行 UI 操作,将结果显示到界面上。
tvRequest.text = response
}
}
}

注:从 Android 9.0 系统开始,HTTP 类型的网络请求需要做些处理。


SAX 解析方式

SAX 解析的用法比 Pull 解析要复杂一些,但在语义方面会更加清楚。使用 SAX 解析,通常情况下会新建一个类继承自 DefaultHandler,并重写父类的 5 个方法。

示例(Kotlin):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
class MyHandler : DefaultHandler(){

private var nodeName = ""

private lateinit var id : StringBuilder

private lateinit var name : StringBuilder

private lateinit var version : StringBuilder

/**
* 开始 XML 解析时调用
*/
override fun startDocument() {
// 初始化
id = StringBuilder()
name = StringBuilder()
version = StringBuilder()
}

/**
* 开始解析某个节点时调用
* 每当开始解析某个节点时,此方法会调用。
*/
override fun startElement(uri: String, localName: String, qName: String, attributes: Attributes) {
// 记录当前节点名,localName 参数记录着当前节点的名字
nodeName = localName
Log.d("TAG","uri is $uri")
Log.d("TAG","localName is $localName")
Log.d("TAG","qName is $qName")
Log.d("TAG","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?) {
// 如果 app 节点已经解析完成
if ("app" == localName){
// 调用 trim(),避免可能包括回车或换行符。
Log.e("TAG","id is ${id.toString().trim()}")
Log.e("TAG","name is ${name.toString().trim()}")
Log.e("TAG","version is ${version.toString().trim()}")
// 最后要将 StringBuilder 清空
id.setLength(0)
name.setLength(0)
version.setLength(0)
}
}

/**
* 完成整个 XML 解析时调用
*/
override fun endDocument() {

}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
class  NetworkActivity : BaseActivity() {

private val tag = "TAG_NetworkActivity"

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_network)

initView()
}

private fun initView() {
btnSAX.setOnClickListener{
saxXMLWithOkHttp()
}
}

private fun saxXMLWithOkHttp() {
thread {
try {
val client = OkHttpClient()
val request = Request.Builder()
.url("http://10.0.2.2/get_data.xml")
.build()
val response = client.newCall(request).execute()
val responseData = response.body?.string()
if (responseData != null){
// 解析服务器返回的数据
parseXMLWithSAX(responseData)
}
}catch (e:Exception){
e.printStackTrace()
}
}
}

private fun parseXMLWithSAX(xmlData: String) {
try {
// 创建了一个 SAXParserFactory 对象
val factory = SAXParserFactory.newInstance()
// 获取 XmlReader 对象
val xmlReader = factory.newSAXParser().xmlReader
val handler = MyHandler()
// 将 MyHandler 的实例设置到 XMLReader 中
xmlReader.contentHandler = handler
// 开始执行解析
xmlReader.parse(InputSource(StringReader(xmlData)))
}catch (e:Exception){
e.printStackTrace()
}
}
}

DOM 解析


解析 JSON 格式数据

比起 XML,JSON 的主要优势在于它的体积更小,在网络上传输的时候更省流量。但缺点在于,它的语义性较差,看起来不如 XML 直观。

1
2
3
4
// 测试文件:get_data.json
[{"id":"5","version":"5.5","name":"Clash of Clans"},
{"id":"6","version":"7.0","name":"Boom Beach"},
{"id":"7","version":"3.5","name":"Clash Royale"}]

使用 JSONObject

JSONObject 是谷歌官方提供的。

示例(Kotlin):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
class  NetworkActivity : BaseActivity() {

private val tag = "TAG_NetworkActivity"

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_network)

initView()
}

private fun initView() {
btnJSONObject.setOnClickListener{
jsonObjectWithOkHttp()
}
}

private fun jsonObjectWithOkHttp() {
thread {
try {
val client = OkHttpClient()
val request = Request.Builder()
.url("http://10.0.2.2/get_data.json")
.build()
val response = client.newCall(request).execute()
val responseData = response.body?.string()
if (responseData != null){
// 解析服务器返回的数据
parseJSONWithJSONObject(responseData)
}
}catch (e:Exception){
e.printStackTrace()
}
}
}

private fun parseJSONWithJSONObject(jsonData: String) {
try {
val jsonArray = JSONArray(jsonData)
for (i in 0 until jsonArray.length()){
val jsonObject = jsonArray.getJSONObject(i)
val id = jsonObject.getString("id")
val name = jsonObject.getString("name")
val version = jsonObject.getString("version")
Log.d(tag,"id is $id")
Log.d(tag,"name is $name")
Log.d(tag,"version is $version")
}
}catch (e:Exception){
e.printStackTrace()
}
}
}

使用 GSON

GSON 是 Google 提供的开源库,可以让解析 JSON 数据的工作变得更简单。

1
2
// 首先,添加依赖。
implementation "com.google.code.gson:gson:2.8.5"

GSON 的强大之处在于可以将一段 JSON 格式的字符串自动映射成一个对象,从而不需要我们再手动编写代码进行解析了。

比如一段 JSON 格式数据:{“name”:”Tom”, “age”:20}。便可以定义一个 Person 类,并加入 name 和 age 字段,然后调用如下代码将 JSON 数据自动解析成一个 Person 对象。

1
2
val gson = Gson()
val person = gson.fromJson(jsonData, Person::class.java)

如果需要解析一段 JSON 数组格式数据:这时需要借助 TypeToken 将期望解析成的数据类型传入 fromJson()。

[{“name”:”Tom”, “age”:20}, {“name”:”Tom”, “age”:20}, {“name”:”Tom”, “age”:20}]

1
2
val typeOf = object:TypeToken<List<Person>>(){}.type
val person = gson.fromJson<List<Person>>(jsonData, typeOf)

示例(Kotlin):

1
2
3
4
/**
* 新增一个 App 类,并加入字段。
*/
class AppBean(val id: String, val name: String, val version: String)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
class  NetworkActivity : BaseActivity() {

private val tag = "TAG_NetworkActivity"

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_network)

initView()
}

private fun initView() {
btnGSON.setOnClickListener{
gsonWithOkHttp()
}
}

private fun gsonWithOkHttp() {
thread {
try {
val client = OkHttpClient()
val request = Request.Builder()
.url("http://10.0.2.2/get_data.json")
.build()
val response = client.newCall(request).execute()
val responseData = response.body?.string()
if (responseData != null){
// 解析服务器返回的数据
parseJSONWithGSON(responseData)
}
}catch (e:Exception){
e.printStackTrace()
}
}
}

private fun parseJSONWithGSON(jsonData: String) {
val gson = Gson()
val typeOf = object: TypeToken<List<AppBean>>(){}.type
val appList = gson.fromJson<List<AppBean>>(jsonData, typeOf)
for (app in appList){
Log.d(tag,"id is ${app.id}")
Log.d(tag,"name is ${app.name}")
Log.d(tag,"version is ${app.version}")
}
}
}

备注

参考资料

第一行代码(第3版)

欢迎关注微信公众号:非也缘也