当前最流行的网络请求框架, 由Square公司开发, 在Android4.0被Google替换HttpUrlConnection的底层实现为Okhttp. 意味着官方也承认Okhttp的优秀.
这一篇主要介绍Okhttp常用使用.
关于Cookie/WebSocket/连接池/代理/Https以及SSL等进阶用法将后面再开一篇文章.
参考文档:
特点
- 创建连接池缓存响应数据来减少重复的网络请求
- 加载图片
- 支持SPDY, Google开发的基于TCP的应用层协议
- IP地址自动切换
- 支持Gzip数据传输. 自动解压Gzip
示例
OkHttp必须创建一个Request
对象才能发送网络请求, Request对象可以添加请求所需的地址,以及请求头
网络请求方式常见的有Post和Get方式
执行示意图:
导入依赖
以官方版本为准
compile 'com.squareup.okhttp3:okhttp:3.6.0'
GET请求示例
创建请求
OkHttp默认是Get请求
Request request = new Request.Builder()
.url("请求地址")
.build();
执行请求
这里是同步线程请求回调
OkHttpClient client = new OkHttpClient();
Response response = client.newCall(request).execute(); // 同步请求得到响应体
return response.body().string(); // 响应体转字符串
POST请求示例
再次演示POST请求方式提交Json数据. Post请求需要一个RequestBudy来携带数据.
public static final MediaType JSON
= MediaType.parse("application/json; charset=utf-8"); // 指定请求类型为Json
OkHttpClient client = new OkHttpClient();
String post(String url, String json) throws IOException {
RequestBody body = RequestBody.create(JSON, json); // 创建请求体
Request request = new Request.Builder()
.url(url)
.post(body) // POST请求方式且携带请求数据
.build();
Response response = client.newCall(request).execute(); // 同步请求得到响应体
return response.body().string();
}
网络请求
请求头
关键类:
- Header 请求头
- Header.Builder 请求头的构造器
Build
通过构造器Build来创建请求头
public Headers.Builder add(String line) // 添加字符串请求头
public Headers.Builder add(String name, // 参数名
String value) // 参数值
public Headers.Builder removeAll(String name) // 删除字符串同名的全部请求头参数
public Headers.Builder set(String name, // 修改请求头, 如果这个参数名不存在添加这个参数
String value) // 设置请求参数
public String get(String name) // 通过参数名得到值
public Headers build() // 创建请求头对象
Header
请求头的类是Header. 提供两个静态方法直接创建请求头.
public Map<String,List<String>> toMultimap() // 请求头生成Map集合
public static Headers of(Map<String,String> headers) // 传入一个Map集合生成请求头
public static Headers of(String... namesAndValues) // 传入偶数的字符串来创建请求头
创建请求头Header还需要添加到请求Request里面去. 往后看
请求
关键类:
- Request
- Build
- RequestBody
创建请求. 同样Request类也有个构造器Build
Build
// 直接添加请求头
public Request.Builder addHeader(String name,
String value)
// 根据参数名删除请求头
public Request.Builder removeHeader(String name)
// 根据参数名修改请求头
public Request.Builder header(String name,
String value)
// 直接添加请求头类, 会覆盖原本的请求头信息
public Request.Builder headers(Headers headers)
public Request.Builder url(HttpUrl url) // 请求链接
public Request.Builder url(String url) // 请求链接字符串
public Request.Builder url(URL url) // 请求链接Url对象
// 设置一个缓存控制器. 注意缓存控制器在这设置.
public Request.Builder cacheControl(CacheControl cacheControl)
public Request.Builder tag(Object tag) // 设置一个对象作为标记.
// 请求方式
public Request.Builder get()
public Request.Builder post(RequestBody body) // 请求体. 详情看后面
public Request.Builder head()
public Request.Builder delete(RequestBody body)
public Request.Builder delete()
public Request.Builder put(RequestBody body)
public Request.Builder patch(RequestBody body)
public Request.Builder method(String method, // 请求方式
RequestBody body) // 请求体
// 创建Request
public Request build()
Request
Request类表示请求. 无论是什么请求方式都需要创建Request这个类作为请求信息.
public HttpUrl url() // 得到链接HttpUrl对象, 后面介绍这个对象
public String method() // 请求方式
public Headers headers() // 得到请求头
public String header(String name) // 通过参数名得到请求头的值
public List<String> headers(String name) // 如果请求头包含多个参数
public RequestBody body() // 得到请求体
public Object tag() // 得到请求标记
public Request.Builder newBuilder() // 得到Request构造器
public CacheControl cacheControl() // 得到缓存控制器
public boolean isHttps() // 是否是Https请求. Https请求不了解的Google
请求体
和GET请求简单的拼接链接不同的是Post请求的请求体需要复杂的数据. 可以看到请求方法post(ResponseBody body)
需要一个参数RequestBody. 这里同样介绍他的两个子类.
RequestBody
↳ FormBody, MultipartBody
抽象类RequestBody代表请求体.
// 创建字符串请求体
public static RequestBody create(MediaType contentType,
String content)
// 创建字节请求体
public static RequestBody create(MediaType contentType,
byte[] content)
// 创建字节请求体, 支持偏移
public static RequestBody create(MediaType contentType,
byte[] content,
int offset, // 偏移
int byteCount)
// 创建文件请求体
public static RequestBody create(MediaType contentType,
File file)
public long contentLength() // 字节长度. 详情看Okio
throws IOException
// 下面介绍两个抽象方法. 只有子类FormBody和MultiparBody有效. 后面我不再多说.
public abstract MediaType contentType() // 返回MediaType
// 详情看Okio
abstract void writeTo(okio.BufferedSink sink)
public long contentLength()
throws IOException
※ 需要注意尽量避免字节数组的形式传输文件. 因为这样会加载整个文件字节到内存中容易引起内存溢出.
Post请求又分为表单请求和Json请求
- Json
- 表单
- 多类型
表单请求
表单请求是键和值想对应的数据提交方式
RequestBody requestBody = new FormBody.Builder()
.add("username", mUsername)
.add("password", mPassword)
.build();
// Request添加请求体
Request request = new Request.Builder()
.url(url)
.Post(requestBody)
.build();
表单请求的关键类:
- FormBody
- Build
Build
Build类是FormBody的内部类构造器.
public FormBody.Builder add(String name, // 表单参数名
String value) // 表单参数值
// 这个方法和上边方法的区别是会对参数进行Encode编码.
public FormBody.Builder addEncoded(String name,
String value)
public FormBody build() // 创建FormBody对象
FormBody
public int size() // 得到表单参数的数量
public String encodedName(int index) // 通过索引得到表单参数名字
public String name(int index) // 通过索引得到表单参数名字
public String encodedValue(int index) // 同上. 参数值
public String value(int index)
多类型提交
多类型提交可以提交表单参数也可以上传文件
关键类:
- MediaType
- MultipartBody
- Build
- Part
File file = new File("文件路径");
RequestBody body = RequestBody.create(null, file); // 创建文件请求体
RequestBody requestBody = new MultipartBody.Builder()
.addFormDataPart("file", file.getName(), body) // 添加文件请求体
.setType(MultipartBody.FORM) // 必须设置类型
.build();
Request request = new Request.Builder()
.url("http://mob.mgtvshop.com/JIANGTAO/upload.php")
.post(requestBody)
.build();
// 开始网络请求
new OkHttpClient().newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
// 打印返回数据
System.out.println(response.body().string());
}
});
MediaType
该类是负责指定请求的内容类型(即HTTP协议中的Content-Type). 便于服务器进行解码
Content-Type是Http协议中的请求头的键值对. 用于定义服务器或者浏览器以什么形式对传入的数据进行解析处理.
举例常用的几个:
- multipart/form-data 通用的类型
- application/x-www-form-urlencoded 表单类型
- application/json Json类型
// 创建媒体类型. 通过填写Content-Type的字符串来创建
public static MediaType parse(String string)
// 完整的Content-Type字符串输出
public String toString()
// 得到Content-Type的字符集
public Charset charset()
public Charset charset(Charset defaultValue) // 如果没有设置字符集会返回一个默认的
// 得到主类型, 即Content-Type "/"前面的字符串
public String type()
// 得到子类型, 即Content-Type"/"后面的字符串
public String subtype()
MultipartBody内部提供五个MediaType字段用于多类型提交支持
- ALTERNATIVE
- DIGEST
- FORM
- MIXED
- PARALLEL
FORM 即multipart/form-data
. 一般就都可以用这个. 觉得手写content-type字符串麻烦直接用这个. 其他的类型我暂时没用过.
Build
属于Multipart构造器. 用于创建Multipart类型参数.
支持添加类型
- RequeBody
- Part
- 表单参数
public Builder()
public Builder(String boundary) // 分隔符
public MultipartBody.Builder setType(MediaType type) // 设置Content-Type.
public MultipartBody.Builder addPart(RequestBody body)
// 附带添加请求头
public MultipartBody.Builder addPart(Headers headers,
RequestBody body)
// 添加表单参数
public MultipartBody.Builder addFormDataPart(String name,
String value)
// 添加文件对象
public MultipartBody.Builder addFormDataPart(String name,
String filename,
RequestBody body)
// 添加part
public MultipartBody.Builder addPart(MultipartBody.Part part)
public MultipartBody build()
MultipartBody
多类型的请求体. Protected权限所以无法直接创建该对象. 只能用上面的构造器创建.
Multipart是RequestBody的子类.
public MediaType type() // 媒体类型
public String boundary() // 分隔符
public int size() // 参数值数量
public List<MultipartBody.Part> parts() // 返回part集合
public MultipartBody.Part part(int index) // 通过索引得到part
public MediaType contentType() // 媒体类型和分隔符
Part
Multipart的组成部分. 看我介绍了MultipartBody的构造器就可以看出该类其实没有什么必要性.
支持添加类型:
- 添加表单参数
- 添加请求体
public static MultipartBody.Part create(RequestBody body) // 添加请求体
public static MultipartBody.Part create(Headers headers, // 请求头
RequestBody body) // 请求体
public static MultipartBody.Part createFormData(String name, // 参数名
String value) // 参数值
public static MultipartBody.Part createFormData(String name, // 参数名
String filename, // 文件名
RequestBody body) // 请求体
public Headers headers() // 得到请求头
public RequestBody body() // 得到请求体
网络响应
OkHttp请求操作完成后会返回一个Response
对象, Response包含响应信息
Response
// 状态码
public int code()
// 访问成功响应体, (状态码在200~300之间)
public boolean isSuccessful()
// 服务器响应状态信息, 如果未知返回null
public String message()
// 请求头. 用法很简单不提了
public List<String> headers(String name)
public String header(String name)
public String header(String name,
String defaultValue)
public Headers headers()
// 得到请求对象
public Request request()
// 得到缓存控制器. 绝对不为null,即使没有设置缓存控制器
public CacheControl cacheControl()
// 轻量级响应体. 对于某些响应体过大如果进行string()等操作会导致内存溢出. 使用该方法会返回一个轻量级的响应体. 如果字节超过1mb将只输出1mb的内容.
public ResponseBody peekBody(long byteCount)
throws IOException
// 返回一个不为null的响应体
public ResponseBody body()
// 构造器. 该构造器我没有使用的机会过.
public Response.Builder newBuilder()
// 该响应是否存在重定向
public boolean isRedirect()
// 关闭流. 相当于ResponseBody.close(). 谁关都算关了流.
public void close()
// 开始请求的时间戳. 如果是读取缓存则返回当初联网的时间戳
public long sentRequestAtMillis()
// 得到响应体的时间戳, 如果是读取缓存还是联网的时间戳
public long receivedResponseAtMillis()
// 返回缓存或联网的响应. 如果不是联网或者缓存的响应则会返回null
public Response networkResponse()
public Response cacheResponse()
ResponseBody
// 返回字节数组, 注意如果响应体过大会内存溢出
public final byte[] bytes()
throws IOException
// 返回字符串, 注意如果响应体过大会内存溢出
public final String string()
throws IOException
// 返回字符流
public final Reader charStream()
// 返回读取流
public final InputStream byteStream()
// 关流
public void close()
public abstract okio.BufferedSource source()
public abstract MediaType contentType()
public abstract long contentLength()
// 创建响应体
public static ResponseBody create(MediaType contentType,
String content)
public static ResponseBody create(MediaType contentType,
byte[] content)
public static ResponseBody create(MediaType contentType,
long contentLength,
okio.BufferedSource content)
执行网络请求
执行请求操作, OkHttp提供了同步和异步请求
关键类:
- Call
- OKhttpClient
同步请求示例
try {
// 获取响应对象
Response response = okHttpClient.newCall(request).execute();
if (response.isSuccessful()) {
// body为请求数据
ResponseBody body = response.body();
}
} catch (IOException e) {
e.printStackTrace();
}
异步请求示例
okHttpClient.newCall(request).enqueue(new Callback() {
/**
* 请求失败
* @param call
* @param e
*/
@Override
public void onFailure(Call call, IOException e) {
}
/**
* 请求成功
* @param call
* @param response 响应体
* @throws IOException
*/
@Override
public void onResponse(Call call, Response response) throws IOException {
// 异步请求如果需要更新UI需要回到主线程
}
});
Call
网络请求的回调处理
// 返回请求类
Request request()
// 同步请求
Response execute()
throws IOException
// 异步请求
void enqueue(Callback responseCallback)
// 取消网络请求
void cancel()
// 是否执行了同步或异步请求
boolean isExecuted()
// 网络请求是否取消
boolean isCanceled()
// 复制回调. 返回一个新的Call
Call clone()
OkhttpClient
该类是网络请求的主体. 可以开始执行网络请求
// 创建回调, 准备进行网络请求
public Call newCall(Request request)
// 创建构造器
public OkHttpClient.Builder newBuilder()
// 得到所有的拦截器
public List<Interceptor> interceptors()
public List<Interceptor> networkInterceptors()
// 缓存
public Cache cache()
Okhttp同样有个构造器Build
Build
// 设置连接超时时间
public OkHttpClient.Builder connectTimeout(long timeout,
TimeUnit unit)
// 读写超时. 关于超时后面会有详细讲解
public OkHttpClient.Builder readTimeout(long timeout,
TimeUnit unit)
public OkHttpClient.Builder writeTimeout(long timeout,
TimeUnit unit)
// 设置缓存
public OkHttpClient.Builder cache(Cache cache)
// 设置拦截器
public OkHttpClient.Builder addInterceptor(Interceptor interceptor)
public OkHttpClient.Builder addNetworkInterceptor(Interceptor interceptor)
// 得到拦截器
public List<Interceptor> interceptors()
public List<Interceptor> networkInterceptors()
// 创建客户端
public OkHttpClient build()
// 连接失败重试
public OkHttpClient.Builder retryOnConnectionFailure(boolean retryOnConnectionFailure)
拦截器
拦截器的作用是对于联网的请求和响应进行信息捕捉并且反馈. 可以理解为”抓包”.
关键类:
- Interceptor
- Intercept.Chain
自定义拦截器
Interceptor属于接口, 在使用的时候需要实现该接口, 在他的回调方法内对拦截进行自定义操作
public class LoggingInterceptor implements Interceptor {
/**
* 拦截器会在网络请求开始之前回调该方法
*
* @param chain
* @return 返回经过修改的响应体
* @throws IOException
*/
@Override
public Response intercept(Chain chain) throws IOException {
return null;
}
}
这里不得不提Intercept的方法参数Chain.
// 得到当前客户端(OkhttpClient)的请求对象
Request request()
// 得到当前客户端(OkhttpClient)的响应对象. 至于为什么需要传入参数因为你修改了请求对象响应肯定会跟着改变.
Response proceed(Request request)
throws IOException
// 进阶用法. 么用过
Connection connection()
拦截器的添加和得到需要用到OkhttpClient以及其构造器Build. 不记得请回看.
拦截器分为两个
- 应用拦截器 interceptors
- 网络拦截器 networkInterceptors
示例
public class LoggingInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
// 得到当前请求
Request original = chain.request();
// 新增加个请求头
Request.Builder requestBuilder = original.newBuilder()
.addHeader("params", "value")
.method(original.method(), original.body()); // 方法和请求体不变
Request request = requestBuilder.build();
return chain.proceed(request);
}
}
}
日志拦截器
Okhttp官方提供一个拦截器日志输出的库, 如果觉得麻烦可以直接使用这个而不自己写.
compile 'com.squareup.okhttp3:logging-interceptor:3.7.0'
用法示例:
// 创建拦截器
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
// 设置日志输出等级
logging.setLevel(Level.BASIC);
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(logging) // 添加拦截器
.build();
自定义
HttpLoggingInterceptor logging = new HttpLoggingInterceptor(new Logger() {
@Override public void log(String message) {
Timber.tag("OkHttp").d(message);
}
});
在上面的setLevel中. 传入了一个HttpLoggingInterceptor.Level.BODY
,这里还有两个属性
- Level.BODY: 一个完整的日志过滤,从发送请求到接收响应.
- Level.BASIC: 只有最基本的请求地址信息.
- Level.HEADERS: 请求头
- Level.NONE: 不输出日志
取消请求
设置标记的作用就是为了取消请求. 如果用户进入一个网络请求的页面然后马上退出(finish Activity). 在该网络请求完成后对一个已经finish的Activity的控件进行填充数据会导致Crash.
这个时候就需要在离开页面的时候对正在进行的请求结束.
设置标记
Request request = new Request.Builder()
.url(url)
.tag(tag)
.Post(requestBody)
.build();
通过标记取消请求
cancelCall(content,tag)
public static void cancelCallWithTag(String tag) {
for (Call call : RequestApi2.getInstance().getOkHttpClient().dispatcher().queuedCalls()) {
if (call.request().tag().equals(tag))
call.cancel();
}
for (Call call : RequestApi2.getInstance().getOkHttpClient().dispatcher().runningCalls()) {
if (call.request().tag().equals(tag))
call.cancel();
}
}
缓存
关键类:
- Cache
- CacheControl
- CacheControl.Build
设置缓存
由客户端OkHttpClient设置缓存
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(interceptor)
.cache(new Cache(new File(""), 1024 * 1024 * 20))
.build();
Cache
// 构造方法
public Cache(File directory, // 缓存的目录位置
long maxSize) // 缓存目录的大小. 超过以后按照LRU删除
// 得到构造方法设置的缓存参数
public File directory()
public long maxSize()
// 设置缓存key
public static String key(HttpUrl url)
// 初始化缓存信息, 如果你不调用该方法也没事, Okhttp默认在第一次使用的时候自己初始化.
// 初始化的时间根据缓存和日志文件大小有所不同. 所以你如果想提高效率可以自己提前执行该方法而不是默认.
public void initialize()
throws IOException
// 删除私有目录Cache文件夹, 即使你没用过该文件夹
public void delete()
throws IOException
// 返回缓存中的所有链接. 如果在该迭代器中删除链接那么对应缓存也被删除.
public Iterator<String> urls()
throws IOException
public int writeAbortCount()
缓存控制器
上面的通过Cache设置缓存方式只能在服务器支持缓存的情况下(即返回的请求头包含”Cache-Control”参数) 才有效. 并且所有缓存的参数限制都由服务器控制.
如果服务器没有写缓存请求头, 你就需要自己在Request.Build构造器中传入一个缓存控制器来限制缓存策略.
关于缓存参考: Http缓存
public Request.Builder cacheControl(CacheControl cacheControl)
CacheControl
包含字段
public static final CacheControl FORCE_NETWORK // 强制使用联网
public static final CacheControl FORCE_CACHE // 强制使用缓存
方法
// 缓存是否私有
public boolean isPrivate()
// 缓存是否公开
public boolean isPublic()
public boolean mustRevalidate()
public int maxStaleSeconds()
public int minFreshSeconds()
// 如果有缓存只使用缓存
public boolean onlyIfCached()
// 数据是否被转换. 例如是否进行过gzip压缩
public boolean noTransform()
// 缓存控制器说实话就是一个请求头而已. 直接通过请求体来控制缓存
public static CacheControl parse(Headers headers)
构造器
// 不使用缓存, 但会缓存数据
public CacheControl.Builder noCache()
// 不使用也不保存缓存数据
public CacheControl.Builder noStore()
// 只使用缓存, 如果没有缓存返回504错误
public CacheControl.Builder onlyIfCached()
// 缓存不超过这个事件
public CacheControl.Builder maxAge(int maxAge,
TimeUnit timeUnit) // 时间单位. 包含多种静态字段
// 客户端可接受超过多长时间的缓存
public CacheControl.Builder maxStale(int maxStale,
TimeUnit timeUnit)
// 接受一个小于一定时间内刷新的响应体
public CacheControl.Builder minFresh(int minFresh,
TimeUnit timeUnit)
// 请求服务器对数据不进行任何改变, 例如不进行压缩处理
public CacheControl.Builder noTransform()
// 创建缓存控制器
public CacheControl build()
注意Okhttp不支持Post缓存
超时
前面介绍过OkhttpClient有三个方法设置超时. 我这里详细介绍下
//设置合理的超时
setConnectTimeOut(15,TimeUitl.SECONDS);//设置连接超时
setReadTimeOut(20,TimeUitl.SECONDS);//设置读取超时
setWriteTimeOut(20,TimeUitl.SECONDS);//设置写入超时
三种超时区别:
- 连接超时: 连接到服务器的时间限制. 连接成功将不再管理任何时间
- 读取时间: 即读取服务器的数据出现阻塞. 该阻塞的时间限制. 例如在客户端读取服务器的时候服务器崩溃了
- 写入时间: 和上面差不多.