赞
踩
要弄明白网络框架,首先需要先掌握Http请求的,响应的报文格式。
HTTP请求报文格式:
HTTP请求报文主要由请求行、请求头部、请求正文3部分组成.
request.png
请求方法.png
image.png
HTTP响应报文格式:
HTTP响应报文主要由状态行、响应头部、响应正文3部分组成
响应报文.png
状态行:
由3部分组成,分别为:协议版本,状态码,状态码描述,之间由空格分隔
状态码:为3位数字,200-299的状态码表示成功,300-399的状态码指资源重定向,400-499的状态码指客户端请求出错,500-599的状态码指服务端出错(HTTP/1.1向协议中引入了信息性状态码,范围为100-199)
常见的:
200:响应成功
302:重定向跳转,跳转地址通过响应头中的Location属性指定
400:客户端请求有语法错误,参数错误,不能被服务器识别
403:服务器接收到请求,但是拒绝提供服务(认证失败)
404:请求资源不存在
500:服务器内部错误
image.png
响应头部 :
与请求头部类似,为响应报文添加了一些附加信息
Server - 服务器应用程序软件的名称和版本
Content-Type - 响应正文的类型(是图片还是二进制字符串)
Content-Length - 响应正文长度
Content-Charset - 响应正文使用的编码
Content-Encoding - 响应正文使用的数据压缩格式
Content-Language - 响应正文使用的语言
- Server: bfe/1.0.8.1
- Date: Sat, 04 Apr 2015 02:49:41 GMT
- Content-Type: text/html; charset=utf-8
- Vary: Accept-Encoding
- Cache-Control: private
- cxy_all: baidu+8ee3da625d74d1aa1ac9a7c34a2191dc
- Expires: Sat, 04 Apr 2015 02:49:38 GMT
- X-Powered-By: HPHP
- bdpagetype: 1
- bdqid: 0xb4eababa0002db6e
- bduserid: 0
- Set-Cookie: BDSVRTM=0; path=/
- BD_HOME=0; path=/
- H_PS_PSSID=13165_12942_1430_13075_12867_13322_12691_13348_12723_12797_13309_13325_13203_13161_13256_8498; path=/; domain=.baidu.com
- __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=/
- Content-Encoding: gzip
- X-Firefox-Spdy: 3.1
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
image.png
主要包含:
- <uses-permission android:name="android.permission.INTERNET" />
- api 'com.squareup.okhttp3:okhttp:3.9.0'
- //1,创建okHttpClient对象
- OkHttpClient mOkHttpClient = new OkHttpClient();
- //2,创建一个Request
- final Request request = new Request.Builder()
- .url("https://www.baidu.com")
- .build();
- //3,新建一个call对象
- Call call = mOkHttpClient.newCall(request);
- //4,请求加入调度,这里是异步Get请求回调
- call.enqueue(new Callback()
- {
- @Override
- public void onFailure(Request request, IOException e)
- {
- }
-
- @Override
- public void onResponse(final Response response) throws IOException
- {
- //String htmlStr = response.body().string();
- }
- });
-
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
对以上的简单请求的构成:
- //URL带的参数
- HashMap<String,String> params = new HashMap<>();
- //GET 请求带的Header
- HashMap<String,String> headers= new HashMap<>();
- //HttpUrl.Builder构造带参数url
- HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder();
- if (params != null) {
-
- for (String key : params.keySet()) {
- urlBuilder.setQueryParameter(key, params.get(key));
- }
- }
- Request request = new Request.Builder()
- .url(urlBuilder.build())
- .headers(headers == null ? new Headers.Builder().build() : Headers.of(headers))
- .get()
- .build();
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
- //同步
- Response response = call.execute()
- if(response.isSuccessful()){
- //响应成功
- }
enqueue():异步GET请求,将call加入调度队列,然后等待任务执行完成,我们在Callback中即可得到结果。
cancel():Call请求的取消,okHttp支持请求取消功能,当调用请求的cancel()时,请求就会被取消,抛出异常。又是需要监控许多Http请求的执行情况,可以把这些请求的Call搜集起来,执行完毕自动剔除,如果在请求执行过程中(如下载),想取消执行,可使用call.cancel()取消。
response.body().string()
获取;response.body().bytes()
;response.body().byteStream()
3. HTTP的POST请求
看来上面的简单的get请求,基本上整个的用法也就掌握了,比如post携带参数,也仅仅是Request的构造的不同。
- //POST参数构造MultipartBody.Builder,表单提交
- HashMap<String,String> params = new HashMap<>();
- MultipartBody.Builder urlBuilder = new MultipartBody.Builder()
- .setType(MultipartBody.FORM);
- if (params != null) {
- for (String key : params.keySet()) {
- if (params.get(key)!=null){
- urlBuilder.addFormDataPart(key, params.get(key));
- }
- //urlBuilder.addFormDataPart(key, params.get(key));
-
- }
- }
- // 构造Request->call->执行
- Request request = new Request.Builder()
- .headers(extraHeaders == null ? new Headers.Builder().build() : Headers.of(extraHeaders))//extraHeaders 是用户添加头
- .url(url)
- .post(urlBuilder.build())//参数放在body体里
- .build();
- Call call = httpClient.newCall(request);
- try (Response response = call.execute()) {
- if (response.isSuccessful()){
- //响应成功
- }
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
Post的时候,参数是包含在请求体中的,所以我们通过MultipartBody.Builder 添加多个String键值对,然后去构造RequestBody,最后完成我们Request的构造。
4. OKHTTP的上传文件
上传文件本身也是一个POST请求。在上面的POST请求中可以知道,POST请求的所有参数都是在BODY体中的,我们看看请求体的源码RequestBody:请求体=contentType + BufferedSink
RequestBody
- //抽象类请求体,**请求体=contentType + BufferedSink**
- public abstract class RequestBody {
- /** Returns the Content-Type header for this body. */
- //返回Body体的内容类型
- public abstract @Nullable MediaType contentType();
-
- /**
- * Returns the number of bytes that will be written to {@code sink} in a call to {@link #writeTo},
- * or -1 if that count is unknown.
- */
- //返回写入sink的字节长度
- public long contentLength() throws IOException {
- return -1;
- }
-
- /** Writes the content of this request to {@code sink}. */
- //写入缓存sink
- public abstract void writeTo(BufferedSink sink) throws IOException;
-
- /**
- * Returns a new request body that transmits {@code content}. If {@code contentType} is non-null
- * and lacks a charset, this will use UTF-8.
- */
- //创建一个请求体,如果contentType不等于null且缺少字符集,将使用UTF-8
- public static RequestBody create(@Nullable MediaType contentType, String content) {
- Charset charset = Util.UTF_8;
- if (contentType != null) {
- //contentType里面的字符集
- charset = contentType.charset();
- if (charset == null) {
- charset = Util.UTF_8;
- //contentType 里面加入字符集
- contentType = MediaType.parse(contentType + "; charset=utf-8");
- }
- }
- //按字符集变成字节
- byte[] bytes = content.getBytes(charset);
- return create(contentType, bytes);
- }
-
- /** Returns a new request body that transmits {@code content}. */
- //创建新的请求体,传输字节
- public static RequestBody create(
- final @Nullable MediaType contentType, final ByteString content) {
- return new RequestBody() {
- @Override public @Nullable MediaType contentType() {
- //请求体需要的内容类型
- return contentType;
- }
-
- @Override public long contentLength() throws IOException {
- //写入BufferedSink 的长度
- return content.size();
- }
-
- @Override public void writeTo(BufferedSink sink) throws IOException {
- //将需要传输的字节,写入缓存BufferedSink 中
- sink.write(content);
- }
- };
- }
-
- /** Returns a new request body that transmits {@code content}. */
- public static RequestBody create(final @Nullable MediaType contentType, final byte[] content) {
- return create(contentType, content, 0, content.length);
- }
-
- /** Returns a new request body that transmits {@code content}. */
- public static RequestBody create(final @Nullable MediaType contentType, final byte[] content,
- final int offset, final int byteCount) {
- if (content == null) throw new NullPointerException("content == null");
- Util.checkOffsetAndCount(content.length, offset, byteCount);
- return new RequestBody() {
- @Override public @Nullable MediaType contentType() {
- return contentType;
- }
-
- @Override public long contentLength() {
- return byteCount;
- }
-
- @Override public void writeTo(BufferedSink sink) throws IOException {
- sink.write(content, offset, byteCount);
- }
- };
- }
-
- /** Returns a new request body that transmits the content of {@code file}. */
- //创建一个请求体,传输文件file内容,其实就是file写入bufferedSink
- public static RequestBody create(final @Nullable MediaType contentType, final File file) {
- if (file == null) throw new NullPointerException("content == null");
-
- return new RequestBody() {
- @Override public @Nullable MediaType contentType() {
- return contentType;
- }
-
- @Override public long contentLength() {
- return file.length();
- }
-
- @Override public void writeTo(BufferedSink sink) throws IOException {
- Source source = null;
- try {
- //文件写入BufferedSink
- source = Okio.source(file);
- sink.writeAll(source);
- } finally {
- Util.closeQuietly(source);
- }
- }
- };
- }
- }
-
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
Http请求中Content-Type
客户端在进行http请求服务器的时候,需要告诉服务器请求的类型,服务器在返回给客户端的数据的时候,也需要告诉客户端返回数据的类型
默认的ContentType为 text/html 也就是网页格式. 常用的内容类型
- //MultipartBody源码,MultipartBody其实也是RequestBody ,需要在此RequestBody 体内,添加多个Part
- /** An <a href="http://www.ietf.org/rfc/rfc2387.txt">RFC 2387</a>-compliant request body. */
- public final class MultipartBody extends RequestBody {
- /**
- * The "mixed" subtype of "multipart" is intended for use when the body parts are independent and
- * need to be bundled in a particular order. Any "multipart" subtypes that an implementation does
- * not recognize must be treated as being of subtype "mixed".
- */
- //混合的内容类型
- public static final MediaType MIXED = MediaType.parse("multipart/mixed");
-
- /**
- * The "multipart/alternative" type is syntactically identical to "multipart/mixed", but the
- * semantics are different. In particular, each of the body parts is an "alternative" version of
- * the same information.
- */
- public static final MediaType ALTERNATIVE = MediaType.parse("multipart/alternative");
-
- /**
- * This type is syntactically identical to "multipart/mixed", but the semantics are different. In
- * particular, in a digest, the default {@code Content-Type} value for a body part is changed from
- * "text/plain" to "message/rfc822".
- */
- public static final MediaType DIGEST = MediaType.parse("multipart/digest");
-
- /**
- * This type is syntactically identical to "multipart/mixed", but the semantics are different. In
- * particular, in a parallel entity, the order of body parts is not significant.
- */
- 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 static final byte[] COLONSPACE = {':', ' '};
- private static final byte[] CRLF = {'\r', '\n'};
- private static final byte[] DASHDASH = {'-', '-'};
-
- private final ByteString boundary;
- private final MediaType originalType;
-
- //请求体的内容类型
- private final MediaType contentType;
- //MultiPartBody需要添加多个Part对象,一起请求
- 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 MediaType type() {
- return originalType;
- }
-
- public String boundary() {
- return boundary.utf8();
- }
-
- /** The number of parts in this multipart body. */
- //multipart 的数量
- public int size() {
- return parts.size();
- }
- //多个parts
- public List<Part> parts() {
- return parts;
- }
-
- public Part part(int index) {
- return parts.get(index);
- }
-
- /** A combination of {@link #type()} and {@link #boundary()}. */
- //MultiPart的内容类型
- @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写入BufferedSink中,传输
- @Override public void writeTo(BufferedSink sink) throws IOException {
- writeOrCountBytes(sink, false);
- }
-
- /**
- * Either writes this request to {@code sink} or measures its content length. We have one method
- * do double-duty to make sure the counting and content are consistent, particularly when it comes
- * to awkward operations like measuring the encoded length of header strings, or the
- * length-in-digits of an encoded integer.
- */
- //将每个Part的内容都写入,MultiPartBody的BufferedSink 中
- private long writeOrCountBytes(@Nullable BufferedSink sink, boolean countBytes) throws IOException {
- long byteCount = 0L;
-
- Buffer byteCountBuffer = null;
- if (countBytes) {
- sink = byteCountBuffer = new Buffer();
- }
- //写每个part
- for (int p = 0, partCount = parts.size(); p < partCount; p++) {
- Part part = parts.get(p);
- //Part的Headers和RequestBody
- Headers headers = part.headers;
- RequestBody body = part.body;
-
- sink.write(DASHDASH);
- sink.write(boundary);
- sink.write(CRLF);
-
- //Part的Headers写入sink
- if (headers != null) {
-
- for (int h = 0, headerCount = headers.size(); h < headerCount; h++) {
- sink.writeUtf8(headers.name(h))
- .write(COLONSPACE)
- .writeUtf8(headers.value(h))
- .write(CRLF);
- }
- }
- //Part的RequestBody写入Part
- //1,写contentType
- MediaType contentType = body.contentType();
- if (contentType != null) {
- sink.writeUtf8("Content-Type: ")
- .writeUtf8(contentType.toString())
- .write(CRLF);
- }
- //2,写contentLength
- long contentLength = body.contentLength();
- if (contentLength != -1) {
- sink.writeUtf8("Content-Length: ")
- .writeDecimalLong(contentLength)
- .write(CRLF);
- } else if (countBytes) {
- // We can't measure the body's size without the sizes of its components.
- byteCountBuffer.clear();
- return -1L;
- }
-
- sink.write(CRLF);
- //3,写body体
- if (countBytes) {
- byteCount += contentLength;
- } else {
- body.writeTo(sink);
- }
-
- sink.write(CRLF);
- }
-
- sink.write(DASHDASH);
- sink.write(boundary);
- sink.write(DASHDASH);
- sink.write(CRLF);
-
- if (countBytes) {
- byteCount += byteCountBuffer.size();
- byteCountBuffer.clear();
- }
-
- return byteCount;
- }
-
- /**
- * Appends a quoted-string to a StringBuilder.
- *
- * <p>RFC 2388 is rather vague about how one should escape special characters in form-data
- * parameters, and as it turns out Firefox and Chrome actually do rather different things, and
- * both say in their comments that they're not really sure what the right approach is. We go with
- * Chrome's behavior (which also experimentally seems to match what IE does), but if you actually
- * want to have a good chance of things working, please avoid double-quotes, newlines, percent
- * signs, and the like in your field names.
- */
- //装换换行符,tab符号,引号
- static StringBuilder appendQuotedString(StringBuilder target, String key) {
- target.append('"');
- for (int i = 0, len = key.length(); i < len; i++) {
- char ch = key.charAt(i);
- switch (ch) {
- case '\n':
- target.append("%0A");
- break;
- case '\r':
- target.append("%0D");
- break;
- case '"':
- target.append("%22");
- break;
- default:
- target.append(ch);
- break;
- }
- }
- target.append('"');
- return target;
- }
- //Part 的定义,Part 是由Headers+RequestBody组成
- public static final class Part {
- public static Part create(RequestBody body) {
- return create(null, body);
- }
-
- public static Part create(@Nullable Headers headers, RequestBody body) {
- if (body == null) {
- throw new NullPointerException("body == null");
- }
- //Part的headers不能存在Content-Type和Content-Length字段
- if (headers != null && headers.get("Content-Type") != null) {
- throw new IllegalArgumentException("Unexpected header: Content-Type");
- }
- if (headers != null && headers.get("Content-Length") != null) {
- throw new IllegalArgumentException("Unexpected header: Content-Length");
- }
- return new Part(headers, body);
- }
- //创建key-value的Part,name其实就是key
- public static Part createFormData(String name, String value) {
- return createFormData(name, null, RequestBody.create(null, value));
- }
- //创建key-value的Part
- public static Part createFormData(String name, @Nullable String filename, RequestBody body) {
- if (name == null) {
- throw new NullPointerException("name == null");
- }
- StringBuilder disposition = new StringBuilder("form-data; name=");
- // disposition = form-data; name=name;
- appendQuotedString(disposition, name);//对name中的特殊符号转换
-
- if (filename != null) {
- disposition.append("; filename=");
- // disposition = form-data; name=name; filename=filename;
- appendQuotedString(disposition, filename);//对filename中的特殊符号转换
- }
- //创建Part 体,Headers(Content-Disposition- form-data; name=name; filename=filename)+body
- return create(Headers.of("Content-Disposition", disposition.toString()), body);
- }
- //headers
- final @Nullable Headers headers;
- //body
- final RequestBody body;
-
- private Part(@Nullable Headers headers, RequestBody body) {
- this.headers = headers;
- this.body = body;
- }
- //Part的headers
- public @Nullable Headers headers() {
- return headers;
- }
- //Part的body体
- public RequestBody body() {
- return body;
- }
- }
-
- 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. */
- //添加Part
- public Builder addPart(RequestBody body) {
- return addPart(Part.create(body));
- }
-
- /** Add a part to the body. */
- //添加Part
- public Builder addPart(@Nullable Headers headers, RequestBody body) {
- return addPart(Part.create(headers, body));
- }
-
- /** Add a form data part to the body. */
- //添加表单数据Part
- public Builder addFormDataPart(String name, String value) {
- return addPart(Part.createFormData(name, value));
- }
-
- /** Add a form data part to the body. */
- //添加表单数据Part
- public Builder addFormDataPart(String name, @Nullable 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.");
- }
- //构建MultipartBody对象
- return new MultipartBody(boundary, type, parts);
- }
- }
- }
-
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
总结一下MultipartBody:
HTTP真正的上传文件
重点:RequestBody create(MediaType contentType, final File file)构造文件请求体RequestBody ,并且添加到MultiPartBody中
- OkHttpClient client = new OkHttpClient();
- // form 表单形式上传,MultipartBody的内容类型是表单格式,multipart/form-data
- MultipartBody.Builder urlBuilder= new MultipartBody.Builder().setType(MultipartBody.FORM);
-
- //参数
- HashMap<String,String> params = new HashMap<>();
- if (params != null) {
- for (String key : params.keySet()) {
- if (params.get(key)!=null){
- urlBuilder.addFormDataPart(key, params.get(key));
- }
- }
- }
- //需要上传的文件,需要携带上传的文件(小型文件 不建议超过500K)
- HashMap<String,String> files= new HashMap<>();
- if (files != null) {
- for (String key : files.keySet()) {
- //重点:RequestBody create(MediaType contentType, final File file)构造文件请求体RequestBody
- urlBuilder.addFormDataPart(key, files.get(key).getName(), RequestBody.create(MediaType.parse("multipart/form-data"), files.get(key)));
- }
- }
- //构造请求request
- Request request = new Request.Builder()
- .headers(extraHeaders == null ? new Headers.Builder().build() : Headers.of(extraHeaders))
- .url(url)
- .post(urlBuilder.build())
- .build();
- //异步执行请求
- newCall(request).enqueue(new Callback() {
- @Override
- public void onFailure(Call call, IOException e) {
- Log.i("lfq" ,"onFailure");
- }
-
- @Override
- public void onResponse(Call call, Response response) throws IOException {
- //非主线程
- if (response.isSuccessful()) {
- String str = response.body().string();
- Log.i("tk", response.message() + " , body " + str);
-
- } else {
- Log.i("tk" ,response.message() + " error : body " + response.body().string());
- }
- }
- });
-
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
2. 大文件分块异步上传
我们知道Post上传文件,简单的说就是将文件file封装成RequestBody体,然后添加到MultiPartBody的addPart中构造MultiPartBody所需要的Part对象(Headers+body),RequestBody是个抽象类,里面的所有create方法如下:
image.png
filebody.png
可以看出,基本都是重写了抽象类的RequestBody的三种方法,所以我们也可以继承实现自己的Body体:
image.png
EG:已上传相机图片(5M)为例,分块多线程异步同时上传,但是这种方法需要服务端接口才行。
- //文件路径
- String path = "xxx.jpg";
1,文件块对象
- public static final int FILE_BLOCK_SIZE = 500 * 1024;//500k
- /*文件块描述*/
- public static class FileBlock {
- public long start;//起始字节位置
- public long end;//结束字节位置
- public int index;//文件分块索引
- }
2,文件切块
- //计算切块,存储在数组
- final SparseArray<FileBlock> blockArray = splitFile(path, FILE_BLOCK_SIZE);
- /**
- * 文件分块
- *
- * @param filePath 文件路径
- * @param blockSize 块大小
- *
- * @return 分块描述集合 文件不存在时返回空
- */
- public static SparseArray<FileBlock> splitFile(String filePath, long blockSize) {
- File file = new File(filePath);
- if (!file.exists()) {
- return null;
- }
- SparseArray<FileBlock> blockArray = new SparseArray<>();
- int i = 0;
- int start = 0;
- while (start < file.length()) {
- i++;
- FileBlock fileBlock = new FileBlock();
- fileBlock.index = i;
- fileBlock.start = start;
- start += blockSize;
- fileBlock.end = start;
- blockArray.put(i, fileBlock);
- }
- blockArray.get(i).end = file.length();
- return blockArray;
- }
-
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
3,对文件块分块多线程异步上传
服务端的接口:
- url:domain/sync/img/upload
- method: POST
- //请求参数
- data = {
- 'img_md5': 'dddddsds',
- 'total': 10, #总的分片数
- 'index': 5, #该分片所在的位置, start by 1
- }
- 请求返回值json:
- {
- 'status': 206/205/400/409/500,
- 'msg': '分片上传成功/上传图片成功/参数错误/上传数据重复/上传失败'
- 'data': { # 205时有此字段
- 'img_url': 'https://foo.jpg',
- }
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
只需要图片的md5,总的分片数,该分片的位置,当一块传输成功时返回206,当全部块传完成是返回206,并返回该图片在服务器的url
服务端接口返回解析类:
- /**
- * 分片上传部分的接口返回
- *
- * @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}
- */
- public static class ChuckUploadData implements Serializable {
- public ChuckUploadBean data;
- public static class ChuckUploadBean implements Serializable{
- public String img_url;
- }
- /** 此块是否上传成功 */
- public boolean isPicSuccess() {
- return status == 206 || status == 409;
- }
-
- /** 全部原图是否上传成功 */
- public boolean isAllPicSuccess() {
- return status == 205;
- }
-
- public boolean isRepitition(){
- return status == 409;
- }
-
- }
- //上传图片的线程池
- ExcutorService threadPool = Executors.newCachedThreadPool();
- //上传函数
- /**
- * 上传原图,异步上传
- *
- * @param httpCallback 回调接口
- * @param md5 文件md5
- * @param path 图片路径
- * @param total 总块数
- * @param index 分块索引
- * @param start 分块开始位置
- * @param end 分块结束位置
- */
- public static void uploadBigImage(String userId, final HttpListenerAdapter<ChuckUploadData> httpCallback, String md5, String path, int total, int index, long start, long end) {
- HashMap<String, String> params = new HashMap<String, String>();
- params.put("img_uuid", uuid);//完整文件的md5
- params.put("total", String.valueOf(total));//总的分片数
- params.put("index", String.valueOf(index));//当前分片位置,从1开始
- //全局单例OKHttpClient
- OkHttpClient httpClient = DataProvider.getInstance().inkApi.getLongWaitHttpClient();
-
- Runnable httpUploadRunnable = HttpRunnableFactory.newPostFileBlockRunnable(
- httpClient,
- upload_url,//上传url,自定义
- null,
- params,//上传参数
- "image",
- new File(path),//图片文件
- start,//index块开始的位置
- end,//index块结束的位置
- ChuckUploadData.class,
- httpCallback);//回调函数
- threadManager.submit httpUploadRunnable );
- }
- /**
- * 异步post请求 表单方式拆块上传大型文件用,构造Runnable
- *
- * @param httpClient okhttp客户端
- * @param url 请求地址
- * @param headers 额外添加的header(通用header由中断器统一添加)
- * @param params 请求参数
- * @param fileKey 文件的接收用key
- * @param file 大型文件对象
- * @param seekStart 起始字节
- * @param seekEnd 结束字节
- * @param cls 返回结果需要序列化的类型
- * @param listener 异步回调
- * @param <T> 返回结果需要序列化的类型声明
- *
- * @return 异步post请求用的默认Runnable
- */
- 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) {
- return new Runnable () {
- @Override
- public void run() {
- Log.e("http", "---postfile---");
- Log.e("http", "url: " + url);
- Log.e("http", "extraHeaders: " + headers);
- Log.e("http", "params: " + params);
- Log.e("http", "filepath: " + file.getPath());
- Log.e("http", "seekStart: " + seekStart);
- Log.e("http", "seekEnd: " + seekEnd);
-
- Call call = null;
- if (listener != null) {
- listener.onStart(call);
- }
- try {
- if (TextUtils.isEmpty(url)) {
- throw new InterruptedException("url is null exception");
- }
- //构造path文件的index块的seekStart到seekEnd的请求体requestBody ,添加到MultiPartBody中
- RequestBody requestBody = new RequestBody() {
- @Override
- public MediaType contentType() {
- //请求体的内容类型
- return MediaType.parse("multipart/form-data");
- }
-
- @Override
- public void writeTo(BufferedSink sink) throws IOException {
- //切块上传
- long nowSeek = seekStart;
- long seekEndWrite = seekEnd;
- if (seekEndWrite == 0) {
- seekEndWrite = file.length();
- }
- //跳到开始位置
- FileInputStream in = new FileInputStream(file);
- if (seekStart > 0) {
- long amt = in.skip(seekStart);
- if (amt == -1) {
- nowSeek = 0;
- }
- }
- //将该块的字节内容写入body的BufferedSink 中
- int len;
- byte[] buf = new byte[BUFFER_SIZE_DEFAULT];
- while ((len = in.read(buf)) >= 0 && nowSeek < seekEndWrite) {
- sink.write(buf, 0, len);
- nowSeek += len;
- if (nowSeek + BUFFER_SIZE_DEFAULT > seekEndWrite) {
- buf = new byte[Integer.valueOf((seekEndWrite - nowSeek) + "")];
- }
- }
- closeStream(in);
- }
-
- };
- //组装其它参数
- MultipartBody.Builder urlBuilder = new MultipartBody.Builder()
- .setType(MultipartBody.FORM);
- if (params != null) {
- for (String key : params.keySet()) {
- //urlBuilder.addFormDataPart(key, params.get(key));
- if (params.get(key)!=null){
- urlBuilder.addFormDataPart(key, params.get(key));
- }
- }
- }
- //把文件块的请求体添加到MultiPartBody中
- urlBuilder.addFormDataPart(fileKey, file.getName(), requestBody);
- Request request = new Request.Builder()
- .headers(headers == null ? new Headers.Builder().build() : Headers.of(headers))
- .url(url)
- .post(urlBuilder.build())
- .build();
-
- call = httpClient.newCall(request);
- //虽说是同步调用call.execute(),但是此Http请求过程是在线程池中的,相当于异步调用
- try (Response response = call.execute()) {
- if (!response.isSuccessful()){
- throw new IOException("Unexpected code " + response.code());
- }
- /*打印json串,json样式的*/
- String json = response.body().string();
- //解析返回的响应json
- T result = JsonUtils.getObjFromStr(cls, json);
- if (listener != null) {
- //防止回调内的业务逻辑引起二次onFailure回调
- try {
- listener.onResponse(call, result);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- } finally {
-
- }
- } catch (Exception e) {
- if (listener != null) {
- //中途取消导致的中断
- if (call != null && call.isCanceled()) {
- listener.onCancel(call);
- } else {
- //其它意义上的请求失败
- listener.onFailure(call, e);
- }
- }
- } finally {
- if (listener != null) {
- listener.onEnd(call);
- }
- }
- }
- };
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
- //循环遍历所有的文章块,多线程上传
- for (int i = 0; i < blockArray.size(); i++) {
- //异步分块上传
- final FileUtil.FileBlock block = blockArray.get(i + 1);
- //提交线程池,异步上传单块
- uploadBigImage(userId, new HttpListenerAdapter<ChuckUploadData>() {
- @Override
- public void onResponse(Call call, SyncBeans.ChuckUploadData bean) {
- try {
- //单块上传
- if (bean != null ) {
- if (bean.isPicSuccess()) {
- //205,单块成功不做处理
- } else if (bean.isAllPicSuccess()) {
- //206,全部成功
- }
- }
- }catch(Exception e){}
- },uuid, mediaBean.imageNativeUrl, blockArray.size(), block.index, block.start, block.end);
- }
-
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
5. OKHttp下载文件,并通知进度
下载文件的原理其实很简单,下载过程其实就是一个GET过程(上传文件是POST过程相对应),下载文件需要在异步线程中执行(方法有二,1,使用okhttp的call.enquene()方法异步执行,2,使用call.excute()同步方法,但是在线程次中执行整个请求过程),在成功响应之后,获得网络文件输入流InputStream,然后循环读取输入流上的文件,写入文件输出流。
- /**
- * @param url 下载连接
- * @param saveDir 储存下载文件的SDCard目录
- * @param params url携带参数
- * @param extraHeaders 请求携带其他的要求的headers
- * @param listener 下载监听
- */
- public void download(final String url, final String saveDir,HashMap<String,String> params, HashMap<String,String> extraHeaders,final OnDownloadListener listener) {
- //构造请求Url
- HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder();
- if (params != null) {
- for (String key : params.keySet()) {
- if (params.get(key)!=null){
- urlBuilder.setQueryParameter(key, params.get(key));//非必须
- }
- }
- }
- //构造请求request
- Request request = new Request.Builder()
- .url(urlBuilder.build())
- .headers(extraHeaders == null ? new Headers.Builder().build() : Headers.of(extraHeaders))//headers非必须
- .get()
- .build();
- //异步执行请求
- okHttpClient.newCall(request).enqueue(new Callback() {
- @Override
- public void onFailure(Call call, IOException e) {
- // 下载失败
- listener.onDownloadFailed();
- }
- @Override
- public void onResponse(Call call, Response response) throws IOException {
- //非主线程
- InputStream is = null;
- byte[] buf = new byte[2048];
- int len = 0;
- FileOutputStream fos = null;
- // 储存下载文件的目录
- String savePath = isExistDir(saveDir);
- try {
- //获取响应的字节流
- is = response.body().byteStream();
- //文件的总大小
- long total = response.body().contentLength();
- File file = new File(savePath);
- fos = new FileOutputStream(file);
- long sum = 0;
- //循环读取输入流
- while ((len = is.read(buf)) != -1) {
- fos.write(buf, 0, len);
- sum += len;
- int progress = (int) (sum * 1.0f / total * 100);
- // 下载中
- if(listener != null){
- listener.onDownloading(progress);
- }
-
- }
- fos.flush();
- // 下载完成
- if(listener != null){
- listener.onDownloadSuccess();
- }
-
- } catch (Exception e) {
- if(listener != null){
- listener.onDownloadFailed();
- }
-
- } finally {
- try {
- if (is != null)
- is.close();
- } catch (IOException e) {
- }
- try {
- if (fos != null)
- fos.close();
- } catch (IOException e) {
- }
- }
- }
- });
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
至此,OKHTTP3的基本网络请求访问,发送GET请求,发送POST请求,基本上传文件,切块多线程异步上传文件,下载文件就到这里了,其实下载文件还可以做成断点续传,获取每次的seek点
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。