Android学习路线_入门篇(十一)网络请求的基本实现

网络请求是互联网开发的基石,单机的应用终究不能发挥互联网的优势,互联网+任何东西都有不小的发展潜力。在Android开发领域,基本的网络连接主要涉及到数据的上传、下载与网页浏览,而其中数据的上传和下载是APP和服务器交互的基石,目前主要是通过HTTP协议上传和下载文本、XML、Json、图片、音视频文件,基本实现方式主要涉及HttpURLConnection和HttpClient。

本文已收录至☞Android学习路线_梳理
上一篇☞Android学习路线_入门篇(十)数据持久化之数据库

1 网络请求基本知识

1.1 Http协议

实际开发中我们和服务器交互一般用的都是基于Http协议的通信,所以学好Http协议是非常重要的,当然,我们不用过于考究一些细节的东西,有个大体的了解即可,都是一些概念性的东西。

Http全称hypertext transfer protocol(超文本传输协议),TCP/IP协议的一个应用层协议,用于定义客户端与服务器之间交换数据的过程。客户端连上服务器后,若想获得服务器中的某个资源,需遵守一定的通讯格式,HTTP协议用于定义客户端与服务器通讯的格式。

Http操作的流程:

  • 客户端与服务器建立连接
  • 客户端发送请求给服务器,请求的格式为:统一资源标识符(URL) + 协议版本号(一般是1.1) + MIME信息(多个消息头) + 一个空行
  • 服务器收到请求后,给予相应的返回信息,返回格式为:协议版本号 + 状态行(处理结果) + 多个信息头 + 空行 + 实体内容(比如返回的XML)
  • 客户端接收服务器返回信息,显示出来,然后与服务端断开连接;当然如果中途某步发生错误的话,错误信息会返回到客户端,比如:经典的404错误
    HTTP协议流程图

1.2 HTTP请求的方式

实际开发中我们用得较多的方式是GetPost,但是实际开发可能还会用到其他请求方式,所有的请求方式如下:

  • Get:请求获取Request-URI所标识的资源
  • POST:在Request-URI所标识的资源后附加新的数据
  • HEAD请求获取由Request-URI所标识的资源的响应信息报头
  • PUT:请求服务器存储一个资源,并用Request-URI作为其标识
  • DELETE:请求服务器删除Request-URI所标识的资源
  • TRACE:请求服务器回送收到的请求信息,主要用于测试或诊断
  • CONNECT:保留将来使用
  • OPTIONS:请求查询服务器的性能,或者查询与资源相关的选项

用得最多的GetPost对比如下:

  • GET:在请求的URL地址后以?的形式带上交给服务器的数据,多个数据之间以&进行分隔, 但数据容量通常不能超过2K,比如:http://xxx?username=…&pawd=…这种就是GET
  • POST: 这个则可以在请求的实体内容中向服务器发送数据,传输没有数量限制
  • 另外要说一点,GetPost都是发送数据的,只是发送机制不一样,不要相信网上说的 “Get获得服务器数据,Post向服务器发送数据”。另外Get发送数据用的明文,安全性非常低,Post发送数据的安全性较高,但是Get执行效率却比Post方法好,一般查询的时候我们用Get,数据增删改的时候用Post

1.3 HTTP请求头

HTTP请求头信息对照表:

Header解释示例
Accept指定客户端能够接收的内容类型Accept: text/plain, text/html
Accept-Charset浏览器可以接受的字符编码集。Accept-Charset: iso-8859-5
Accept-Encoding指定浏览器可以支持的web服务器返回内容压缩编码类型。Accept-Encoding: compress, gzip
Accept-Language浏览器可接受的语言Accept-Language: en,zh
Accept-Ranges可以请求网页实体的一个或者多个子范围字段Accept-Ranges: bytes
AuthorizationHTTP授权的授权证书Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
Cache-Control指定请求和响应遵循的缓存机制Cache-Control: no-cache
Connection表示是否需要持久连接。(HTTP 1.1默认进行持久连接)
CookieHTTP请求发送时,会把保存在该请求域名下的所有cookie值一起发送给web服务器。Cookie: $Version=1; Skin=new;
Content-Length请求的内容长度Content-Length: 348
Content-Type请求的与实体对应的MIME信息Content-Type: application/x-www-form-urlencoded
Date请求发送的日期和时间Date: Tue, 15 Nov 2010 08:12:31 GMT
Expect请求的特定的服务器行为Expect: 100-continue
From发出请求的用户的EmailFrom: user@email.com
Host指定请求的服务器的域名和端口号Host: www.zcmhi.com
If-Match只有请求内容与实体相匹配才有效If-Match: “737060cd8c284d8af7ad3082f209582d”
If-Modified-Since如果请求的部分在指定时间之后被修改则请求成功,未被修改则返回304代码If-Modified-Since: Sat, 29 Oct 2010 19:43:31 GMT
If-None-Match如果内容未改变返回304代码,参数为服务器先前发送的Etag,与服务器回应的Etag比较判断是否改变If-None-Match: “737060cd8c284d8af7ad3082f209582d”
If-Range如果实体未改变,服务器发送客户端丢失的部分,否则发送整个实体。参数也为EtagIf-Range: “737060cd8c284d8af7ad3082f209582d”
If-Unmodified-Since只在实体在指定时间之后未被修改才请求成功If-Unmodified-Since: Sat, 29 Oct 2010 19:43:31 GMT
Max-Forwards限制信息通过代理和网关传送的时间Max-Forwards: 10
Pragma用来包含实现特定的指令Pragma: no-cache
Proxy-Authorization连接到代理的授权证书Proxy-Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
Range只请求实体的一部分,指定范围Range: bytes=500-999
Referer先前网页的地址,当前请求网页紧随其后,即来路Referer: http://blog.csdn.net/coder_pig
TE客户端愿意接受的传输编码,并通知服务器接受接受尾加头信息TE: trailers,deflate;q=0.5
Upgrade向服务器指定某种传输协议以便服务器进行转换(如果支持)Upgrade: HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11
User-AgentUser-Agent的内容包含发出请求的用户信息User-Agent: Mozilla/5.0 (Linux; X11)
Via通知中间网关或代理服务器地址,通信协议Via: 1.0 fred, 1.1 nowhere.com (Apache/1.1)
Warning关于消息实体的警告信息Warn: 199 Miscellaneous warning

1.4 HTTP响应头

HTTP响应头信息对照表:

Header解释示例
Accept-Ranges表明服务器是否支持指定范围请求及哪种类型的分段请求Accept-Ranges: bytes
Age从原始服务器到代理缓存形成的估算时间(以秒计,非负)Age: 12
Allow对某网络资源的有效的请求行为,不允许则返回405Allow: GET, HEAD
Cache-Control告诉所有的缓存机制是否可以缓存及哪种类型Cache-Control: no-cache
Content-Encodingweb服务器支持的返回内容压缩编码类型Content-Encoding: gzip
Content-Language响应体的语言Content-Language: en,zh
Content-Length响应体的长度Content-Length: 348
Content-Location请求资源可替代的备用的另一地址Content-Location: /index.htm
Content-MD5返回资源的MD5校验值Content-MD5: Q2hlY2sgSW50ZWdyaXR5IQ==
Content-Range在整个返回体中本部分的字节位置Content-Range: bytes 21010-47021/47022
Content-Type返回内容的MIME类型Content-Type: text/html; charset=utf-8
Date原始服务器消息发出的时间Date: Tue, 15 Nov 2010 08:12:31 GMT
ETag请求变量的实体标签的当前值ETag: “737060cd8c284d8af7ad3082f209582d”
Expires响应过期的日期和时间Expires: Thu, 01 Dec 2010 16:00:00 GMT
Last-Modified请求资源的最后修改时间Last-Modified: Tue, 15 Nov 2010 12:45:26 GMT
Location用来重定向接收方到非请求URL的位置来完成请求或标识新的资源Location: http://blog.csdn.net/coder_pig
Pragma包括实现特定的指令,它可应用到响应链上的任何接收方Pragma: no-cache
Proxy-Authenticate它指出认证方案和可应用到代理的该URL上的参数Proxy-Authenticate: Basic

2 HttpURLConnection的使用

HttpURLConnection是一种多用途、轻量极的HTTP客户端,使用它来进行HTTP操作可以适用于大多数的应用程序。 虽然HttpURLConnection的API提供的比较简单,但是同时这也使得我们可以更加容易地去使 用和扩展它。继承至URLConnection,抽象类,无法直接实例化对象。通过调用openCollection() 方法获得对象实例,默认是带gzip压缩的。

HttpUrlConnectionJDK里提供的联网API,我们知道Android SDK是基于Java的,所以当然优先考虑HttpUrlConnection这种最原始最基本的API,其实大多数开源的联网框架基本上也是基于JDKHttpUrlConnection进行的封装罢了。

2.1 初始化

使用之前,先在AndroidManifest中加上联网权限:

<uses-permission android:name="android.permission.INTERNET" />
  1. 将访问的路径转换成URL:
URL url = new URL(path);
  1. 通过URL获取连接:
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
  1. 设置连接超时时间:
conn.setConnectTimeout(5000); // 设置超时时间为5000毫秒,即5秒
  1. 设置请求头的信息:
// 根据具体情境设置请求头,可参考 1.3 HTTP请求头
conn.setRequestProperty(Content-Type, application/x-www-form-urlencoded);
  1. 封装输入流转换成字符串方法:
public String streamToString(InputStream is) {
    try {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len = 0;
        while ((len = is.read(buffer)) != -1) {
            baos.write(buffer, 0, len);
        }
        baos.close();
        is.close();
        byte[] byteArray = baos.toByteArray();
        return new String(byteArray);
    } catch (Exception e) {
        Log.e(tag, e.toString());
        return null;
    }
}

2.2 Get请求

  1. 设置请求方式:
conn.setRequestMethod(GET);
  1. 获取响应码:
int code = conn.getResponseCode();
  1. 请求码200,表明请求成功,获取返回内容的输入流:
if (code == 200) {
    InputStream is = conn.getInputStream();
}
  1. 将输入流转换成目标格式:
// 将输入流转换成字符串
String result = streamToString(is);

2.3 Post请求

  1. 设置请求方式:
conn.setRequestMethod(POST);
  1. 添加请求数据:
try {
    String data = "baseData";
    conn.setRequestProperty(Content-Length, data.length() + "");
    // POST方式,其实就是把数据写给服务器
    conn.setDoOutput(true); // 设置可输出流
    OutputStream os = conn.getOutputStream(); // 获取输出流
    os.write(data.getBytes()); // 将数据写给服务器
} catch (Exception e) {
   e.printStackTrace();
    return 网络访问失败;
}
  1. 获取响应码:
int code = conn.getResponseCode();
  1. 请求码200,表明请求成功,获取返回内容的输入流:
if (code == 200) {
    InputStream is = conn.getInputStream();
}
  1. 将输入流转换成目标格式:
// 将输入流转换成字符串
String result = streamToString(is);

3 HttpClient的使用

HttpClient是开源组织Apache提供的Java请求网络框架,其最早是为了方便Java服务器开发而诞生的,是对JDK中的HttpUrlConnection各API进行了封装和简化,提高了性能并且降低了调用API的繁琐。

Android因此也引进了这个联网框架,我们再不需要导入任何jar或者类库就可以直接使用,值得注意的是Android官方已经宣布不建议使用HttpClient了,开发的时候尽量少用吧。尽管被Google 弃用了,但是我们我们平时也可以拿HttpClient来抓下包。HttpClient 用于接收/发送Http请求/响应,但不缓存服务器响应,不执行HTML页面潜入的JS代码,不会对页面内容进行任何解析,处理。

3.1 Get请求

  1. 创建HttpClient对象:
HttpClient client = new DefaultHttpClient();
  1. 创建HttpGet对象,指定请求地址(带参数);
HttpGet httpGet = new HttpGet(path);
  1. 使用HttpClientexecute(),方法执行HttpGet请求,得到HttpResponse对象;
HttpResponse response = client.execute(httpGet);
  1. 调用HttpResponsegetStatusLine().getStatusCode()方法得到响应码;
int code = response.getStatusLine().getStatusCode();
  1. 调用的HttpResponsegetEntity().getContent()得到输入流,获取服务端写回的数据。
if (code == 200) {
    InputStream is = response.getEntity().getContent(); // 获取实体内容
    String result = streamToString(is); // 字节流转字符串
}

3.2 Post请求

  1. 创建HttpClient对象;
HttpClient client = new DefaultHttpClient(); 
  1. 创建HttpPost对象,指定请求地址;
HttpPost httpPost = new HttpPost(path);
  1. 创建List,用来装载参数;
List<namevaluepair> parameters = new ArrayList<namevaluepair>();
parameters.add(new BasicNameValuePair(data, "baseData"));
  1. 调用HttpPost对象的setEntity()方法,装入一个UrlEncodedFormEntity对象,携带之前封装好的参数;
httpPost.setEntity(new UrlEncodedFormEntity(parameters, UTF-8));
  1. 使用HttpClientexecute()方法执行HttpPost请求,得到HttpResponse对象;
HttpResponse response = client.execute(httpPost);
  1. 调用HttpResponsegetStatusLine().getStatusCode()方法得到响应码;
int code = response.getStatusLine().getStatusCode();
  1. 调用的HttpResponsegetEntity().getContent()得到输入流,获取服务端写回的数据;
if (code == 200) {
    InputStream is = response.getEntity().getContent();
    String result = streamToString(is);
}

4 数据解析

上面的请求中最后获得的数据格式都是字符串,日常开发中不可能只有纯文本数据,常见的其他数据主要有XML、JSON、图片、音视频文件。其实图片和音视频文件都属于文件,只需要将响应中获取的输入流写入目标格式的文件就完成了。XML和JSON属于特殊的文本数据,通常需要经过再次解析才能使用。

4.1 XML数据解析

常见的XML解析方式有SAX、DOM、PULL三种:
SAX、DOM、PULL解析方式比较
在Android系统中内置了PULL解析器,常用的SharedPreference就是XML格式的数据并且通过PULL完成数据解析,这里就不对SAX和DOM进行详细介绍了,主要介绍一下PULL解析器的使用。

// 首先需要一个实体类Person
class Person{
	private String id;
	private String name;
	private String age;
	public String getId() { return id; }
	public void setId(String id) { this.id = id; }
	public String getName() { return name; }
	public void setName(String name) { this.name = name; }
	public String getAge() { return age; }
	public void setAge(String age) { this.age = age; }
}

// 将XML数据转换成Person列表
public static ArrayList<Person> getPersons(InputStream xml)throws Exception{
    ArrayList<Person> persons = null;
    Person person = null;
    // 创建一个xml解析的工厂  
    XmlPullParserFactory factory = XmlPullParserFactory.newInstance();  
    // 获得xml解析类的引用  
    XmlPullParser parser = factory.newPullParser();  
    parser.setInput(xml, "UTF-8");  
    // 获得事件的类型  
    int eventType = parser.getEventType();  
    while (eventType != XmlPullParser.END_DOCUMENT) {  
        switch (eventType) {  
        case XmlPullParser.START_DOCUMENT:  
            persons = new ArrayList<Person>();  
            break;  
        case XmlPullParser.START_TAG:  
            if ("person".equals(parser.getName())) {  
                person = new Person();  
                // 取出属性值  
                int id = Integer.parseInt(parser.getAttributeValue(0));  
                person.setId(id);  
            } else if ("name".equals(parser.getName())) {  
                String name = parser.nextText();// 获取该节点的内容  
                person.setName(name);  
            } else if ("age".equals(parser.getName())) {  
                int age = Integer.parseInt(parser.nextText());  
                person.setAge(age);  
            }  
            break;  
        case XmlPullParser.END_TAG:  
            if ("person".equals(parser.getName())) {  
                persons.add(person);  
                person = null;  
            }  
            break;  
        }  
        eventType = parser.next();  
    }  
    return persons;  
} 

// 将Person列表存为XML文件
public static void save(List<Person> persons, OutputStream out) throws Exception {
    XmlSerializer serializer = Xml.newSerializer();
    serializer.setOutput(out, "UTF-8");
    serializer.startDocument("UTF-8", true);
    serializer.startTag(null, "persons");
    for (Person p : persons) {
        serializer.startTag(null, "person");
        serializer.attribute(null, "id", p.getId() + "");
        serializer.startTag(null, "name");
        serializer.text(p.getName());
        serializer.endTag(null, "name");
        serializer.startTag(null, "age");
        serializer.text(p.getAge() + "");
        serializer.endTag(null, "age");
        serializer.endTag(null, "person");
    }
    serializer.endTag(null, "persons");
    serializer.endDocument();
    out.flush();
    out.close();
}

4.2 JSON数据解析

XML数据已经很少用于网络请求的数据了,现在主流的是JSON数据,这里介绍一下原生的JSONObject和JSONArray,第三方的库感兴趣的可以自行了解,比较常用的有Gson、Fastjson、Jackson。

还是基于4.1中的实体类Person进行分析,Json数据的基本格式示例:

[
    { "id":"1","name":"AAA","age":"18" },
    { "id":"2","name":"BBB","age":"18"  },
    { "id":"3","name":"CCC","age":"18" }
]

JSONObject类主要用来解析一个Json数据单位,常用的方法有:

  • JSONObject(String json);将Json字符串解析成Json对象;
  • getXxx(String name) ;根据name在json对象中得到相应的value值;

JSONObject类主要用来解析一组Json数据,得到一个JSONObject数组,常用的方法有:

  • JSONArray(String json);将json字符串解析成JSONObject数组;
  • int length();得到JSONObject数组中元素的个数;

简单Json数据解析使用示例:

private void parseEasyJson(String json){
    persons = new ArrayList<Person>();
    try{
        JSONArray jsonArray = new JSONArray(json);
        for(int i = 0;i < jsonArray.length();i++){
            JSONObject jsonObject = (JSONObject) jsonArray.get(i);
            Person person = new Person();
            person.setId(i+"");
            person.setName(jsonObject.getString("name"));
            person.setAge(jsonObject.getString("age"));
            persons.add(person);
        }
    }catch (Exception e){e.printStackTrace();}
}

完毕

今天的分享就到这里,文章多有不足,各位小伙伴有什么想法可以直接评论或是私信,要是对你有所帮助就给我一个赞吧,喜欢我的小伙伴可以关注我哦~

本文已收录至☞Android学习路线_梳理
上一篇☞Android学习路线_入门篇(十)数据持久化之数据库

支持我的小伙伴们可以微信搜索“Android思维库”,或者微信扫描下方二维码,关注我的公众号,每天都会推送新知识~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值
>