Android学习-网络技术-使用HTTP协议进行网络交互、HttpURLConnection与OkHttp、XML与JSON的解析、使用java回调机制将服务器响应的数据进行返回

Android中最常使用到的网络技术:在手机端使用HTTP写一和服务器端进行网络交互,并对服务器返回的数据进行解析。

WebView
可以在自己的应用程序里嵌入一个浏览器,来展示网页。
例子:
在这里插入图片描述
在这里插入图片描述
首先使用findViewById方法获取到了WebView的实例,然后调用WebView的getSettings方法可以去设置一些浏览器的属性,这里只调用了setJavaScriptEnabled方法来让WebView支持javascript脚本。

接下来调用了WebView的setwebviewclient方法,并传入了一个webviewclient实例。这段代码的作用是,当需要从一个网页跳转到另一个网页时,我们希望目标网页仍然在当前webview中显示,而不是打开系统浏览器

最后一步就是调用webview的loadurl方法将网址传入。

注意,程序使用到了网络功能,而访问网络时需要声明权限的,因此需要修改AndroidManifest.xml文件,并加入声明权限。
在这里插入图片描述

使用HTTP协议访问网络
HTTP协议的工作原理:
客户端向服务器发送一条HTTP请求,服务器收到请求之后会返回一些数据给客户端,然后客户端在对这些数据进行解析和处理就可以了。一个浏览器的基本工作原理也就是如此了。
比如webview控件,其实也就是我们向百度的服务器发起了一条HTTP请求,接着服务器分析出我们想要访问的时百度的首页,于是会把该网页的HTML代码进行返回,然后webview再调用手机浏览器的内核对返回的HTML代码进行解析,最终将页面展示出来。

简单来说,webview已经在后台帮我们处理好了发送HTTP请求、接收服务响应、解析返回数据,以及最终页面的展示这几步工作,不过由于它封装的太好了,反而使得我们不能那么直观的看出HTTP写一到底是如何工作的。接下来使用手动发送HTTP请求方式来学习。

使用HttpURLConnection

首先需要获取到HttpURLConnection的实例,一般只需要new出一个URL对象,并传入目标的网络地址,然后调用一下openConnection方法即可。
在这里插入图片描述
得到实例后,可以设置一下HTTP请求所使用的方法。常用的方法主要有两个:GET和POST。GET表示希望从服务器那里获取数据,而post则表示希望提交数据给服务器。
在这里插入图片描述
接下来可以进行一些自由的定制,比如设置连接超时、读取超时的毫秒数,以及服务器希望得到的一些消息头等。
在这里插入图片描述
之后再调用getInputStream方法获取到服务器返回的输入流,剩下的任务就是对输入流进行读取。
在这里插入图片描述
最后可以调用disconnect方法将这个HTTP连接关闭
在这里插入图片描述
具体实例:
在这里插入图片描述
在这里插入图片描述
这里使用了一个新控件ScrollView。由于手机屏幕的空间一般都比较小,有时候过多的内容一屏是显示不下的,借助ScrollView控件的,我们就可以以滚动形式查看屏幕外的那部分内容。另外,布局中还放置了一个Button和一个TextView,Button用于发送HTTP请求,TextView用于将服务器返回的数据显示出来。、

接着修改MainActivity中的代码,如下:
在这里插入图片描述
在这里插入图片描述
首先在send request按钮的点击事件中调用了sendrequestwithHttpRULConnection方法,这个方法中先是开启了一个子线程,然后在子线程里使用HttpURLConnection发出一条http请求,请求的目的地址就是百度的首页。

接着利用bufferedreader对服务器返回的流进行读取,并将结果传入到了showresponse方法中。而在showresponese方法里调用了一个runOnUiThread方法,然后在这个方法的匿名类参数中进行操作,将返回的数据显示到界面上。这里使用这个方法是因为Android不允许在子线程中进行UI操作,我们需要通过这个方法将线程切换到主线程,然后更新UI元素。现在知道需要这么写就可以了。注意声明网络权限。

运行程序后
在这里插入图片描述
服务器给我们返回的就是这种HTML代码,只是通常情况下浏览器都会讲这些代码解析成漂亮的网页然后再展示出俩。

提交数据给服务器

将http请求的方法改成post,并在获取输入流之前把要提交的数据写出即可。注意每条数据都要以键值对的形式存在,数据与数据之间用“&”符号隔开,比如说我们想要向服务器提交用户名和密码,就可以写成:
在这里插入图片描述

使用Ok’Http
出色的网络通信开源库。

在使用前,需要在项目中添加OkHttp库的依赖。
编辑app/build.gradle文件,在dependecies闭包中添加如下内容:
在这里插入图片描述

添加依赖后会自动下载两个库,一个是OkHttp库,一个式Okio库,后者是前者的通信基础。

下面看一下OkHttp的基本用法,首先需要创建一个OkHttpClient的实例:在这里插入图片描述
如果想要发起一条Http请求,就需要创建一个Request对象:
在这里插入图片描述

上述代码创建的是一个空的Request对象,并没有实际作用,我们可以在最终的build方法之前连缀很多其他方法来丰富这个Request对象。比如可以通过rul方法来设置目标的网络地址,如下:
在这里插入图片描述

之后调用okhttpclient的newCall方法来创建一个Call对象,并调用他的execute方法来发送请求并获取服务器返回的数据,写法如下:
在这里插入图片描述
其中response对象就是服务器返回的数据了,我们可以使用如下写法来得到返回的具体内容:
在这里插入图片描述
如果是发起一条POST请求会比GET请求稍微复杂一点,我们需要先构造出一个Request Body对象来存放待提交的参数,如下所示:
在这里插入图片描述
然后在Requset.Builder中调用一下post方法,并将RequestBody对象传入:
在这里插入图片描述

接下来的操作就和GET请求一样了,调用execute方法来发送请求随并获取服务器返回的数据即可。

解析XML格式数据

通常情况下,每个需要访问网络的应用程序都会有一个自己的服务器,我们可以向服务器提交数据,也可以从服务器上获取数据。不过这个时候就出现了一个问题,这些数据到底要以什么样的格式在网络上传输呢?随便传递一段文本肯定是不行的,因为另一方根本就不会知道这段文字的用途是什么。因此,一般我们都会在网络上传输一些格式化后的数据,这种数据会有一定的结构规格和语义,当另一方收到数据消息之后就可以按照相同的结构规格进行解析,从而取出它想要的那部分内容。

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

在开始之前需要解决一个问题,就是从哪里才能获取到一段XML格式的数据呢?这里准备学习搭建一个最简单的web服务器,在这个服务器上提供一段XML文本,然后再程序里去访问这个服务器,再对得到的XML文本进行解析。

搭建web服务器有很多的服务器类型可供选择,这里准备使用Apache服务器。
首先需要下载一个Apache服务器的安装包,然后一直点next,会提示让你输入自己的域名,随便填一个就可以了。

然后打开电脑的浏览器,在地址栏输入127.0.0.1
出现如下界面,说明服务器已经启动成功了
在这里插入图片描述

接下来进入到C:/Apache/htdocs目录下,在这里新建一个名为get_data.xml的文件,然后编辑这个文件,并加入如下XML格式的内容。
在这里插入图片描述
这时在浏览器中访问http://127.0.0.1/get_data.xml这个网址,应该出现如下内容:

在这里插入图片描述

准备工作结束,接下来在Android程序中去获取并解析这段XML数据。

Pull解析方式
解析XML格式的数据有很多种方式。
为了简便重用之前网络通信部分代码。
现在要做的是从中解析出我们想要得到的那部分内容。修改MainActivity中的代码,如下:

在这里插入图片描述
在这里插入图片描述
可以看到,这里首先是将http请求的地址改成了http://10.0.2.2/get_data.xml,10.0.2.2对于模拟器来说就是电脑本机的IP地址。
在得到了服务器返回的数据之后,并不再直接将其展示,而是调用了paresXMLWithPull方法来解析服务器返回的数据。

下面来看paresXMLWithPull方法。这里首先需要获取一个xmlpullpareserfactory的实例,并借助这个实例得到xmlpullparser对象,然后调用xmlpullparser的setInput方法将服务器返回的XML数据设置进去就可以开始解析了。解析的过程也非常简单,如果当前的解析时间不等于xmlpullparser.END_DOCUMENT,说明解析工作还没有完成,调用next方法后可以获取下一个解析事件。

while循环中,我们通过getName方法得到当前节点的名字,如果发现节点名等于id、name或者version,就调用nextText方法来获取节点内的具体内容,每当解析完一个app节点后就将获取到的内容打印出来。

SAX解析方式

通常情况下会新建一个类继承自DefaultHandler,并重写父类的五个方法,如下:
在这里插入图片描述
startDocument方法会在开始XML解析的时候调用,startElement方法会在开始解析某个节点的时候调用,characters方法会在获取节点内容的时候调用,endElement方法会在完成解析某个节点的时候调用,endDocument方法会在完成整个XML解析的时候调用。
其中startElement、characters、endElement这三个方法是有参数的,从XML中解析出的数据就会以参数的形式传入到这些方法中。需要注意的是,在获取节点的内容时,characters方法可能会被调用多次,一些换行符也被当作内容被解析出来,我们需要对这种情况在代码中做好控制。

那么尝试用SAX解析的方式来实现和刚刚同样的功能。
新建一个ContentHandler类继承DefaultHander,并重写父类的五个方法,如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
首先给id、name、version节点分别定义了一个StringBuilder对象,并在startDocument方法里对它们进行了初始化。每当开始解析某个节点的时候,startElement方法就会得到调用,其中localName参数记录着当前节点的名字,这里我们把它记录下来。接着在解析节点中具体内容的时候就会调用characters方法,我们会根据当前的节点名进行判断,将解析出来的内容添加到哪一个StringBuilder对象。最后在endElement方法中进行判断,如果app节点已经解析完成,就打印出id、name、version的内容。需要注意的是,目前这id、name、version中都可能包括回车或者换行符的,因此在打印之前我们还需要调用一个trim方法,并且打印完成后还要将StringBuilder的内容清空掉,不然会影响下一次内容的读取。

接下来修改活动中的代码:
在这里插入图片描述
在这里插入图片描述
在得到了服务器返回的数据后,我们这次去调用parseXMLWithSAX方法来解析XML数据。
方法中首先是创建了一个SAXParseFactory的对象,然后再获取到XMLReader对象,接着将我们编写的ContentHandler的实例设置到XMLReader中,最后调用parse方法开始执行解析就好了。

解析JSON格式数据
相比于XML,JSON的主要优势在于它的体积更小,在网络上传输的时候可以更省流浪。但缺点在于,它的语义性较差,看起来不如XML直观。
在开始之前,需要在C:/Apache/htdocs目录中新建一个get_data.json的文件,然后编辑这个文件,并加入如下JSON格式的内容:
在这里插入图片描述

这时在浏览器中访问http://127.0.0.1/get_data.json这个网址,就应该出现如图所示的内容:
在这里插入图片描述
接下来在Android程序中解析这些数据

使用JSONObject
可以使用官方提供的JSONObject,也可以使用谷歌的开源库GSON。另外一些第三方的开源库如Jackson、FastJSON等。
这里学习前两种。

修改活动中代码
在这里插入图片描述
在这里插入图片描述
在得到服务器返回的数据后调用parseJSONWithJSONObject方法来解析数据。
由于在服务器中定义的是一个JSON数组,因此这里首先是将服务器返回的数据传入到了一个JSONArray对象中。然后循环遍历这个JSONArray,从中取出的每一个元素都是一个JSONObject对象,每个JSONObject对象中又回包含id、name和version这些数据。
接下来只需要调用getString方法将这些数据取出来并打印即可。

使用GSON
添加GSON库的依赖。
编辑app/build.gradle文件,在dependencies闭包中添加如下内容:
在这里插入图片描述

GSON库主要就是可以将一段JSON格式的字符串自动映射成一个对象,从而不需要我们再动手去编写代码进行解析了。

比如说一段JSON格式的数据如下所示:
在这里插入图片描述
那么我们就可以定义一个Person类,并加入name和age两个字段,然后只需要简单的调用如下代码就可以将JSON数据自动解析成一个Person对象了:
在这里插入图片描述

如果需要解析的是一段JSON数组会稍微马一点,我们需要借助TypeToken将期望解析成的数据类型传入到fromJson方法中,如下:
在这里插入图片描述

例子:
新建一个App类,并加入id、name和version这三个字段。如下:
在这里插入图片描述
在这里插入图片描述

然后修改活动中的代码

在这里插入图片描述
即可。

网络编程的最佳实践

目前学习了HttpURLConnection和OkHttp的用法,知道了如何发起HTTP请求,以及解析服务器返回的数据。
但其实之前这个写法是很有问题的。因为一个应用程序很可能会在许多地方都使用到网络功能,而发送HTTP请求的代码基本都是相同的,如果我们每次都去编写一遍发送HTTP请求的代码,这显然是非常差劲的做法。

通常情况下我们都应该将这些通用的网络操作提取到一个公共的类里,并提供一个静态方法,当想要发起网络请求的时候,只需要简单的调用一下这个方法即可。比如使用如下的写法:
在这里插入图片描述

以后每当需要发起一条HTTP请求的时候就可以这样写:
在这里插入图片描述

在获取到服务器响应的数据后,我们就可以对它进行解析和处理了。但是需要注意,网络请求通常都是属于耗时操作,而sendHttpRequest方法的内部并没有开启线程,这样就有可能导致sendHttpRequest方法的时候使得主线程被阻塞住。

但是如果我们在sendHttpRequest方法中开启了一个线程来发起HTTP请求,那么服务器响应的数据是无法进行返回的,所有的耗时逻辑都是在子线程里进行的,sendHttpRequest方法会在服务器还没来得及响应的时候就执行结束了,当然也就无法返回响应的数据了。

原因:主线程 会在子线程还在响应服务器的时候就运行完毕,并发的缘故 ,所以要采用异步回调才行

这里需要使用java的回调机制

需要定义一个接口,比如将他命名为HttpCallbackListener,代码如下:
在这里插入图片描述
在接口中定义了两个方法,onFinish方法表示当服务器成功响应我们请求的时候调用,onError表示当进行网络操作出现错误的时候调用。
这两个方法都带有参数,onFinish方法中的参数代表着服务器返回的数据,而onError方法中的参数记录着错误的详细信息。

接着修改HttpUitl中的代码:
在这里插入图片描述
在这里插入图片描述
首先给sendHttpRequest方法添加了一个HttpCallbackListener参数,并在方法的内部开启了一个子线程,然后在这个子线程里去执行具体的网络操作。
注意,子线程中是无法通过return语句来返回数据的,因此这里我们将服务器响应的数据传入了HttpCallbackListener的onFinish方法中,如果出现了异常就将异常原因传入到onError方法中。

现在sendHttpRequest方法接收了两个参数了,因此我们在调用它的时候还需要将HttpCallbackListener的实例传入,如下:
在这里插入图片描述
这样的话,当服务器成功响应的时候,我们就可以在onFinish方法里对响应数据进行处理了。类似,如果出现了异常就可以在onError方法里对异常情况进行处理。如此一来就利用了回调机制将响应数据成功返回给调用方了。

上述使用HttpURLConnection的写法总体来说还是比较复杂的,那么使用OkHttp会变得简单嘛?是的,还会简单得多,下面具体来看。

在HttpUtil中加入一个sendOkHttpRequest方法,如下:
在这里插入图片描述
可以看到方法中有一个okhttp3.Callback参数,这个是OkHttp库中自带的一个回调接口,类似于我们刚才自己编写的HttpCallbackListener。
然后再client.newCall之后没有像之后没有像之前那样一直调用execute方法,而是调用了一个enqueue方法,并把okhttp3.Callback参数传入。
enqueue方法的内部已经帮我们开启了子线程,然后会在子线程中去执行HTTP请求,并将最终的请求结果回调到okhttp3.Callback当中。

那么我们在调用sendOkHttpRequest方法的时候就可以这样写:
在这里插入图片描述

注意:不管是使用HttpURLConnection还是OkHttp,最终的回调接口都还是在子线程中运行的,因此我们不可以在这里执行任何的UI操作,除非借助runOnUiThread方法来进行线程转换。

猜你喜欢

转载自blog.csdn.net/vbbbbbbbbbbb/article/details/113897622