OKHTTP学习之高级特性

2024-08-21 22:48
文章标签 学习 特性 高级 okhttp

本文主要是介绍OKHTTP学习之高级特性,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

上一篇我已经将OKHTTP的基础知识介绍了一番<< 
OKHTTP学习之基础知识及运用 >>。这一篇我们一起探索一些复杂的功能。 
在这之前我们将基础知识再回顾一下。

  • Call
  • 同步请求 execute
  • 异步请求 enqueue
  • 异步请求时的回调 Callback
  • 服务器的回复 Response
  • 服务的消息体 ResponseBody
  • 网络访问的请求 Request
  • Header
  • 请求的消息体 RequestBody
  • 消息体的数据类型MediaType

不熟悉的话大家也可以返回点击这里。

基础功能

我们已经知道了

同步请求

execute()

异步请求

call()方法和Callback回调。

get和post

复杂功能

这一部分,我讲解Okhttp能够帮助我们做的一些工作。

下载文件

我之前讲ResponseBody的时候讲了它的

  • byte()
  • string()

  • bytesStream()

  • charStream()

其中byte()和string()是一次读取,用来获取体积比较小的内容。但如果遇到大文件的话,就应该用流的方式。 
所谓下载也就是将服务器返回的数据存储在本地。

  • 当体积体积较小时,用byte()或者string()获取内容。
  • 当体积很大时(超过1m),就应该用流的方式,用byteStream()或者charStream().

这里我用流的方式演示从网络上下载一张图片,然后保存在本地,然后显示出来。我是用bytesStream()方法。

private void testDownload(){//网络上的一张图String url = "http://img4.cache.netease.com/photo/0026/2015-05-19/APVC513454A40026.jpg";//图片下载时保存的地址final File filePath = new File(getExternalCacheDir().toString(),"tmp.jpg");OkHttpClient client = new OkHttpClient();Request request = new Request.Builder().url(url).build();Call call = client.newCall(request);call.enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {}@Overridepublic void onResponse(Call call, Response response) throws IOException {if(response.isSuccessful()){//获取inputstream对象InputStream is = response.body().byteStream();FileOutputStream fos = new FileOutputStream(filePath);int  b = 0;while((b = is.read()) != -1){fos.write(b);}fos.close();//下载成功后加载图片final Bitmap bitmap = BitmapFactory.decodeFile(filePath.getAbsolutePath());runOnUiThread(new Runnable() {@Overridepublic void run() {mImg.setImageBitmap(bitmap);}});}//关掉response.bodyresponse.body().close();}});}
  • 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
  • 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

效果如下图:

这里写图片描述

上传文件

说到下载功能就得说到上传功能,这样http访问才完整。 
因为没有找到网络上现在的可以上传调用的API,所以这部分还是要在我自己的电脑上编写PHP服务来验证。

php代码编写

php代码的上传功能分为两个部分。 
1. 客户端html发送表单数据。 
2. 服务端php程序通过$_FILES这个域变量来接收传过来的文件,然后移动文件到指定目录,整个过程就完成了。

html代码

文件名testupload.html

<html>
<head><title>test upload</title>
</head>
<body>
<form action="upload_file.php" method="post" enctype="multipart/form-data">
<label>filename:</label><input type="file" name="file" id="file" /><br/><input type="submit" name="submit" value="submit">
</form>
</body>
</html>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

重点在于这个标签。

action定义到表单发送的位置,这里是upload_file.php,说明表单将会发送到主机上的upload_file.php上。

method 中的方法是post。这个一定要写对,文件上传的内容必须放在实体中,不能添加在header中,所以不能用get,要用post.

enctype,这个定义内容是 multipart/form-data. 
enctype有两个值的范围。 
* 一个是application/x-www-form-urlencoded(默认值),传输文本信息 
* 一个是multipart/form-data.传输二进制信息

标签的 type=”file” 属性规定了应该把输入作为文件来处理。举例来说,当在浏览器中预览时,会看到输入框旁边有一个浏览按钮。

php代码

我直接就张贴代码了。我用的是Phpnow套件。解压后就放在E盘。 
然后把Php代码放在解压的的目录的package\hotdoc目录下。 
比如:E:\PHPnow-1.5.6.1428396605\Package\htdocs。前面的testupload.html也是放在这个目录。不熟悉php的朋友可以直接copy我的代码,然后放在里面。 
如果熟悉Php或者j2ee的同学则自己进行模拟。

upload_file.php

<?php//如果文件上传失败。if($_FILES['file']['error'] > 0){echo "Return code: ".$_FILES['franktest']['error']."<br/>";}else{echo "Upload: ".$_FILES['file']['name']."<br/>";//如果文件已经存在服务器if(file_exists($_FILES['file']['name'])){echo $_FILES['file']['name']." already exists.";}else{//移动临时文件到指定的路径,上传成功move_uploaded_file($_FILES['file']['tmp_name'],$_FILES['file']['name']);echo "upload successed";}}
?>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

为了更清楚的说明上传的原理,我用fiddler来抓包。不熟悉fiddler的同学可以自行上网查阅相关知识。 
如下图所演示: 
这里写图片描述

用fiddler抓取刚才的包可以得到下面消息:

POST http://localhost/upload_file.php HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:27.0) Gecko/20100101 Firefox/27.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Referer: http://localhost/upload.html
Connection: keep-alive
Content-Type: multipart/form-data; boundary=---------------------------178612565028255
Content-Length: 11185-----------------------------178612565028255
Content-Disposition: form-data; name="file"; filename="test.png"
Content-Type: image/pngPNGIHDR        )      sRGB       gAMA    a     pHYs       o d  *"IDATx^  a'p kf? Y#AJT 8   P  V  nd  #   Gl p D B^ +Z   W $.-     *  B AG   KI  C; ~     [  G 7   t     `z `z `z `z `z `z `z `z w E t s   P    IEND B` 
-----------------------------178612565028255
Content-Disposition: form-data; name="submit"submit
-----------------------------178612565028255--
  • 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
  • 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

提取我们关心的内容则是:

Content-Type: multipart/form-data; boundary=---------------------------178612565028255
Content-Length: 11185-----------------------------178612565028255
Content-Disposition: form-data; name="file"; filename="test.png"
Content-Type: image/pngPNGIHDR        )      sRGB       gAMA    a     pHYs       o d  *"IDATx^  a'p kf? Y#AJT 8   P  V  nd  #   Gl p D B^ +Z   W $.-     *  B AG   KI  C; ~     [  G 7   t     `z `z `z `z `z `z `z `z w E t s   P    IEND B` 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
Content-Type: multipart/form-data;

在这里可以看到Content-Type果真是multipart/form-data,而后面的boundary=—————————178612565028255,boundary是分界线的意思,后面的数字是随机数,用来分割实体。比如我上传了一个文本一张图片,它们之间就用这个来分割区别开来。

Content-Disposition

disposition的英文单词是配置的意思,在这里用来区分表单的内容,因为一个表单中有许多项,这里为了说明这一段属于哪一项。name=”file”是因为之前我的标签中定义了name=”file”。filename=”test.png”代表我此次上传的文件名字为test.png. 
Content-Type:在Content-Disposition:后面是为了说明表单中某一项传输的内容格式。比如,我此次上传的是一个图片文件test.png。所以它的值是image/png.如果我上传的是一个文本文件text.txt.则它的值是text/plain。 
常见的Content-type值如下: 
* .*(二进制文件,不知道格式的文件) application/octet-stream 
* .txt -> text/plain 
* .png -> image/png 
* .jpg -> image/jpeg 
* .html -> text/html 
* .mp4 -> video/mpeg 
* .apk -> application/vnd.Android.package-archive

更多的情况请访问这个链接: 
常用的html content-type对照表

Android用Okhttp上传文件代码

前面用一大段介绍了html上传文件流程。接下来就要编写如何在Android上编写上传代码。

清楚了上传的原理与流程,我们就可以用okhttp来模拟表单发送消息,从而达到上传文件的目的。

我们再把思路捋一捋。 
1. 用http协议。 
2. 添加相应的header.这里指Content-type:multipart/form-data 
3. 在表单项的实体中添加对应的内容描述。Content-Disposition:form-data; name=”file”; filename=”test.png”和Content-Type: image/png在这里Content-Type:application/octet-stream的话可以传输任何文件。 
4. 添加具体的实体数据。

好了,现在假设我们要用Android手机上传一张图片到服务器。代码如下:

private void testUpload(){//这个地址是我PC机上的IP地址,我用的是genimotion模拟器,如果用手机要保证手机和pc在同一个局域网内String url = "http://172.26.133.50//upload_file.php";File file = new File(Environment.getExternalStorageDirectory().toString(),"test.png");//创建Okhttp客户端OkHttpClient  client = new OkHttpClient();//定义MIME类型MediaType mediaType = MediaType.parse("application/octet-stream");//创建代表文件的实体RequestBody fileBody = RequestBody.create(mediaType,file);//创建表单实体,并把文件实体添加到表单实体当中RequestBody requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM).addPart(Headers.of("Content-Disposition","form-data;name=\"file\";" +"filename=\"test.png\""),fileBody).build();//创建request对象,并把实体以Post方式发送给服务器Request request = new Request.Builder().url(url).post(requestBody).build();//创建Call对象。final Call call = client.newCall(request);new Thread(new Runnable() {@Overridepublic void run() {try {Response response = call.execute();if(response.isSuccessful()){Log.d(TAG, "run: upload is successed.");}else{Log.d(TAG, "run: upload is failed ");}response.close();} catch (IOException e) {e.printStackTrace();Log.e(TAG, "run: "+e.getLocalizedMessage() );}}}).start();}
  • 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
  • 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

大家对照我前面所讲的,细细体会一下。

这里运用了一个知识点MultiPartBody.

MultipartBody.Builder can build sophisticated request bodies compatible with HTML file upload forms. Each part of a multipart request body is itself a request body, and can define its own headers. If present, these headers should describe the part body, such as its Content-Disposition. The Content-Length and Content-Type headers are added automatically if they’re available.

这是官网上的说明。 
MuiltipartBody.Builder可以构建一个html的文件上传表单这样的复杂的网络请求消息实体(request body). 
注意它能够构造复杂的消息实体。复杂在于它包含的内容也可以由RequestBody构成,在Okhttp中称为Part. 
如我可以同时发送一段文本、一张图片、一个Mp4文件给服务器,它们被MultipartBody封装在同一个表单,然后进行post请求。 
每一个part,也就是每一个实体都可以定义自己的headers。目前,这些被包含在form-data中的消息实体应该有描述了Content-Disosition的header。当然,Okhttp会自动添加它的Content-Length属性。

下面是精简的MultipartBody源码

public final class MultipartBody extends RequestBody {public static final MediaType MIXED = MediaType.parse("multipart/mixed");public static final MediaType ALTERNATIVE = MediaType.parse("multipart/alternative");public static final MediaType DIGEST = MediaType.parse("multipart/digest");public static final MediaType PARALLEL = MediaType.parse("multipart/parallel");/*** The media-type multipart/form-data follows the rules of all multipart MIME data streams as* outlined in RFC 2046. In forms, there are a series of fields to be supplied by the user who* fills out the form. Each field has a name. Within a given form, the names are unique.*/public static final MediaType FORM = MediaType.parse("multipart/form-data");private final ByteString boundary;private final MediaType originalType;private final MediaType contentType;private final List<Part> parts;private long contentLength = -1L;MultipartBody(ByteString boundary, MediaType type, List<Part> parts) {this.boundary = boundary;this.originalType = type;this.contentType = MediaType.parse(type + "; boundary=" + boundary.utf8());this.parts = Util.immutableList(parts);}public List<Part> parts() {return parts;}public Part part(int index) {return parts.get(index);}/** A combination of {@link #type()} and {@link #boundary()}. */@Override public MediaType contentType() {return contentType;}@Override public long contentLength() throws IOException {long result = contentLength;if (result != -1L) return result;return contentLength = writeOrCountBytes(null, true);}//Part是它的内部静态类public static final class Part {public static Part create(RequestBody body) {return create(null, body);}private final Headers headers;private final RequestBody body;private Part(Headers headers, RequestBody body) {this.headers = headers;this.body = body;}}//MultiPartBody通过内部的Builder对象构建public static final class Builder {private final ByteString boundary;private MediaType type = MIXED;private final List<Part> parts = new ArrayList<>();public Builder() {this(UUID.randomUUID().toString());}public Builder(String boundary) {this.boundary = ByteString.encodeUtf8(boundary);}/*** Set the MIME type. Expected values for {@code type} are {@link #MIXED} (the default), {@link* #ALTERNATIVE}, {@link #DIGEST}, {@link #PARALLEL} and {@link #FORM}.*/public Builder setType(MediaType type) {if (type == null) {throw new NullPointerException("type == null");}if (!type.type().equals("multipart")) {throw new IllegalArgumentException("multipart != " + type);}this.type = type;return this;}/** Add a part to the body. */public Builder addPart(RequestBody body) {return addPart(Part.create(body));}/** Add a part to the body. */public Builder addPart(Headers headers, RequestBody body) {return addPart(Part.create(headers, body));}/** Add a form data part to the body. */public Builder addFormDataPart(String name, String value) {return addPart(Part.createFormData(name, value));}/** Add a form data part to the body. */public Builder addFormDataPart(String name, String filename, RequestBody body) {return addPart(Part.createFormData(name, filename, body));}/** Add a part to the body. */public Builder addPart(Part part) {if (part == null) throw new NullPointerException("part == null");parts.add(part);return this;}/** Assemble the specified parts into a request body. */public MultipartBody build() {if (parts.isEmpty()) {throw new IllegalStateException("Multipart body must have at least one part.");}return new MultipartBody(boundary, type, parts);}}
}
  • 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
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 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
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140

代码不多。主要使用步骤。 
1. new MultiPartBody.Builder()创建Builder对象。 
2. addPart()或者addFormDataPart()添加文件或者是表单数据。 
3. 然后调用build()方法生成MultiPartBody对象。 
4. 调用Requst对象的post()方法,访问远程服务。

拦截器(Interceptors)

拦截器是一个强大的机制,它能对Call进行监测、改写、重试连接。它能够对请求和回复进行二次加工。

OKHTTP中的拦截器是链式的这个跟MINA框架中的拦截器类似。

拦截器的作用之log

下面是官网的一个例子。

class LoggingInterceptor implements Interceptor {@Override public Response intercept(Interceptor.Chain chain) throws IOException {Request request = chain.request();long t1 = System.nanoTime();Log.i(TAG, "intercept: "+String.format("Sending request %s on %s%n%s",request.url(), chain.connection(), request.headers()));Response response = chain.proceed(request);long t2 = System.nanoTime();Log.i(TAG, "intercept: "+String.format("Received response for %s in %.1fms%n%s",response.request().url(), (t2 - t1) / 1e6d, response.headers()));return response;}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

这段代码的主要功能是打印Request和Response的相关信息,比如url地址,比如路由,比如headers.

下面我们来编写代码测试一下。

在编写代码之前,先介绍两个概念就是应用拦截器(Application Interceptors)和网络拦截器(Network Interceptors). 
Interceptors要么被当成Application Interceptors注册,要么被当成Network Interceptors注册。

应用拦截器(Application Interceptors)

如果当成应用拦截器添加的话,那么要在OkHttpClient.Builder中的addInterceptors()方法中添加。

private void testInterceptors(){OkHttpClient client = new OkHttpClient.Builder().addInterceptor(new LoggingInterceptor()).build();String url = "http://blog.csdn.net/briblue";Request request = new Request.Builder().url(url).addHeader("name","frank").build();final Call call = client.newCall(request);new Thread(new Runnable() {@Overridepublic void run() {try {Response response = call.execute();response.body().close();} catch (IOException e) {e.printStackTrace();}}}).start();}
  • 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
  • 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

我在请求中人为中添加了一个头信息,这个信息只用来演示,没有实际意义。结果Log信息如下:

I/SeniorActivity: intercept: Sending request http://blog.csdn.net/briblue on null
name: frankI/SeniorActivity: intercept: Received response for http://blog.csdn.net/briblue in 164.0msServer: openresty
Date: Mon, 24 Oct 2016 04:03:57 GMT
Content-Type: text/html; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Keep-Alive: timeout=20
Vary: Accept-Encoding
Cache-Control: private
Set-Cookie: uuid=1ffd7e7e-6bed-41c3-b48c-f61f915f02f8;
expires=Tue, 25-Oct-2016 04:03:57 GMT; path=/
X-Powered-By: PHP 5.4.28
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

可以看到它详细打印了Request和Response的的一些信息。

那么我们再看NetworkInterceptors

网络拦截器(Network Interceptors)

上面讲了一个拦截器被当成Application Interceptors注册到了Okhttpclient.同时,一个拦截器也可以当成Netowork Interceptors注册到Okhttpclient.调用的是它的Builder对象的addNetworkInterceptor.代码区别不大。

private void testNetworkInterceptors(){OkHttpClient client = new OkHttpClient.Builder()//不同于Application Interceptor中addInterceptor().addNetworkInterceptor(new LoggingInterceptor()).build();String url = "http://blog.csdn.net/briblue";Request request = new Request.Builder().url(url).addHeader("name","frank").build();final Call call = client.newCall(request);new Thread(new Runnable() {@Overridepublic void run() {try {Response response = call.execute();response.body().close();} catch (IOException e) {e.printStackTrace();}}}).start();}
  • 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
  • 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

而log信息,也是完全一样的。说到这里大家可能有些迷惑。其实是这样的,NetworkInterceptor比Application打印更详尽的信息。我举的例子中http://blog.csdn.net/briblue没有进行重定向。如果我把上面例子中的url换成是http:www.github.com来进行测试的话。情况大有不同。


I/SeniorActivity: intercept: Sending request http://www.github.com/ on Connection{www.github.com:80, proxy=DIRECT hostAddress=www.github.com/192.30.253.113:80 cipherSuite=none protocol=http/1.1}
name: frank
host: www.github.com
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/3.4.1I/SeniorActivity: intercept: Received response for http://www.github.com/ in 1040.4ms
Content-length: 0
Location: https://www.github.com/
Connection: closeI/SeniorActivity: intercept: Sending request https://www.github.com/ on Connection{www.github.com:443, proxy=DIRECT hostAddress=www.github.com/192.30.253.113:443 cipherSuite=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 protocol=http/1.1}
name: frank
Host: www.github.com
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/3.4.1I/SeniorActivity: intercept: Received response for https://www.github.com/ in 703.6ms
Content-length: 0
Location: https://github.com/
Connection: closeI/SeniorActivity: intercept: Sending request https://github.com/ on Connection{github.com:443, proxy=DIRECT hostAddress=github.com/192.30.253.113:443 cipherSuite=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 protocol=http/1.1}
name: frank
Host: github.com
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/3.4.1I/SeniorActivity: intercept: Received response for https://github.com/ in 524.6ms Server: GitHub.com Date: Mon, 24 Oct 2016 06:17:32 GMT Content-Type: text/html; charset=utf-8 Transfer-Encoding: chunked Status: 200 OK Cache-Control: no-cache Vary: X-PJAX X-UA-Compatible: IE=Edge,chrome=1 Set-Cookie: logged_in=no; domain=.github.com; path=/; expires=Fri, 24 Oct 2036 06:17:32 -0000; secure; HttpOnly Set-Cookie: _gh_sess=eyJzZXNzaW9uX2lkIjoiNWJhMWZkYTUyYjFhMmVmZWM1OTc5ZTUzNGE0ZTQ4OTMiLCJfY3NyZl90b2tlbiI6IlZXNGVTTVhSRHhiOVlWL2E5anEwSHFxYXBOcFd3OHFvcHJtZjI5c05FOVk9In0%3D--64b03a16a3f1bf8b15208b101702a02a0419d9ec; path=/; secure; HttpOnly X-Request-Id: 9eaae01ca7d4e264de21da3d1a1c36e7 X-Runtime: 0.010049 Content-Security-Policy: default-src 'none'; base-uri 'self'; block-all-mixed-content; child-src render.githubusercontent.com; connect-src 'self' uploads.github.com status.github.com api.github.com www.google-analytics.com github-cloud.s3.amazonaws.com wss://live.github.com; font-src assets-cdn.github.com; form-action 'self' github.com gist.github.com; frame-ancestors 'none'; frame-src render.githubusercontent.com; img-src 'self' data: assets-cdn.github.com identicons.github.com collector.githubapp.com github-cloud.s3.amazonaws.com *.githubusercontent.com; media-src 'none'; script-src assets-cdn.github.com; style-src 'unsafe-inline' assets-cdn.github.com Strict-Transport-Security: max-age=31536000; includeSubdomains; preload Public-Key-Pins: max-age=5184000; X-Content-Type-Options: nosniff X-Served-By: a22dbcbd09a98eacdd14ac7804a635dd Content-Encoding: gzip X-GitHub-Request-Id: 0E17AF3E:6C7B:67BF0A4:580DA77B 
  • 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
  • 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

可以看到我们原本是访问http://www.github.com,但是它内部重定向到https://www.github.com. 
但是添加NetworkInterceptor追踪到了它的状态。并且NetworkInterceptror能够打印的信息更多。比如Content-Encoding:gzip。

Application Interceptor还是NetworkInterceptor?

它们各有优点。

Application interceptor特点
  • 不必关心url的重定向和重连。
  • 只执行一次,即使Resopnse是来自于缓存。
  • 只关心request的原始意图,而不用关心额外添加的Header信息如If-None-Match
NetworkInterceptor的特点
  • 能够详尽地追踪访问链接的重定向。
  • 短时间内的网络访问,它将不执行缓存过来的回应。
  • 监测整个网络访问过程中的数据流向。

实际开发中,大家可以根据自己的需求添加相应的Interceptor.

拦截器作用之压缩数据

因为拦截器可以拿到请求的数据,和回应的数据,所以基本上它能做任何事。比如我们可以在这里拦截一些不符合特定场景的请求。比如我们可以在回应中校验数据的完整性。比如为了节省带宽,我们可以将数据进行gzip压缩进行数据发送,然后在Response中解压,一切都神不知鬼不觉的。下面的例子来自官网,讲得是一个如何定义一个压缩数据功能的拦截器。

/** This interceptor compresses the HTTP request body. Many webservers can't handle this! */
final class GzipRequestInterceptor implements Interceptor {@Override public Response intercept(Interceptor.Chain chain) throws IOException {Request originalRequest = chain.request();if (originalRequest.body() == null || originalRequest.header("Content-Encoding") != null) {return chain.proceed(originalRequest);}Request compressedRequest = originalRequest.newBuilder().header("Content-Encoding", "gzip").method(originalRequest.method(), gzip(originalRequest.body())).build();return chain.proceed(compressedRequest);}private RequestBody gzip(final RequestBody body) {return new RequestBody() {@Override public MediaType contentType() {return body.contentType();}@Override public long contentLength() {return -1; // We don't know the compressed length in advance!}@Override public void writeTo(BufferedSink sink) throws IOException {BufferedSink gzipSink = Okio.buffer(new GzipSink(sink));body.writeTo(gzipSink);gzipSink.close();}};}
}
  • 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
  • 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

接下来,我们要讲OKHttp开发中一个很重要和实用的功能,缓存。但因为篇幅有限,我新开一篇文章来讲解。

这篇关于OKHTTP学习之高级特性的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1094508

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss

【学习笔记】 陈强-机器学习-Python-Ch15 人工神经网络(1)sklearn

系列文章目录 监督学习:参数方法 【学习笔记】 陈强-机器学习-Python-Ch4 线性回归 【学习笔记】 陈强-机器学习-Python-Ch5 逻辑回归 【课后题练习】 陈强-机器学习-Python-Ch5 逻辑回归(SAheart.csv) 【学习笔记】 陈强-机器学习-Python-Ch6 多项逻辑回归 【学习笔记 及 课后题练习】 陈强-机器学习-Python-Ch7 判别分析 【学

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识

线性代数|机器学习-P36在图中找聚类

文章目录 1. 常见图结构2. 谱聚类 感觉后面几节课的内容跨越太大,需要补充太多的知识点,教授讲得内容跨越较大,一般一节课的内容是书本上的一章节内容,所以看视频比较吃力,需要先预习课本内容后才能够很好的理解教授讲解的知识点。 1. 常见图结构 假设我们有如下图结构: Adjacency Matrix:行和列表示的是节点的位置,A[i,j]表示的第 i 个节点和第 j 个

Node.js学习记录(二)

目录 一、express 1、初识express 2、安装express 3、创建并启动web服务器 4、监听 GET&POST 请求、响应内容给客户端 5、获取URL中携带的查询参数 6、获取URL中动态参数 7、静态资源托管 二、工具nodemon 三、express路由 1、express中路由 2、路由的匹配 3、路由模块化 4、路由模块添加前缀 四、中间件