- N +

OkHttp3简单使用教程(一):请求和响应

OkHttp3简单使用教程(一):请求和响应原标题:OkHttp3简单使用教程(一):请求和响应

导读:

一,HTTP请求、响应报文格式要弄明白网络框架,首先需要先掌握Http请求的,响应的报文格式。HTTP请求报文格式:HTTP请求报文主要由请求行、请求头部、请求正文3部分组成....

文章目录 [+]

一,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 - 响应正文使用的语言

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
  1. 响应正文
    是请求响应的最终结果,都在响应体里。
    报文可以承载很多类型的数字数据:图片、视频、HTML文档、软件应用程序等

  2. 响应示例

    image.png

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

主要包含:

  • 一般的get请求

  • 一般的post请求

  • 基于Http的文件上传

  • 文件下载

  • 加载图片

  • 支持请求回调,直接返回对象、对象集合

  • 支持session的保持

  1. 添加网络访问权限并添加库依赖

??
api?'com.squareup.okhttp3:okhttp:3.9.0'
  1. HTTP的GET请求

//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();
????????????}
????????});

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

  • 发送一个GET请求的步骤,首先构造一个Request对象,参数最起码有个URL,当然也可以通过Request.Builder设置更多的参数比如:header、method等

//URL带的参数
HashMap?params?=?new?HashMap<>();
//GET?请求带的Header
HashMap?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();
  • 通过Request的对象去构造得到一个Call对象,类似于将你的请求封装成了任务,既然是任务,就会有execute(),enqueue()和cancel()等方法。
    execute():同步GET请求

?//同步
Response?response?=?call.execute()
if(response.isSuccessful()){
?????//响应成功
}

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的构造的不同。

???//POST参数构造MultipartBody.Builder,表单提交
???HashMap?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()){
?????????????//响应成功
?????????????}
??}

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);
????????}
??????}
????};
??}
}

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键值对

//MultipartBody源码,MultipartBody其实也是RequestBody?,需要在此RequestBody?体内,添加多个Part
/**?An?RFC?2387-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?parts;
??private?long?contentLength?=?-1L;
??//构造函数
??MultipartBody(ByteString?boundary,?MediaType?type,?List?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?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?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??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);
????}
??}
}

总结一下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中

OkHttpClient?client?=?new?OkHttpClient();
????????//?form?表单形式上传,MultipartBody的内容类型是表单格式,multipart/form-data
????????MultipartBody.Builder?urlBuilder=?new?MultipartBody.Builder().setType(MultipartBody.FORM);
??????
??????//参数
???????HashMap?params?=?new?HashMap<>();
???????if?(params?!=?null)?{
????????????????for?(String?key?:?params.keySet())?{
????????????????????if?(params.get(key)!=null){
????????????????????????urlBuilder.addFormDataPart(key,?params.get(key));
????????????????????}
???????????????}
????????????}
????????//需要上传的文件,需要携带上传的文件(小型文件?不建议超过500K)
?????????HashMap?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());
????????????????}
????????????}
????????});

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?blockArray?=?splitFile(path,?FILE_BLOCK_SIZE);
?/**
?????*?文件分块
?????*
?????*?@param?filePath??文件路径
?????*?@param?blockSize?块大小
?????*
?????*?@return?分块描述集合?文件不存在时返回空
?????*/
????public?static?SparseArray?splitFile(String?filePath,?long?blockSize)?{
????????File?file?=?new?File(filePath);
????????if?(!file.exists())?{
????????????return?null;
????????}
????????SparseArray?blockArray?=?new?SparseArray<>();
????????int?i?=?0;
????????int?start?=?0;
????????while?(start?

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',
????????}
????}

只需要图片的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?httpCallback,?String?md5,?String?path,?int?total,?int?index,?long?start,?long?end)?{
????????HashMap?params?=?new?HashMap();
????????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??????????返回结果需要序列化的类型声明
?????*
?????*?@return?异步post请求用的默认Runnable
?????*/
????public?static??Runnable?newPostFileBlockRunnable(final?OkHttpClient?httpClient,??final?String?url,?final?Map?headers,?final?Map?params,?final?String?fileKey,?final?File?file,?final?long?seekStart,?final?long?seekEnd,?final?Class?cls,?final?HttpListenerAdapter?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)?{
????????????????????????????????????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);
????????????????????}
????????????????}
????????????}
????????};
????}
//循环遍历所有的文章块,多线程上传
?for?(int?i?=?0;?i?()?{
?????????????????????????@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);
?????}

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?params,?HashMap?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)?{
????????????????????}
????????????????}
????????????}
????????});
????}

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


本文标题:OkHttp3简单使用教程(一):请求和响应
本文链接:http://www.kyjszj.com/wdzl/176.html
作者授权:除特别说明外,本文由?开源技术之家?原创编译并授权?开源技术之家?刊载发布。
版权声明:本文不使用任何协议授权,您可以任何形式自由转载或使用。

有好的文章希望我们帮助分享和推广,猛戳这里我要投稿

返回列表
上一篇:
下一篇:

发表评论中国互联网举报中心

快捷回复:

    评论列表 (暂无评论,共319人参与)参与讨论

    还没有评论,来说两句吧...