MVP+okHttp+Retrofit+RxJava+Glide+Dagger 是现在最流行的一套技术框架, MVP 现在没心思去弄,所以先把后面的都给学习了再说。 okHttp 的版本现在已经到3了,综合来说是比较好的一个网络请求框架,相比Volley,它可以上传大数据就是个优胜了,而且 okHttp 还处理了代理服务器问题和 SSL 握手失败等等很多问题,反正就是选它了。
用Android Studio开发,当然要导入 okHttp 库,而 okHttp 内部依赖 okio ,所以最好也导入 okio 库(下面两个库是我刚刚在 AS 里面搜索到的最新的版本):
compile 'com.squareup.okhttp3:okhttp:3.5.0'
compile 'com.squareup.okio:okio:1.11.0'
使用 okHttp 请求网络,主要是请求( Request )和响应( Response )两部分,请求方式有 get、post 两种,又可以分为同步和异步两种方式。但说起来主要流程有三步:
1.获得 okHttpClient 对象;
2.构建 Request 对象,利用 Request 对象获得 Call 对象;
3.执行请求(同步/异步),返回并处理响应结果。
获得 okHttpClient 对象
简单写法:
OkHttpClient mOkHttpClient = new OkHttpClient();
添加一些设置的较为复杂的写法:
// 获得 OkHttpClient 对象
OkHttpClient mOkHttpClient = new OkHttpClient();
// 获得 OkHttpClient.Builder 对象
OkHttpClient.Builder builder = mOkHttpClient.newBuilder();
// 设置
builder.connectTimeout(10, TimeUnit.SECONDS)//设置超时时间
.readTimeout(10, TimeUnit.SECONDS)//设置读取超时时间
.writeTimeout(10, TimeUnit.SECONDS)//设置写入超时时间
.build();
这样就可以做一些设置,OKHttp 十分强大,还可以设置拦截器、缓存等。
构建 Request 对象
到这一步, Get 请求和 Post 请求就有区别了,但大体上是一样的。先来看 Get 请求。
** Get 请求构建 Request 对象:**
// 1.获得 Request.Builder 对象
Request.Builder requestBuilder = new Request.Builder();
// 2.添加 url、header 等必要信息
requestBuilder.url("www.baidu.com").addHeader("User-Agent","android").
header("Content-Type","text/html; charset=utf-8");
// 3.构建 Request 对象
Request request = requestBuilder.build();
** Post 请求构建 Request 对象:**
// 1.获得 Request.Builder 对象
Request.Builder requestBuilder = new Request.Builder();
// 2.添加 url、header 等必要信息
requestBuilder.url("www.baidu.com").addHeader("User-Agent", "android")
.header("Content-Type", "text/html; charset=utf-8");
// 3.构建 RequestBody 对象(这里以表单形式作为例子)
FormBody requestBody = new FormBody.Builder().add("name", "value").build();
// 4.加入 RequestBody 对象构建 Request 对象
Request request = requestBuilder.post(requestBody).build();
注意:
- addHeader(String key, String value) 调用了 headers.add(String key, String value) ,而 header(String key, String value) 调用了 headers.set(String key, String value) ,所以虽然都是键值对形式添加请求头,但是, addHeader(...) 可以添加相同 key 的 header ,不会移除;而 header(...) 会移除相同 key 的 header ,只保留一个。
- Get 请求,如果有请求参数,是直接拼接在 url 之后的,以?key1=value&key2=value2的结构出现;而 post 则是把请求参数通过构建 RequestBody 对象放在了 Request 的 body 部分。
- RequestBody 对象可通过多种方式构建,以提供表单、流、字符串、文件、多块请求等各种 post 上传方式。
获得 Call 对象
Call call = mOkHttpClient.newCall(request);
执行请求,返回并处理响应结果
同步 Get / Post 请求
Response response = call.execute();
可以调用 isSuccessful() 判断请求是否成功
if (response.isSuccessful()){
// 同步请求成功的处理逻辑
} else {
// 同步请求失败的处理逻辑
}
异步 Get / Post 请求
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
// 请求失败处理逻辑
}
@Override
public void onResponse(Call call, Response response) throws IOException {
// 请求成功处理逻辑
// 可以从Response对象调用相应方法获得字符串、流、byte[]等,譬如:
// String str = response.body().string();
// byte[] bytes = response.body().bytes();
// InputStream is = response.body().byteStream();
}
});
注意:
1.上传下载功能必须得用异步的方式进行,所以调用 call.enqueue,将 call 加入调度队列,然后等待任务执行完成,在 Callback 中即可得到结果;
2.把流形式转化为字符串用 string() ,把 Object 对象转化为字符串用 toString();
3.以流形式操作就可以通过 IO 的方式写文件。这也说明了 onResponse(...) 并不是在UI线程里面执行(异步嘛),所以如果希望操作控件,还是需要使用 handler 等。
例子
同步请求
/**
* 同步Post请求
*/
public void postSyncHttp() {
// 获得OkHttpClient对象
OkHttpClient mOkHttpClient = new OkHttpClient().newBuilder()
.connectTimeout(10, TimeUnit.SECONDS)//设置超时时间
.readTimeout(10, TimeUnit.SECONDS)//设置读取超时时间
.writeTimeout(10, TimeUnit.SECONDS)//设置写入超时时间
.build();
// 获得Request对象(表单形式)
Request request = new Request.Builder()
.url("www.baidu.com")
.addHeader("User-Agent", "android")
.header("Content-Type", "text/html; charset=utf-8")
.post(new FormBody.Builder()
.add("name", "value")
.build())
.build();
// 流形式RequestBody
// RequestBody requestBody = new RequestBody() {
// @Override
// public MediaType contentType() {
// MediaType MEDIA_TYPE_MARKDOWN = MediaType.parse("text/x-markdown; charset=utf-8")
// return MEDIA_TYPE_MARKDOWN;
// }
//
// @Override
// public void writeTo(BufferedSink sink) throws IOException {
// sink.writeUtf8("Numbers\n");
// sink.writeUtf8("-------\n");
// for (int i = 2; i <= 997; i++) {
// sink.writeUtf8(String.format(" * %s = %s\n", i, factor(i)));
// }
// }
//
// private String factor(int n) {
// for (int i = 2; i < n; i++) {
// int x = n / i;
// if (x * i == n) return factor(x) + " × " + i;
// }
// return Integer.toString(n);
// }
// };
// 文件形式RequestBody
// MediaType MEDIA_TYPE_MARKDOWN
// = MediaType.parse("text/x-markdown; charset=utf-8");
// File file = new File("README.md");
// RequestBody requestBody = RequestBody.create(MEDIA_TYPE_MARKDOWN, file);
// 获得Call对象
Call call = mOkHttpClient.newCall(request);
try {
Response response = call.execute();
if (response.isSuccessful()) {
String responseStr = response.body().string();
Log.d("", "Post同步请求响应结果:" + responseStr);
} else {
Log.d("", "Post同步请求失败");
}
} catch (IOException e) {
e.printStackTrace();
}
}
异步请求
/**
* 异步Post请求
*/
public void postSyncHttp() {
// 获得OkHttpClient对象
...
同上
// 获得Request对象
...
同上
// 获得Call对象
Call call = mOkHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.d("", "Post同步请求失败");
}
@Override
public void onResponse(Call call, Response response) throws IOException {
final String responseStr = response.body().string();
Log.d("", "Post同步请求响应结果:" + responseStr);
// UI线程更新控件
runOnUiThread(new Runnable() {
@Override
public void run() {
mTextView.setText("Post同步请求响应结果:" + responseStr);
}
});
}
});
}
Get 请求忽略 .post(RequestBody requestBody) 就好。
暂时记录到这里。这就是okHttp的基本使用,缓存、Gson解析JSON响应、处理验证、每个Cell配置等等,后面再继续。
2016.12.13更新
Interceptor
Interceptor 也就是拦截器,这是一个很强大的机制,在 okHttp 中有两种拦截器:
- Application Interceptor:可缓存,对于每个 HTTP 响应都只会调用一次,可以通过不调用 Chain.proceed 方法来终止请求,也可以通过多次调用 Chain.proceed 方法来进行重试;
- Network Interceptor: 不可缓存,调用执行中的自动重定向和重试所产生的响应也会被调用,而如果响应来自缓存,则不会被调用。
在 okHttpClient 建造的时候,使用 addInterceptor() 添加拦截器,拦截器执行顺序等于添加顺序。
实现 Interceptor 接口
Interceptor 接口只包含一个方法 intercept,其参数是 Chain 对象。Chain 对象表示的是当前的拦截器链条。
1.通过 Chain 的 request 方法可以获取到当前的 Request 对象;
2.在使用完 Request 对象之后,通过 Chain 对象的 proceed 方法来继续拦截器链条的执行;
3.当执行完成之后,可以对得到的 Response 对象进行额外的处理。
看一个例子:
/**
* 实现拦截器,查看请求的 url 和响应结果
*/
public class CustomInterceptor implements Interceptor {
public Response intercept(Chain chain) throws IOException {
// 获取到当前的 Request 对象
Request request = chain.request();
// 查看发送请求的url
String url = request.url();
System.out.println("发送请求url:" + url);
// 执行,获得Response
Response response = chain.proceed(request);
// 查看响应结果
System.out.println("响应结果:" + response.body().string());
return response;
}
}
而 okHttp 提供了一个网络日志拦截器,使用的时候先要导入依赖:
compile 'com.squareup.okhttp3:logging-interceptor:3.5.0'
HttpLoggingInterceptor 提供了四种控制打印信息类型的等级,分别是:
- NONE:没有任何日志信息;
- BASIC:打印请求类型,URL,请求体大小,返回值状态以及返回值的大小;
- HEADERS:打印返回请求和返回值头部信息,请求类型,URL和返回值状态码;
- BODY:打印请求和返回值的头部和body信息。
创建一个 HttpLoggingInterceptor 对象,然后调用 setLevel() ,再将 HttpLoggingInterceptor 对象添加到 okHttpClient 即可。eg:
// 新建 HttpLoggingInterceptor 对象
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
// 设置日志等级
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
// 通过 addInterceptor() 添加到 okHttpClient 中
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(new CustomInterceptor())
.addInterceptor(logging)
...
.build();
上面的例子是应用拦截器的,拦截器真的很强大,拦截器更多的相关知识请看:Okhttp-wiki 之 Interceptors 拦截器
Cache
重要概念
Cache-Control
Cache-Control 是由服务器返回的 Response 中添加的 Header 信息,是请求和响应遵循的缓存机制。在请求消息或响应消息中设置Cache-Control并不会修改另一个消息处理过程中的缓存处理过程。目的是告诉客户端是要从本地读取缓存,还是从服务器获取消息,它有不同的值,每个值有不同的作用:
- Public:响应可被任何缓存区缓存,告诉缓存服务器,即使是对于不该缓存的内容也缓存起来;
- Private:对于单个用户的整个或部分响应消息,不能被共享缓存处理。这允许服务器仅仅描述当用户的部分响应消息,此响应消息对于其他用户的请求无效;
- no-cache:请求或响应消息不能缓存;
- no-store:用于防止重要的信息被无意的发布。在请求消息中发送将使得请求和响应消息都不使用缓存;
- max-age:可以接收生存期不大于指定时间(以秒为单位)的响应;
- min-fresh:可以接收响应时间小于当前时间加上指定时间的响应;
- max-stale:可以接收超出超时期间的响应消息。如果指定 max-stale 消息的值,那么客户机可以接收超出超时期指定值之内的响应消息。
实现缓存
1.创建缓存文件夹;
2.设置缓存大小;
3.创建缓存对象,添加到 okHttpClient 对象中。
eg:
OkHttpClient okHttpClient = new OkHttpClient();
// 创建缓存文件夹,为安全起见,一般是私密数据空间;
File cacheFile = new File(context.getExternalCacheDir(), "cacheDir");
// 设置缓存大小
int cacheSize = 1024 * 1024 * 100;
// 创建缓存对象
Cache cache = new Cache(cacheFile, cacheSize);
// 添加到 okHttpClient 对象中
okHttpClient.setCache(cache);
// OkHttpClient okHttpClient = new OkHttpClient().Builder().cache(cache).build();
这样在允许缓存的时候,就可以看到 Response 缓存了,Response 对象调用 cacheResponse() 就可以获得缓存内容了。
但有个问题就是,服务端在返回 Response 的时候,不一定添加好了 Cache-Control 相关的内容(譬如就算添加了,也是 no-cache 不允许缓存这种情况),那么怎么办?
1.使用拦截器拦截 Response 修改 Header 信息
eg:
/**
* 拦截响应结果,实现缓存
*/
private Interceptor cacheInterceptor() {
Interceptor cacheInterceptor = new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
// 获得请求
Request request = chain.request();
// 执行请求,获得响应结果
Response response = chain.proceed(request);
// 获得响应结果的 Cache-Control 信息
String cacheControl = request.cacheControl().toString();
// 修改 Cache-Control 信息,譬如定位缓存时间60秒
if (TextUtils.isEmpty(cacheControl)) {
cacheControl = "public, max-age=60";
}
// 返回新构造的响应结果
return response.newBuilder()
.header("Cache-Control", cacheControl)
.removeHeader("Pragma")
.build();
}
};
}
然后在设置了缓存区域的 okHttpClient 中添加上这个拦截器即可。
但是,使用拦截器实现缓存的方案存在一些缺点:
APP 里面最好只有一个 okHttpClient,也只有一个缓存文件夹,很好理解,单例模式,而使用拦截器实现缓存,拦截器是添加到 okHttpClient 中的,那么岂不是 okHttpClient 处理的网络请求都只有一个缓存时间? 问题就在于很多种请求需求的缓存时间不一定是一致的(譬如请求的图片希望缓存7天,而网络新闻只缓存1分钟)。
2.使用官方推荐的方法:CacheControl 类实现缓存
CacheControl 有两个静态常量:
- FORCE_CACHE:强制使用缓存,如果用了这个常量但还去做网络请求的话,okHttp会提示504 code;
- FORCE_NETWORK:强制使用网络。
使用这两个常量返回的都是 CacheControl 对象,那么如果想设置更多的情况怎么做呢?,CacheControl 也有使用建造者的方式,eg:
// 获得 CacheControl 的 Builder 对象
CacheControl.Builder builder = new CacheControl.Builder();
// 设置各种情况:
builder.noCache();//不使用缓存,全部走网络,其实 FORCE_NETWORK 常量获得的 CacheControl 对象就是调用了这个方法
builder.noStore();//不使用缓存,也不存储缓存
builder.onlyIfCached();//只使用缓存
builder.noTransform();//禁止转码
builder.maxAge(10, TimeUnit.MILLISECONDS);//可以接收生存期不大于指定时间的响应,设为0,不会缓存,直接走网络
builder.maxStale(10, TimeUnit.SECONDS);//可以接收超出超时期间的响应消息,这个方法加上 onlyIfCached() 就是 FORCE_CACHE 调用的方法
builder.minFresh(10, TimeUnit.SECONDS);//可以接收响应时间小于当前时间加上指定时间的响应
// 设置好之后,获得 CacheControl 对象
CacheControl cache = builder.build();//cacheControl
怎么给 Request 设置缓存?
1.创建一个设置好的 CacheControl 对象;
2.Request 的 Builder 对象调用 cacheControl(CacheControl cacheControl)。
eg:
....// 创建 okHttpClient 对象并已经设置好缓存文件夹
// 获得 CacheControl 的 Builder 对象
CacheControl.Builder builder = new CacheControl.Builder();
// 设置好缓存情况
builder.maxAge(10, TimeUnit.MILLISECONDS)
...;
// 生成 CacheControl 对象
CacheControl cache = builder.build();
// 或者使用两个静态常量中的一个,直接生产 CacheControl 对象
// CacheControl cache = CacheControl.FORCE_CACHE;
// CacheControl cache = CacheControl.FORCE_NETWORK
// 获得 Request 的 Builder 对象
Request.Builder requestBuilder = new Request.Builder();
// 将 CacheControl 对象 添加给 Request 的 Builder 对象,并设置好其它请求设置
requestBuilder.cacheControl(cache)
...;
// 获得 Request 对象
Request request = requestBuilder.build();
// 发出请求
...
解决504问题
上面说到504的问题,会出现在只使用缓存的情况,譬如使用 FORCE_CACHE 常量或者同时调用 onlyIfCached() 和 maxStale()。
1.判断网络情况,在无网情况下才发出只使用缓存的 Request:
if(non_NetWork) {
request = request.newBuilder().cacheControl(CacheControl.FORCE_CACHE).build();
}
2.判断返回的 Response 是否已经在本地有缓存了:
Response response = okHttpClient.newCall(request).excute();
if(response.code() != 504) {
// 已经有缓存,直接使用
} else {
// 没有缓存,做其它处理
}
缓存部分内容参考OKHTTP之缓存配置详解