这一节主要讲如何在手机端使用HTTP协议和服务器端进行网络交互,并对服务器返回的数据进行解析,这也是Android中最常用的网络技术。
一、WebView的用法
有时候我们可能会碰到比较特殊的需求,比如说要求在应用程序里展示一些网页,相信每个人都知道,加载和显示网页通常都是浏览器的任务,但是需求里又明确指出,不允许打开系统浏览器,而我们当然也不可能自己去编写一个浏览器出来,这时候该怎么办呢?为了解决这个问题,Android为我们提供了一个WebView控件,借助它我们就可以在自己的应用程序里嵌入一个浏览器,从而非常轻松的展示各种各样的网页。
WebView的用法也很简单,下面就通过一个例子来学习一下:
第一步:新建一个WebViewTest项目,在布局文件中使用:WebView控件,用来显示网页,并充满这个屏幕。
第二步:在MainActivity中实现打开网页的功能
第三步:访问网络是需要声明权限的,因此,在AndroidManifest文件中加入权限声明
第四步:将手机先连接上网络,然后运行程序,可以看到,不仅将百度的首页展示了出来,还可以通过点击链接浏览更多的网页。
这里先简单介绍了一下WebView的用法,只是希望能对HTTP协议的使用有一个最基本的认识,关于它的一些更高级的使用技巧,就不多说了,接下来我们就要利用这个协议来做一些真正的网络开发工作了。
二、使用HTTP协议访问网络
HTTP协议的工作原理特别简单,就是客户端向服务器发出一条HTTP请求,服务器收到请求之后会返回一些数据给客户端,然后客户端再对这些数据进行解析和处理就可以了。是不是很简单,一个浏览器的基本工作原理就是如此的。比如上面我们使用的WebView控件,其实也就是我们向百度的服务器发起了一条HTTP请求,接着服务器分许出我们想要访问的是百度的首页,于是会把该网页的HTML代码进行返回,然后WebView再调用手机浏览器的内核对返回的HTML代码进行解析,最终将画面展示出来。
简单的说,WebView已经在后台帮我们处理好了发送HTTP请求、接收服务响应、解析返回数据,以及最终的页面展示这几步工作,不过由于它封装得实在太好了,反而使得我们不能那么直接地看出HTTP协议到底是如何工作的。因此接下来我们通过手动发送HTTP请求的方式,来更加深入地理解一下这个过程。
2.1、使用HttpURLConnection
在过去,Android上发送HTTP请求一般有两种方式:HttpURLConnection和HttpClient。不过由于HttpClient存在的API数量过多、扩展困难等缺点,Android团队越来越不建议我们使用这个方式,终于在Android6.0系统中,HttpClient的功能被完全移除了,标志着此功能被正式弃用,因此本节我们就学习一下现在官方建议使用的HttpURLConnection的用法。
首先需要获取到:HttpURLConnection的实例,一般只需要new出一个URL对象,并传入目标的网络地址,然后调用一下:openConnection()方法即可:
在得到了HttpURLConnection的实例之后,我们可以设置一下HTTP请求所使用的方法,常用的方法主要有两个:GET和POST。GET表示希望从服务器那里获取数据,而POST则表示希望提交数据给服务器。写法如下:
接下来就可以进行一些自由的定制了,比如设置连接超时,读取超时的毫秒数,以及服务器希望得到的一些消息头等。这部分内容根据自己的实际情况进行编写,示例写法如下:
之后再调用:getInputStream()方法就可以获取到服务器返回的输入流了,剩下的任务就是对输入流进行读取,如下所示:
最后可以调用:disconnect()方法将这个HTTP连接关闭掉,如下所示:
接下来我们就通过具体的例子来真正体验一下HttpURLConnection的用法:
第一步:新建一个NetworkTest项目,修改activity_main.xml中的代码:
这里我们使用了一个新的控件:ScrollView,它是用来做什么的呢?由于手机屏幕的空间一般都比较小,有些时候过多的内容一屏是显示不下的,借助ScrollView控件的话,我们就可以以滚动的形式查看屏幕外的那部分内容,另外,布局之后还放置了一个Button和一个TextView,Button用于发送HTTP请求,TextView用于将服务器返回的数据显示出来。
第二步:修改MainActivity中的代码:
代码分析:
可以看到,在Send Request按钮的点击事件里调用了:sendRequestWithHttpURLConnection()方法,在这个方法中先是开启了一个子线程,然后在子线程里使用HttpURLConnection发出一条HTTP请求,请求的目标地址就是百度的首页。
接着利用BufferReader对服务器返回的流进行读取,将结果传入到了:showResponse()方法中,而在:showResponse()方法里则是调用了一个:runOnUiThread()方法,然后在这个方法的匿名类参数中进行操作,将返回的数据显示在界面上。那么这里为什么要使用这个:runOnUiThread()方法呢?这是因为Android是不允许在子线程中进行UI操作的,我们需要通过这个方法将线程切换到主线程,然后再更新UI元素,关于这部分内容,我们将会在下一章中进行详细讲解,现在只需要记得必须要这么写就可以了。
第三步:声明网络权限:
第四步:运行程序,点击按钮:
分析:
从上面看出:服务器返回给我们的是这种HTML代码,只是通常情况下浏览器都会将这些代码解析成漂亮的网页后再展示出来。
注意:这里网址是:https而不是http,如果是http,会提示:No Network Security Config specified, using platform default,并且点击按钮过后,也不会显示出HTML代码。
如果想要提交数据给服务器应该怎么办?其实并不复杂,只需要将HTTP请求的方法改成POST,并在获取输入流之前把要提交的数据写出即可。注意每条数据都要以键值对的形式存在,数据与数据之间用“&”符号隔开,比如说我们想要向服务器提交用户名和密码,就可以写成:
2.2、使用OKHttp
我们并不是只能使用HttpURLConnection,在开源盛行的今天,有许多出色的网络通信库都可以替代原生的HttpURLConnection,而OKHttp就是做得最出色的一个。
OKHttp是由大名鼎鼎的Square公司开发的,这个公司在开源事业上面贡献良多,除了OKHttp之外,还开发了像:Picasso、Retrofit等著名的开源项目。OKHttp不仅在接口封装上面做的简单易用,就连在底层实现上也是自成一派,比起原生的HttpURLConnection,可以说是有过之而无不及,现在已经成了广大Android开发者首选的网络通信库。OkHttp的项目主页是:https://github.com/square/okhttp。
在使用OkHttp之前,我们需要先在项目中添加OkHttp库的依赖,编辑:app/build.gradle文件,在dependencies闭包中添加如下内容:
添加上述依赖会自动下载两个库,一个是OkHttp库,一个是Okio库,后者是前者的通信基础,其中3.12.1是版本号,可以在OkHttp项目主页查看当前最新的版本是多少。
下面来看看OKHttp的具体用法:
接下来先把NetworkTest这个项目改用OkHttp的方式来实现一遍:
布局不用改动,修改MainActivity中的代码:
这里我们没有做太多的改动,只是添加了一个:sendRequestWithOkHttp()方法,并在Send Request按钮的点击事件里去调用这个方法,在这个方法中同样还是先开启了一个子线程,然后在子线程里使用OkHttp发出一条HTTP请求,请求的目标地址还是百度的首页,OkHttp的用法也正如前面所介绍的一样,最后仍然还是调用了:showResponse()方法来将服务器返回的数据显示在界面上。
最后运行程序,点击按钮,效果与前面是一样的。
三、解析XML格式数据
通常情况下,每个需要访问网络的应用程序都会有一个自己的服务器,我们可以向服务器提交数据,也可以向服务器获取数据,不过这个时候就出现了一个问题,这些数据到底以什么样的格式在网络上传输的呢?随便传递一段文本肯定是不行的,因为另一方根本不知道这段文本的用途是什么,因此,一般我们都会在网络上传输一些格式化后的数据,这种数据会有一定的结构规格和语义,当另一方收到数据消息之后就可以按照相同的结构规格进行解析,从而取出他想要的那部分内容。
在网络上传输数据时最常用的格式有两种:XML和JSON,下面我们就来一个一个地进行学习,本节首先学习一下如何解析XML格式的数据。
在开始之前,我们还需要先解决一个问题,就是从哪儿才能获取一段XML格式的数据呢?在这里我们搭建一个简单的Web服务器,在这个服务器上提供一段XML文本,然后我们在程序里去访问这个服务器,再对得到的XML文本进行解析。
搭建Web服务器其实非常简单,有很多的服务器类型可供选择,这里准备使用Apache服务器,关于Apache服务器的下载和安装,请详看https://www.cnblogs.com/hh8888-log/p/10277781.html,这里就不细说了。
启动Apache服务器后,接下来进入到Apache目录下的htdocs目录下,在这里新建一个名为:get_data.xml的文件,然后编辑这个文件,并加入如下XML内容:
然后在浏览器中访问:http://127.0.0.1/get_data.xml这个网址,就会显示出以下内容:
到此为止,准备工作就结束了,接下来我们在Android程序里去获取并解析这段XML数据。
3.1、Pull解析方式
解析XML格式的数据很多,这里我们学习两种比较常用的两种,Pull解析和SAX解析,简单起见,我们还是在NetworkTest项目的基础上继续开发,这样就可以重用之前网络通信部分的代码,从而把工作重心放在XML数据的解析上。
前面XML格式的数据已经提供好了,现在要做的就是从中解析出我们想要得到的那部分内容。
第一步:修改MainActivity中的代码
代码分析:
1.首先是将HTTP请求的地址改成了:http://192.168.1.146/get_data.xml,192.168.1.146对于模拟器来说就是电脑本机的IP地址,查看电脑的本机IP地址,请看:https://www.cnblogs.com/hh8888-log/p/10280503.html,在得到了服务器返回的数据后,我们并不直接将其展示,而是调用了:parseXMLWithPull()方法来解析服务器返回的数据。
2.接下来仔细看看:parseXMLWithPull()方法,这里首先要获取到一个XmlPullParseFactory的实例,并借助这个实例得到XmlPullParse对象,然后调用XmlPullParse的setInput()方法将服务器返回的XML数据设置进去就可以开始解析了。解析过程也非常的简单,通过:getEventType()可以得到当前的解析事件,然后在一个while循环中不断的进行解析,如果当前的解析事件不等于XmlPullParse.END_DOCUMENT,说明解析工作还没完成,调用next()方法后可以获取下一个解析事件。
3.在while循环中,我们通过getName()方法得到当前节点的名字,如果发现节点名等于id、name或version,就调用:nextText()方法来获取节点具体的内容,每当解析完一个app节点后就将获取到的内容打印出来。
第二步:运行程序,点击Send Request按钮,查看打印的日志如下,可以看到,我们已经将XML数据中的指定内容成功的解析了出来。
3.2、SAX解析方式
Pull解析方式虽然好用,但它不是我们的唯一选择,SAX解析也是一种特别常用的XML解析方式,虽然它的用法比Pull解析要复杂一些,但在语义方面会更加清楚。
通常我们都会创建一个类继承自DefaultHandler,并重写父类的5个方法,如下所示:
代码分析:
startDocument():在开始XML解析的时候调用
startElement():在开始解析某个节点的时候调用
characters():在获取节点中内容的时候调用
endElement():在完成解析某个节点的时候调用
endDocument():方法会在完成整个XML解析的时候调用
其中,startElement()、characters()和endElement()这3个方法是有参数的,从XML中解析出的数据就会以参数的形式传入到这些方法中,需要注意的是:在获取节点中的内容时,character()方法可能会被调用多次,一些换行符也被当作内容解析出来,我们需要针对这种情况在代码中做好控制。
下面就来尝试使用SAX解析方式来实现和上一节中同样的功能:
第一步:新建一个ContentHandler类继承自DefaultHandler,并重写父类的5个方法:
代码分析:
首先我们先给id、name和version节点分别定义了一个StringBuilder对象,并在startDocument()方法里对它们进行了初始化,每当开始解析某个节点的时候,startElement()方法就会得到调用,其中localName参数记录着当前节点的名字,这里我们把它记录下来。接着在解析节点中具体内容的时候就会调用character()方法,我们会根据当前的节点名进行判断,将解析出的内容添加到哪一个StringBuilder对象中,最后在endElement()方法中进行判断,如果app节点已经解析完成,就打印出id、name、version的内容,需要注意的是:目前id、name、version中可能是包括回车或换行符,因此在打印之前我们还需要调用一下:trim()方法,并且打印完成后还要将StringBuilder的内容清空掉,不然的话会影响下一次内容的读取。
第二步:修改MainActivity中的代码:
在得到服务器返回的数据后,我们这次去调用parseXMLWithSAX()方法来解析XML数据,parseXMLWithSAX()方法中先是创建了一个SAXParseFactory的对象,然后再获取到XMLReader对象,接着将我们编写好的ContentHandler的实例设置到XMLReader中,最后调用parse()方法开始执行解析就好了。
第三步:重新运行程序,点击按钮,观察日志(注意:本机的网络ip地址是变化的,所以在运行程序之前,先查看ip地址是否有改变,如果改变了需要在MainActivity中进行更改。)
除了Pull解析和SAX解析之外,其实还有一种DOM解析方式也算挺常用的,这里就不介绍了,可以查看相关资料进行学习。
四、解析JSON格式数据
比起XML,JSON的主要优势在于它的体积更小,在网络传输的时候可以更省流量。但是缺点在于,它的语义性较差,看起来不如XML直观。
在开始之前,还需要在:Apache\htdocs目录中新建一个:get_data.json的文件,然后编写这个文件,并加入如下JSON格式的内容:
这时在浏览器中访问:http://127.0.0.1/get_data.json这个网址,出现如下内容就表示我们把JSON格式的数据也准备好了,下面就开始学习如何在Android程序中解析这些数据。
4.1、使用JSONObject
类似地,解析JSON数据的也有很多方法,可以使用官方提供的JSONObject,也可以使用谷歌的开源库GSON,另外,一些第三方的开源库如Jackson、FastJSON等也非常不错,本节中就学习前两种方式:
第一步:修改MainActivity中的代码:
代码分析:
首先记得要将HTTP请求地址中的:get_data.xml改成:get_data.json,然后在得到了服务器返回的数据后调用:parseJSONWithJSONObject()方法来解析数据。可以看到,解析JSON的代码真的非常简单,由于我们在服务器中定义的是一个JSON数组,因此这里首先是将服务器返回的数据传入到一个JSONArray对象中,然后循环遍历这个JSONArray,从中取出的每一个元素都是一个JSONObject对象,每个JSONObject对象中又会包含id、name和version这些数据,接下来只需要调用getString()方法将这些数据取出,并打印出来即可。
第二步:运行程序,点击按钮,打印日志:
4.2、使用GSON
如果你认为使用JSONObject来解析JSON数据已经非常简单了,那你就太容易满足了,谷歌提供的GSON开源库可以让解析JSON数据工作简单到让你不敢想象的地步。不过GSON并没有被添加到Android官方的API中,因此如果想要使用这个功能的话,就必须要在项目中添加GSON库的依赖。编辑app/build.gradle文件,在dependencies闭包中添加如下内容:(版本号请查看:https://mvnrepository.com/artifact/com.google.code.gson/gson)
那么GSON库究竟是神奇在哪里呢?其实它主要就是可以将一段JSON格式的字符串自动映射成一个对象,从而不需要我们再手动去编写代码进行解析了。比如说一段JSON格式的数据如下:
那么我们就可以定义一个Person类,并加入name和age这两个字段,然后只需要简单的调用如下代码就可以将JSON数据自动解析成一个Person对象了:
如果需要解析的是一段JSON数组会稍微麻烦一点,我们需要借助TypeToken将期望解析成的数据类型传入到:fromJson()方法中,如下所示:
好了,基本用法就是这样了,下面就来真正尝试一下吧:
第一步:新增一个App类,并加入id、name、version三个字段
第二步:修改MainActivity中的代码
第三步:运行程序,点击按钮打印日志
到此为止,就算已经把XML和JSON这两种数据格式最常用的几种解析方法都学习完了。下一节进行一个网络编程的实战学习。