当前位置:   article > 正文

OkHttp3简单使用:请求和响应,post,get_okhttp3 bytearray.torequestbody 使用示例

okhttp3 bytearray.torequestbody 使用示例

一,HTTP请求、响应报文格式

要弄明白网络框架,首先需要先掌握Http请求的,响应的报文格式。

HTTP请求报文格式:

HTTP请求报文主要由请求行、请求头部、请求正文3部分组成.

request.png

 

  1. 请求行:由请求方法,URL,协议版本三部分构成,之间用空格隔开
    请求方法包括:POST、GET、HEAD、PUT、POST、TRACE、OPTIONS、DELETE
    协议版本:HTTP/主版本号.次版本号,常用的有HTTP/1.0和HTTP/1.1

    请求方法.png

  2. 请求头部:
    请求头部为请求报文添加了一些附加信息,由“名/值”对组成,每行一对,名和值之间使用冒号分隔
    常见请求头如下:
    Host ----接受请求的服务器地址,可以是IP:端口号,也可以是域名
    User-Agent ----发送请求的应用程序名称
    Connection ---- 指定与连接相关的属性,如Connection:Keep-Alive
    Accept-Charset ---- 通知服务端可以发送的编码格式
    Accept-Encoding ---- 通知服务端可以发送的数据压缩格式
    Accept-Language ---- 通知服务端可以发送的语言
  3. 请求正文
    可选部分,比如GET请求就没有请求正文
  4. 请求示例

    image.png

HTTP响应报文格式:

HTTP响应报文主要由状态行、响应头部、响应正文3部分组成

响应报文.png

 

  1. 状态行
    由3部分组成,分别为:协议版本,状态码,状态码描述,之间由空格分隔
    状态码:为3位数字,200-299的状态码表示成功,300-399的状态码指资源重定向,400-499的状态码指客户端请求出错,500-599的状态码指服务端出错(HTTP/1.1向协议中引入了信息性状态码,范围为100-199)
    常见的:
    200:响应成功
    302:重定向跳转,跳转地址通过响应头中的Location属性指定
    400:客户端请求有语法错误,参数错误,不能被服务器识别
    403:服务器接收到请求,但是拒绝提供服务(认证失败)
    404:请求资源不存在
    500:服务器内部错误

    image.png

     

  2. 响应头部 :
    与请求头部类似,为响应报文添加了一些附加信息
    Server - 服务器应用程序软件的名称和版本
    Content-Type - 响应正文的类型(是图片还是二进制字符串)
    Content-Length - 响应正文长度
    Content-Charset - 响应正文使用的编码
    Content-Encoding - 响应正文使用的数据压缩格式
    Content-Language - 响应正文使用的语言

  1. Server: bfe/1.0.8.1
  2. Date: Sat, 04 Apr 2015 02:49:41 GMT
  3. Content-Type: text/html; charset=utf-8
  4. Vary: Accept-Encoding
  5. Cache-Control: private
  6. cxy_all: baidu+8ee3da625d74d1aa1ac9a7c34a2191dc
  7. Expires: Sat, 04 Apr 2015 02:49:38 GMT
  8. X-Powered-By: HPHP
  9. bdpagetype: 1
  10. bdqid: 0xb4eababa0002db6e
  11. bduserid: 0
  12. Set-Cookie: BDSVRTM=0; path=/
  13. BD_HOME=0; path=/
  14. H_PS_PSSID=13165_12942_1430_13075_12867_13322_12691_13348_12723_12797_13309_13325_13203_13161_13256_8498; path=/; domain=.baidu.com
  15. __bsi=18221750326646863206_31_0_I_R_2_0303_C02F_N_I_I; expires=Sat, 04-Apr-15 02:49:46 GMT; domain=www.baidu.com; path=/
  16. Content-Encoding: gzip
  17. X-Firefox-Spdy: 3.1
  1. 响应正文
    是请求响应的最终结果,都在响应体里。
    报文可以承载很多类型的数字数据:图片、视频、HTML文档、软件应用程序等
  2. 响应示例

    image.png

二,HTTP请求和响应的基本使用

主要包含:

  • 一般的get请求
  • 一般的post请求
  • 基于Http的文件上传
  • 文件下载
  • 加载图片
  • 支持请求回调,直接返回对象、对象集合
  • 支持session的保持
  1. 添加网络访问权限并添加库依赖
  1. <uses-permission android:name="android.permission.INTERNET" />
  2. api 'com.squareup.okhttp3:okhttp:3.9.0'
  1. HTTP的GET请求
  1. //1,创建okHttpClient对象
  2. OkHttpClient mOkHttpClient = new OkHttpClient();
  3. //2,创建一个Request
  4. final Request request = new Request.Builder()
  5. .url("https://www.baidu.com")
  6. .build();
  7. //3,新建一个call对象
  8. Call call = mOkHttpClient.newCall(request);
  9. //4,请求加入调度,这里是异步Get请求回调
  10. call.enqueue(new Callback()
  11. {
  12. @Override
  13. public void onFailure(Request request, IOException e)
  14. {
  15. }
  16. @Override
  17. public void onResponse(final Response response) throws IOException
  18. {
  19. //String htmlStr = response.body().string();
  20. }
  21. });

对以上的简单请求的构成:

  • 发送一个GET请求的步骤,首先构造一个Request对象,参数最起码有个URL,当然也可以通过Request.Builder设置更多的参数比如:header、method等
  1. //URL带的参数
  2. HashMap<String,String> params = new HashMap<>();
  3. //GET 请求带的Header
  4. HashMap<String,String> headers= new HashMap<>();
  5. //HttpUrl.Builder构造带参数url
  6. HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder();
  7. if (params != null) {
  8. for (String key : params.keySet()) {
  9. urlBuilder.setQueryParameter(key, params.get(key));
  10. }
  11. }
  12. Request request = new Request.Builder()
  13. .url(urlBuilder.build())
  14. .headers(headers == null ? new Headers.Builder().build() : Headers.of(headers))
  15. .get()
  16. .build();
  • 通过Request的对象去构造得到一个Call对象,类似于将你的请求封装成了任务,既然是任务,就会有execute(),enqueue()和cancel()等方法。
    execute():同步GET请求
  1. //同步
  2. Response response = call.execute()
  3. if(response.isSuccessful()){
  4. //响应成功
  5. }

enqueue():异步GET请求,将call加入调度队列,然后等待任务执行完成,我们在Callback中即可得到结果。
cancel():Call请求的取消,okHttp支持请求取消功能,当调用请求的cancel()时,请求就会被取消,抛出异常。又是需要监控许多Http请求的执行情况,可以把这些请求的Call搜集起来,执行完毕自动剔除,如果在请求执行过程中(如下载),想取消执行,可使用call.cancel()取消。

  • 请求的响应Response
    对于同步GET请求,Response对象是直接返回的。异步GET请求,通过onResponse回调方法传参数,需要注意的是这个onResponse回调方法不是在主线程回调,可以使用runInUIThread(new Runnable(){})
    我们希望获得返回的字符串,可以通过response.body().string()获取;
    如果希望获得返回的二进制字节数组,则调用response.body().bytes()
    如果你想拿到返回的inputStream,则调用response.body().byteStream()

3. HTTP的POST请求
看来上面的简单的get请求,基本上整个的用法也就掌握了,比如post携带参数,也仅仅是Request的构造的不同。

  1. //POST参数构造MultipartBody.Builder,表单提交
  2. HashMap<String,String> params = new HashMap<>();
  3. MultipartBody.Builder urlBuilder = new MultipartBody.Builder()
  4. .setType(MultipartBody.FORM);
  5. if (params != null) {
  6. for (String key : params.keySet()) {
  7. if (params.get(key)!=null){
  8. urlBuilder.addFormDataPart(key, params.get(key));
  9. }
  10. //urlBuilder.addFormDataPart(key, params.get(key));
  11. }
  12. }
  13. // 构造Request->call->执行
  14. Request request = new Request.Builder()
  15. .headers(extraHeaders == null ? new Headers.Builder().build() : Headers.of(extraHeaders))//extraHeaders 是用户添加头
  16. .url(url)
  17. .post(urlBuilder.build())//参数放在body体里
  18. .build();
  19. Call call = httpClient.newCall(request);
  20. try (Response response = call.execute()) {
  21. if (response.isSuccessful()){
  22. //响应成功
  23. }
  24. }

Post的时候,参数是包含在请求体中的,所以我们通过MultipartBody.Builder 添加多个String键值对,然后去构造RequestBody,最后完成我们Request的构造。
4. OKHTTP的上传文件
上传文件本身也是一个POST请求。在上面的POST请求中可以知道,POST请求的所有参数都是在BODY体中的,我们看看请求体的源码RequestBody:请求体=contentType + BufferedSink
RequestBody

  1. //抽象类请求体,**请求体=contentType + BufferedSink**
  2. public abstract class RequestBody {
  3. /** Returns the Content-Type header for this body. */
  4. //返回Body体的内容类型
  5. public abstract @Nullable MediaType contentType();
  6. /**
  7. * Returns the number of bytes that will be written to {@code sink} in a call to {@link #writeTo},
  8. * or -1 if that count is unknown.
  9. */
  10. //返回写入sink的字节长度
  11. public long contentLength() throws IOException {
  12. return -1;
  13. }
  14. /** Writes the content of this request to {@code sink}. */
  15. //写入缓存sink
  16. public abstract void writeTo(BufferedSink sink) throws IOException;
  17. /**
  18. * Returns a new request body that transmits {@code content}. If {@code contentType} is non-null
  19. * and lacks a charset, this will use UTF-8.
  20. */
  21. //创建一个请求体,如果contentType不等于null且缺少字符集,将使用UTF-8
  22. public static RequestBody create(@Nullable MediaType contentType, String content) {
  23. Charset charset = Util.UTF_8;
  24. if (contentType != null) {
  25. //contentType里面的字符集
  26. charset = contentType.charset();
  27. if (charset == null) {
  28. charset = Util.UTF_8;
  29. //contentType 里面加入字符集
  30. contentType = MediaType.parse(contentType + "; charset=utf-8");
  31. }
  32. }
  33. //按字符集变成字节
  34. byte[] bytes = content.getBytes(charset);
  35. return create(contentType, bytes);
  36. }
  37. /** Returns a new request body that transmits {@code content}. */
  38. //创建新的请求体,传输字节
  39. public static RequestBody create(
  40. final @Nullable MediaType contentType, final ByteString content) {
  41. return new RequestBody() {
  42. @Override public @Nullable MediaType contentType() {
  43. //请求体需要的内容类型
  44. return contentType;
  45. }
  46. @Override public long contentLength() throws IOException {
  47. //写入BufferedSink 的长度
  48. return content.size();
  49. }
  50. @Override public void writeTo(BufferedSink sink) throws IOException {
  51. //将需要传输的字节,写入缓存BufferedSink 中
  52. sink.write(content);
  53. }
  54. };
  55. }
  56. /** Returns a new request body that transmits {@code content}. */
  57. public static RequestBody create(final @Nullable MediaType contentType, final byte[] content) {
  58. return create(contentType, content, 0, content.length);
  59. }
  60. /** Returns a new request body that transmits {@code content}. */
  61. public static RequestBody create(final @Nullable MediaType contentType, final byte[] content,
  62. final int offset, final int byteCount) {
  63. if (content == null) throw new NullPointerException("content == null");
  64. Util.checkOffsetAndCount(content.length, offset, byteCount);
  65. return new RequestBody() {
  66. @Override public @Nullable MediaType contentType() {
  67. return contentType;
  68. }
  69. @Override public long contentLength() {
  70. return byteCount;
  71. }
  72. @Override public void writeTo(BufferedSink sink) throws IOException {
  73. sink.write(content, offset, byteCount);
  74. }
  75. };
  76. }
  77. /** Returns a new request body that transmits the content of {@code file}. */
  78. //创建一个请求体,传输文件file内容,其实就是file写入bufferedSink
  79. public static RequestBody create(final @Nullable MediaType contentType, final File file) {
  80. if (file == null) throw new NullPointerException("content == null");
  81. return new RequestBody() {
  82. @Override public @Nullable MediaType contentType() {
  83. return contentType;
  84. }
  85. @Override public long contentLength() {
  86. return file.length();
  87. }
  88. @Override public void writeTo(BufferedSink sink) throws IOException {
  89. Source source = null;
  90. try {
  91. //文件写入BufferedSink
  92. source = Okio.source(file);
  93. sink.writeAll(source);
  94. } finally {
  95. Util.closeQuietly(source);
  96. }
  97. }
  98. };
  99. }
  100. }

Http请求中Content-Type
客户端在进行http请求服务器的时候,需要告诉服务器请求的类型,服务器在返回给客户端的数据的时候,也需要告诉客户端返回数据的类型
默认的ContentType为 text/html 也就是网页格式. 常用的内容类型

  • text/plain :纯文本格式 .txt
  • text/xml : XML格式 .xml
  • image/gif :gif图片格式 .gif
  • image/jpeg :jpg图片格式 .jpg
  • image/png:png图片格式 .png
  • audio/mp3 : 音频mp3格式 .mp3
  • audio/rn-mpeg :音频mpga格式 .mpga
  • video/mpeg4 : 视频mp4格式 .mp4
  • video/x-mpg : 视频mpa格式 .mpg
  • video/x-mpeg :视频mpeg格式 .mpeg
  • video/mpg : 视频mpg格式 .mpg
    以application开头的媒体格式类型:
  • application/xhtml+xml :XHTML格式
  • application/xml : XML数据格式
  • application/atom+xml :Atom XML聚合格式
  • application/json : JSON数据格式
  • application/pdf :pdf格式
  • application/msword : Word文档格式
  • application/octet-stream : 二进制流数据(如常见的文件下载)
    MultipartBody.Builder 添加多个String键值对
  1. //MultipartBody源码,MultipartBody其实也是RequestBody ,需要在此RequestBody 体内,添加多个Part
  2. /** An <a href="http://www.ietf.org/rfc/rfc2387.txt">RFC 2387</a>-compliant request body. */
  3. public final class MultipartBody extends RequestBody {
  4. /**
  5. * The "mixed" subtype of "multipart" is intended for use when the body parts are independent and
  6. * need to be bundled in a particular order. Any "multipart" subtypes that an implementation does
  7. * not recognize must be treated as being of subtype "mixed".
  8. */
  9. //混合的内容类型
  10. public static final MediaType MIXED = MediaType.parse("multipart/mixed");
  11. /**
  12. * The "multipart/alternative" type is syntactically identical to "multipart/mixed", but the
  13. * semantics are different. In particular, each of the body parts is an "alternative" version of
  14. * the same information.
  15. */
  16. public static final MediaType ALTERNATIVE = MediaType.parse("multipart/alternative");
  17. /**
  18. * This type is syntactically identical to "multipart/mixed", but the semantics are different. In
  19. * particular, in a digest, the default {@code Content-Type} value for a body part is changed from
  20. * "text/plain" to "message/rfc822".
  21. */
  22. public static final MediaType DIGEST = MediaType.parse("multipart/digest");
  23. /**
  24. * This type is syntactically identical to "multipart/mixed", but the semantics are different. In
  25. * particular, in a parallel entity, the order of body parts is not significant.
  26. */
  27. public static final MediaType PARALLEL = MediaType.parse("multipart/parallel");
  28. /**
  29. * The media-type multipart/form-data follows the rules of all multipart MIME data streams as
  30. * outlined in RFC 2046. In forms, there are a series of fields to be supplied by the user who
  31. * fills out the form. Each field has a name. Within a given form, the names are unique.
  32. */
  33. public static final MediaType FORM = MediaType.parse("multipart/form-data");
  34. private static final byte[] COLONSPACE = {':', ' '};
  35. private static final byte[] CRLF = {'\r', '\n'};
  36. private static final byte[] DASHDASH = {'-', '-'};
  37. private final ByteString boundary;
  38. private final MediaType originalType;
  39. //请求体的内容类型
  40. private final MediaType contentType;
  41. //MultiPartBody需要添加多个Part对象,一起请求
  42. private final List<Part> parts;
  43. private long contentLength = -1L;
  44. //构造函数
  45. MultipartBody(ByteString boundary, MediaType type, List<Part> parts) {
  46. this.boundary = boundary;
  47. this.originalType = type;
  48. this.contentType = MediaType.parse(type + "; boundary=" + boundary.utf8());
  49. this.parts = Util.immutableList(parts);
  50. }
  51. public MediaType type() {
  52. return originalType;
  53. }
  54. public String boundary() {
  55. return boundary.utf8();
  56. }
  57. /** The number of parts in this multipart body. */
  58. //multipart 的数量
  59. public int size() {
  60. return parts.size();
  61. }
  62. //多个parts
  63. public List<Part> parts() {
  64. return parts;
  65. }
  66. public Part part(int index) {
  67. return parts.get(index);
  68. }
  69. /** A combination of {@link #type()} and {@link #boundary()}. */
  70. //MultiPart的内容类型
  71. @Override public MediaType contentType() {
  72. return contentType;
  73. }
  74. @Override public long contentLength() throws IOException {
  75. long result = contentLength;
  76. if (result != -1L) return result;
  77. return contentLength = writeOrCountBytes(null, true);
  78. }
  79. //将每个part写入BufferedSink中,传输
  80. @Override public void writeTo(BufferedSink sink) throws IOException {
  81. writeOrCountBytes(sink, false);
  82. }
  83. /**
  84. * Either writes this request to {@code sink} or measures its content length. We have one method
  85. * do double-duty to make sure the counting and content are consistent, particularly when it comes
  86. * to awkward operations like measuring the encoded length of header strings, or the
  87. * length-in-digits of an encoded integer.
  88. */
  89. //将每个Part的内容都写入,MultiPartBody的BufferedSink 中
  90. private long writeOrCountBytes(@Nullable BufferedSink sink, boolean countBytes) throws IOException {
  91. long byteCount = 0L;
  92. Buffer byteCountBuffer = null;
  93. if (countBytes) {
  94. sink = byteCountBuffer = new Buffer();
  95. }
  96. //写每个part
  97. for (int p = 0, partCount = parts.size(); p < partCount; p++) {
  98. Part part = parts.get(p);
  99. //Part的Headers和RequestBody
  100. Headers headers = part.headers;
  101. RequestBody body = part.body;
  102. sink.write(DASHDASH);
  103. sink.write(boundary);
  104. sink.write(CRLF);
  105. //Part的Headers写入sink
  106. if (headers != null) {
  107. for (int h = 0, headerCount = headers.size(); h < headerCount; h++) {
  108. sink.writeUtf8(headers.name(h))
  109. .write(COLONSPACE)
  110. .writeUtf8(headers.value(h))
  111. .write(CRLF);
  112. }
  113. }
  114. //Part的RequestBody写入Part
  115. //1,写contentType
  116. MediaType contentType = body.contentType();
  117. if (contentType != null) {
  118. sink.writeUtf8("Content-Type: ")
  119. .writeUtf8(contentType.toString())
  120. .write(CRLF);
  121. }
  122. //2,写contentLength
  123. long contentLength = body.contentLength();
  124. if (contentLength != -1) {
  125. sink.writeUtf8("Content-Length: ")
  126. .writeDecimalLong(contentLength)
  127. .write(CRLF);
  128. } else if (countBytes) {
  129. // We can't measure the body's size without the sizes of its components.
  130. byteCountBuffer.clear();
  131. return -1L;
  132. }
  133. sink.write(CRLF);
  134. //3,写body体
  135. if (countBytes) {
  136. byteCount += contentLength;
  137. } else {
  138. body.writeTo(sink);
  139. }
  140. sink.write(CRLF);
  141. }
  142. sink.write(DASHDASH);
  143. sink.write(boundary);
  144. sink.write(DASHDASH);
  145. sink.write(CRLF);
  146. if (countBytes) {
  147. byteCount += byteCountBuffer.size();
  148. byteCountBuffer.clear();
  149. }
  150. return byteCount;
  151. }
  152. /**
  153. * Appends a quoted-string to a StringBuilder.
  154. *
  155. * <p>RFC 2388 is rather vague about how one should escape special characters in form-data
  156. * parameters, and as it turns out Firefox and Chrome actually do rather different things, and
  157. * both say in their comments that they're not really sure what the right approach is. We go with
  158. * Chrome's behavior (which also experimentally seems to match what IE does), but if you actually
  159. * want to have a good chance of things working, please avoid double-quotes, newlines, percent
  160. * signs, and the like in your field names.
  161. */
  162. //装换换行符,tab符号,引号
  163. static StringBuilder appendQuotedString(StringBuilder target, String key) {
  164. target.append('"');
  165. for (int i = 0, len = key.length(); i < len; i++) {
  166. char ch = key.charAt(i);
  167. switch (ch) {
  168. case '\n':
  169. target.append("%0A");
  170. break;
  171. case '\r':
  172. target.append("%0D");
  173. break;
  174. case '"':
  175. target.append("%22");
  176. break;
  177. default:
  178. target.append(ch);
  179. break;
  180. }
  181. }
  182. target.append('"');
  183. return target;
  184. }
  185. //Part 的定义,Part 是由Headers+RequestBody组成
  186. public static final class Part {
  187. public static Part create(RequestBody body) {
  188. return create(null, body);
  189. }
  190. public static Part create(@Nullable Headers headers, RequestBody body) {
  191. if (body == null) {
  192. throw new NullPointerException("body == null");
  193. }
  194. //Part的headers不能存在Content-Type和Content-Length字段
  195. if (headers != null && headers.get("Content-Type") != null) {
  196. throw new IllegalArgumentException("Unexpected header: Content-Type");
  197. }
  198. if (headers != null && headers.get("Content-Length") != null) {
  199. throw new IllegalArgumentException("Unexpected header: Content-Length");
  200. }
  201. return new Part(headers, body);
  202. }
  203. //创建key-value的Part,name其实就是key
  204. public static Part createFormData(String name, String value) {
  205. return createFormData(name, null, RequestBody.create(null, value));
  206. }
  207. //创建key-value的Part
  208. public static Part createFormData(String name, @Nullable String filename, RequestBody body) {
  209. if (name == null) {
  210. throw new NullPointerException("name == null");
  211. }
  212. StringBuilder disposition = new StringBuilder("form-data; name=");
  213. // disposition = form-data; name=name;
  214. appendQuotedString(disposition, name);//对name中的特殊符号转换
  215. if (filename != null) {
  216. disposition.append("; filename=");
  217. // disposition = form-data; name=name; filename=filename;
  218. appendQuotedString(disposition, filename);//对filename中的特殊符号转换
  219. }
  220. //创建Part 体,Headers(Content-Disposition- form-data; name=name; filename=filename)+body
  221. return create(Headers.of("Content-Disposition", disposition.toString()), body);
  222. }
  223. //headers
  224. final @Nullable Headers headers;
  225. //body
  226. final RequestBody body;
  227. private Part(@Nullable Headers headers, RequestBody body) {
  228. this.headers = headers;
  229. this.body = body;
  230. }
  231. //Part的headers
  232. public @Nullable Headers headers() {
  233. return headers;
  234. }
  235. //Part的body体
  236. public RequestBody body() {
  237. return body;
  238. }
  239. }
  240. public static final class Builder {
  241. private final ByteString boundary;
  242. private MediaType type = MIXED;
  243. private final List<Part> parts = new ArrayList<>();
  244. public Builder() {
  245. this(UUID.randomUUID().toString());
  246. }
  247. public Builder(String boundary) {
  248. this.boundary = ByteString.encodeUtf8(boundary);
  249. }
  250. /**
  251. * Set the MIME type. Expected values for {@code type} are {@link #MIXED} (the default), {@link
  252. * #ALTERNATIVE}, {@link #DIGEST}, {@link #PARALLEL} and {@link #FORM}.
  253. */
  254. public Builder setType(MediaType type) {
  255. if (type == null) {
  256. throw new NullPointerException("type == null");
  257. }
  258. if (!type.type().equals("multipart")) {
  259. throw new IllegalArgumentException("multipart != " + type);
  260. }
  261. this.type = type;
  262. return this;
  263. }
  264. /** Add a part to the body. */
  265. //添加Part
  266. public Builder addPart(RequestBody body) {
  267. return addPart(Part.create(body));
  268. }
  269. /** Add a part to the body. */
  270. //添加Part
  271. public Builder addPart(@Nullable Headers headers, RequestBody body) {
  272. return addPart(Part.create(headers, body));
  273. }
  274. /** Add a form data part to the body. */
  275. //添加表单数据Part
  276. public Builder addFormDataPart(String name, String value) {
  277. return addPart(Part.createFormData(name, value));
  278. }
  279. /** Add a form data part to the body. */
  280. //添加表单数据Part
  281. public Builder addFormDataPart(String name, @Nullable String filename, RequestBody body) {
  282. return addPart(Part.createFormData(name, filename, body));
  283. }
  284. /** Add a part to the body. */
  285. public Builder addPart(Part part) {
  286. if (part == null) throw new NullPointerException("part == null");
  287. parts.add(part);
  288. return this;
  289. }
  290. /** Assemble the specified parts into a request body. */
  291. public MultipartBody build() {
  292. if (parts.isEmpty()) {
  293. throw new IllegalStateException("Multipart body must have at least one part.");
  294. }
  295. //构建MultipartBody对象
  296. return new MultipartBody(boundary, type, parts);
  297. }
  298. }
  299. }

总结一下MultipartBody:

  1. MultipartBody本质一个是一个RequestBody,具有自己的contentType+BufferedSink,是POST请求的最外层封装,需要添加多个Part
  2. Part对象组成:Headers+RequestBody。是MultipartBody的成员变量,需要写入MultipartBody的BufferedSink中。

HTTP真正的上传文件

  1. 最基本的上传文件:

重点:RequestBody create(MediaType contentType, final File file)构造文件请求体RequestBody ,并且添加到MultiPartBody中

  1. OkHttpClient client = new OkHttpClient();
  2. // form 表单形式上传,MultipartBody的内容类型是表单格式,multipart/form-data
  3. MultipartBody.Builder urlBuilder= new MultipartBody.Builder().setType(MultipartBody.FORM);
  4. //参数
  5. HashMap<String,String> params = new HashMap<>();
  6. if (params != null) {
  7. for (String key : params.keySet()) {
  8. if (params.get(key)!=null){
  9. urlBuilder.addFormDataPart(key, params.get(key));
  10. }
  11. }
  12. }
  13. //需要上传的文件,需要携带上传的文件(小型文件 不建议超过500K)
  14. HashMap<String,String> files= new HashMap<>();
  15. if (files != null) {
  16. for (String key : files.keySet()) {
  17. //重点:RequestBody create(MediaType contentType, final File file)构造文件请求体RequestBody
  18. urlBuilder.addFormDataPart(key, files.get(key).getName(), RequestBody.create(MediaType.parse("multipart/form-data"), files.get(key)));
  19. }
  20. }
  21. //构造请求request
  22. Request request = new Request.Builder()
  23. .headers(extraHeaders == null ? new Headers.Builder().build() : Headers.of(extraHeaders))
  24. .url(url)
  25. .post(urlBuilder.build())
  26. .build();
  27. //异步执行请求
  28. newCall(request).enqueue(new Callback() {
  29. @Override
  30. public void onFailure(Call call, IOException e) {
  31. Log.i("lfq" ,"onFailure");
  32. }
  33. @Override
  34. public void onResponse(Call call, Response response) throws IOException {
  35. //非主线程
  36. if (response.isSuccessful()) {
  37. String str = response.body().string();
  38. Log.i("tk", response.message() + " , body " + str);
  39. } else {
  40. Log.i("tk" ,response.message() + " error : body " + response.body().string());
  41. }
  42. }
  43. });

2. 大文件分块异步上传
我们知道Post上传文件,简单的说就是将文件file封装成RequestBody体,然后添加到MultiPartBody的addPart中构造MultiPartBody所需要的Part对象(Headers+body),RequestBody是个抽象类,里面的所有create方法如下:

image.png

 

filebody.png


可以看出,基本都是重写了抽象类的RequestBody的三种方法,所以我们也可以继承实现自己的Body体:

image.png


EG:已上传相机图片(5M)为例,分块多线程异步同时上传,但是这种方法需要服务端接口才行。

 

  1. //文件路径
  2. String path = "xxx.jpg";

1,文件块对象

  1. public static final int FILE_BLOCK_SIZE = 500 * 1024;//500k
  2. /*文件块描述*/
  3. public static class FileBlock {
  4. public long start;//起始字节位置
  5. public long end;//结束字节位置
  6. public int index;//文件分块索引
  7. }

2,文件切块

  1. //计算切块,存储在数组
  2. final SparseArray<FileBlock> blockArray = splitFile(path, FILE_BLOCK_SIZE);
  3. /**
  4. * 文件分块
  5. *
  6. * @param filePath 文件路径
  7. * @param blockSize 块大小
  8. *
  9. * @return 分块描述集合 文件不存在时返回空
  10. */
  11. public static SparseArray<FileBlock> splitFile(String filePath, long blockSize) {
  12. File file = new File(filePath);
  13. if (!file.exists()) {
  14. return null;
  15. }
  16. SparseArray<FileBlock> blockArray = new SparseArray<>();
  17. int i = 0;
  18. int start = 0;
  19. while (start < file.length()) {
  20. i++;
  21. FileBlock fileBlock = new FileBlock();
  22. fileBlock.index = i;
  23. fileBlock.start = start;
  24. start += blockSize;
  25. fileBlock.end = start;
  26. blockArray.put(i, fileBlock);
  27. }
  28. blockArray.get(i).end = file.length();
  29. return blockArray;
  30. }

3,对文件块分块多线程异步上传
服务端的接口:

  1. url:domain/sync/img/upload
  2. method: POST
  3. //请求参数
  4. data = {
  5. 'img_md5': 'dddddsds',
  6. 'total': 10, #总的分片数
  7. 'index': 5, #该分片所在的位置, start by 1
  8. }
  9. 请求返回值json:
  10. {
  11. 'status': 206/205/400/409/500,
  12. 'msg': '分片上传成功/上传图片成功/参数错误/上传数据重复/上传失败'
  13. 'data': { # 205时有此字段
  14. 'img_url': 'https://foo.jpg',
  15. }
  16. }

只需要图片的md5,总的分片数,该分片的位置,当一块传输成功时返回206,当全部块传完成是返回206,并返回该图片在服务器的url
服务端接口返回解析类:

  1. /**
  2. * 分片上传部分的接口返回
  3. *
  4. * @link {http://10.16.69.11:5000/iSync/iSync%E6%9C%8D%E5%8A%A1%E7%AB%AFv4%E6%96%87%E6%A1%A3/index.html#4_1}
  5. */
  6. public static class ChuckUploadData implements Serializable {
  7. public ChuckUploadBean data;
  8. public static class ChuckUploadBean implements Serializable{
  9. public String img_url;
  10. }
  11. /** 此块是否上传成功 */
  12. public boolean isPicSuccess() {
  13. return status == 206 || status == 409;
  14. }
  15. /** 全部原图是否上传成功 */
  16. public boolean isAllPicSuccess() {
  17. return status == 205;
  18. }
  19. public boolean isRepitition(){
  20. return status == 409;
  21. }
  22. }
  23. //上传图片的线程池
  24. ExcutorService threadPool = Executors.newCachedThreadPool();
  25. //上传函数
  26. /**
  27. * 上传原图,异步上传
  28. *
  29. * @param httpCallback 回调接口
  30. * @param md5 文件md5
  31. * @param path 图片路径
  32. * @param total 总块数
  33. * @param index 分块索引
  34. * @param start 分块开始位置
  35. * @param end 分块结束位置
  36. */
  37. public static void uploadBigImage(String userId, final HttpListenerAdapter<ChuckUploadData> httpCallback, String md5, String path, int total, int index, long start, long end) {
  38. HashMap<String, String> params = new HashMap<String, String>();
  39. params.put("img_uuid", uuid);//完整文件的md5
  40. params.put("total", String.valueOf(total));//总的分片数
  41. params.put("index", String.valueOf(index));//当前分片位置,从1开始
  42. //全局单例OKHttpClient
  43. OkHttpClient httpClient = DataProvider.getInstance().inkApi.getLongWaitHttpClient();
  44. Runnable httpUploadRunnable = HttpRunnableFactory.newPostFileBlockRunnable(
  45. httpClient,
  46. upload_url,//上传url,自定义
  47. null,
  48. params,//上传参数
  49. "image",
  50. new File(path),//图片文件
  51. start,//index块开始的位置
  52. end,//index块结束的位置
  53. ChuckUploadData.class,
  54. httpCallback);//回调函数
  55. threadManager.submit httpUploadRunnable );
  56. }
  57. /**
  58. * 异步post请求 表单方式拆块上传大型文件用,构造Runnable
  59. *
  60. * @param httpClient okhttp客户端
  61. * @param url 请求地址
  62. * @param headers 额外添加的header(通用header由中断器统一添加)
  63. * @param params 请求参数
  64. * @param fileKey 文件的接收用key
  65. * @param file 大型文件对象
  66. * @param seekStart 起始字节
  67. * @param seekEnd 结束字节
  68. * @param cls 返回结果需要序列化的类型
  69. * @param listener 异步回调
  70. * @param <T> 返回结果需要序列化的类型声明
  71. *
  72. * @return 异步post请求用的默认Runnable
  73. */
  74. public static <T> Runnable newPostFileBlockRunnable(final OkHttpClient httpClient, final String url, final Map<String, String> headers, final Map<String, String> params, final String fileKey, final File file, final long seekStart, final long seekEnd, final Class<T> cls, final HttpListenerAdapter<T> listener) {
  75. return new Runnable () {
  76. @Override
  77. public void run() {
  78. Log.e("http", "---postfile---");
  79. Log.e("http", "url: " + url);
  80. Log.e("http", "extraHeaders: " + headers);
  81. Log.e("http", "params: " + params);
  82. Log.e("http", "filepath: " + file.getPath());
  83. Log.e("http", "seekStart: " + seekStart);
  84. Log.e("http", "seekEnd: " + seekEnd);
  85. Call call = null;
  86. if (listener != null) {
  87. listener.onStart(call);
  88. }
  89. try {
  90. if (TextUtils.isEmpty(url)) {
  91. throw new InterruptedException("url is null exception");
  92. }
  93. //构造path文件的index块的seekStart到seekEnd的请求体requestBody ,添加到MultiPartBody中
  94. RequestBody requestBody = new RequestBody() {
  95. @Override
  96. public MediaType contentType() {
  97. //请求体的内容类型
  98. return MediaType.parse("multipart/form-data");
  99. }
  100. @Override
  101. public void writeTo(BufferedSink sink) throws IOException {
  102. //切块上传
  103. long nowSeek = seekStart;
  104. long seekEndWrite = seekEnd;
  105. if (seekEndWrite == 0) {
  106. seekEndWrite = file.length();
  107. }
  108. //跳到开始位置
  109. FileInputStream in = new FileInputStream(file);
  110. if (seekStart > 0) {
  111. long amt = in.skip(seekStart);
  112. if (amt == -1) {
  113. nowSeek = 0;
  114. }
  115. }
  116. //将该块的字节内容写入body的BufferedSink 中
  117. int len;
  118. byte[] buf = new byte[BUFFER_SIZE_DEFAULT];
  119. while ((len = in.read(buf)) >= 0 && nowSeek < seekEndWrite) {
  120. sink.write(buf, 0, len);
  121. nowSeek += len;
  122. if (nowSeek + BUFFER_SIZE_DEFAULT > seekEndWrite) {
  123. buf = new byte[Integer.valueOf((seekEndWrite - nowSeek) + "")];
  124. }
  125. }
  126. closeStream(in);
  127. }
  128. };
  129. //组装其它参数
  130. MultipartBody.Builder urlBuilder = new MultipartBody.Builder()
  131. .setType(MultipartBody.FORM);
  132. if (params != null) {
  133. for (String key : params.keySet()) {
  134. //urlBuilder.addFormDataPart(key, params.get(key));
  135. if (params.get(key)!=null){
  136. urlBuilder.addFormDataPart(key, params.get(key));
  137. }
  138. }
  139. }
  140. //把文件块的请求体添加到MultiPartBody中
  141. urlBuilder.addFormDataPart(fileKey, file.getName(), requestBody);
  142. Request request = new Request.Builder()
  143. .headers(headers == null ? new Headers.Builder().build() : Headers.of(headers))
  144. .url(url)
  145. .post(urlBuilder.build())
  146. .build();
  147. call = httpClient.newCall(request);
  148. //虽说是同步调用call.execute(),但是此Http请求过程是在线程池中的,相当于异步调用
  149. try (Response response = call.execute()) {
  150. if (!response.isSuccessful()){
  151. throw new IOException("Unexpected code " + response.code());
  152. }
  153. /*打印json串,json样式的*/
  154. String json = response.body().string();
  155. //解析返回的响应json
  156. T result = JsonUtils.getObjFromStr(cls, json);
  157. if (listener != null) {
  158. //防止回调内的业务逻辑引起二次onFailure回调
  159. try {
  160. listener.onResponse(call, result);
  161. } catch (Exception e) {
  162. e.printStackTrace();
  163. }
  164. }
  165. } finally {
  166. }
  167. } catch (Exception e) {
  168. if (listener != null) {
  169. //中途取消导致的中断
  170. if (call != null && call.isCanceled()) {
  171. listener.onCancel(call);
  172. } else {
  173. //其它意义上的请求失败
  174. listener.onFailure(call, e);
  175. }
  176. }
  177. } finally {
  178. if (listener != null) {
  179. listener.onEnd(call);
  180. }
  181. }
  182. }
  183. };
  184. }
  1. //循环遍历所有的文章块,多线程上传
  2. for (int i = 0; i < blockArray.size(); i++) {
  3. //异步分块上传
  4. final FileUtil.FileBlock block = blockArray.get(i + 1);
  5. //提交线程池,异步上传单块
  6. uploadBigImage(userId, new HttpListenerAdapter<ChuckUploadData>() {
  7. @Override
  8. public void onResponse(Call call, SyncBeans.ChuckUploadData bean) {
  9. try {
  10. //单块上传
  11. if (bean != null ) {
  12. if (bean.isPicSuccess()) {
  13. //205,单块成功不做处理
  14. } else if (bean.isAllPicSuccess()) {
  15. //206,全部成功
  16. }
  17. }
  18. }catch(Exception e){}
  19. },uuid, mediaBean.imageNativeUrl, blockArray.size(), block.index, block.start, block.end);
  20. }

5. OKHttp下载文件,并通知进度

下载文件的原理其实很简单,下载过程其实就是一个GET过程(上传文件是POST过程相对应),下载文件需要在异步线程中执行(方法有二,1,使用okhttp的call.enquene()方法异步执行,2,使用call.excute()同步方法,但是在线程次中执行整个请求过程),在成功响应之后,获得网络文件输入流InputStream,然后循环读取输入流上的文件,写入文件输出流。

  1. /**
  2. * @param url 下载连接
  3. * @param saveDir 储存下载文件的SDCard目录
  4. * @param params url携带参数
  5. * @param extraHeaders 请求携带其他的要求的headers
  6. * @param listener 下载监听
  7. */
  8. public void download(final String url, final String saveDir,HashMap<String,String> params, HashMap<String,String> extraHeaders,final OnDownloadListener listener) {
  9. //构造请求Url
  10. HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder();
  11. if (params != null) {
  12. for (String key : params.keySet()) {
  13. if (params.get(key)!=null){
  14. urlBuilder.setQueryParameter(key, params.get(key));//非必须
  15. }
  16. }
  17. }
  18. //构造请求request
  19. Request request = new Request.Builder()
  20. .url(urlBuilder.build())
  21. .headers(extraHeaders == null ? new Headers.Builder().build() : Headers.of(extraHeaders))//headers非必须
  22. .get()
  23. .build();
  24. //异步执行请求
  25. okHttpClient.newCall(request).enqueue(new Callback() {
  26. @Override
  27. public void onFailure(Call call, IOException e) {
  28. // 下载失败
  29. listener.onDownloadFailed();
  30. }
  31. @Override
  32. public void onResponse(Call call, Response response) throws IOException {
  33. //非主线程
  34. InputStream is = null;
  35. byte[] buf = new byte[2048];
  36. int len = 0;
  37. FileOutputStream fos = null;
  38. // 储存下载文件的目录
  39. String savePath = isExistDir(saveDir);
  40. try {
  41. //获取响应的字节流
  42. is = response.body().byteStream();
  43. //文件的总大小
  44. long total = response.body().contentLength();
  45. File file = new File(savePath);
  46. fos = new FileOutputStream(file);
  47. long sum = 0;
  48. //循环读取输入流
  49. while ((len = is.read(buf)) != -1) {
  50. fos.write(buf, 0, len);
  51. sum += len;
  52. int progress = (int) (sum * 1.0f / total * 100);
  53. // 下载中
  54. if(listener != null){
  55. listener.onDownloading(progress);
  56. }
  57. }
  58. fos.flush();
  59. // 下载完成
  60. if(listener != null){
  61. listener.onDownloadSuccess();
  62. }
  63. } catch (Exception e) {
  64. if(listener != null){
  65. listener.onDownloadFailed();
  66. }
  67. } finally {
  68. try {
  69. if (is != null)
  70. is.close();
  71. } catch (IOException e) {
  72. }
  73. try {
  74. if (fos != null)
  75. fos.close();
  76. } catch (IOException e) {
  77. }
  78. }
  79. }
  80. });
  81. }

至此,OKHTTP3的基本网络请求访问,发送GET请求,发送POST请求,基本上传文件,切块多线程异步上传文件,下载文件就到这里了,其实下载文件还可以做成断点续传,获取每次的seek点

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/blog/article/detail/45631
推荐阅读
相关标签
  

闽ICP备14008679号