一些你可能需要的okhttp实现
https://blog.csdn.net/qq_17766199/article/details/53186874
今天分享一些我在项目中使用到的okhttp实现,由简至难。(以下内容均在okhttp3.4.1下正常使用)
1.okhttp日志打印
这个就简单了,一个工具类。先上代码:
public class LoggingInterceptor implements Interceptor {
@Override public Response intercept(Interceptor.Chain chain) throws IOException {
Request request = chain.request();
KLog.d(String.format("Sending request %s on %s%n%s", request.url(), chain.connection(), request.headers()));
long t1 = System.nanoTime();
okhttp3.Response response = chain.proceed(chain.request());
long t2 = System.nanoTime();
KLog.d(String.format(Locale.getDefault(), "Received response for %s in %.1fms%n%s",
response.request().url(), (t2 - t1) / 1e6d, response.headers()));
okhttp3.MediaType mediaType = response.body().contentType();
String content = response.body().string();
KLog.json(content);
return response.newBuilder()
.body(okhttp3.ResponseBody.create(mediaType, content))
.build();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
这里我为了打印清晰使用了KLog用来打印Log。
使用方法:
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(new LogInterceptor())
.build();
- 1
- 2
- 3
打印例子:
当然了,你也可以使用官方提供的Logging Interceptor,也是非常方便。具体参见链接
2.okhttp网络缓存
(1)首先设置Cache
private static File cacheFile = new File(context.getCacheDir(), "Test");
private static Cache cache = new Cache(cacheFile, 1024 * 1024 * 10);
private static OkHttpClient okHttpClient = new OkHttpClient.Builder()
.cache(cache)
.build();
- 1
- 2
- 3
- 4
- 5
这里使用getCacheDir()来作为缓存文件的存放路径(/data/data/包名/cache) ,如果你想看到缓存文件可以临时使用 getExternalCacheDir()(/sdcard/Android/data/包名/cache)。
(2)如果我们的服务器支持缓存,那么Response中的头文件中会有Cache-Control: max-age=xxx这样的字段。如下图:
这里的public意思是可以无条件的缓存该响应,max-age是你缓存的最大存放的时间。比如你设置了6分钟的最大缓存时间,那么6分钟内他会读取缓存,但超过这个时间则缓存失效。具体的响应标头大家可以自行查询。
(3)如果我们的服务器不支持缓存,也就是响应头没有对应字段,那么我们可以使用网络拦截器实现:
public class CacheInterceptor implements Interceptor {
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
if (!isNetworkConnected()) {
request = request.newBuilder()
.cacheControl(CacheControl.FORCE_CACHE)
.build();
}
Response originalResponse = chain.proceed(request);
if (isNetworkConnected()) {
//有网的时候读接口上的@Headers里的配置,你可以在这里进行统一的设置(注掉部分)
String cacheControl = request.cacheControl().toString();
return originalResponse.newBuilder()
.header("Cache-Control", cacheControl)
//.header("Cache-Control", "max-age=3600")
.removeHeader("Pragma") // 清除头信息,因为服务器如果不支持,会返回一些干扰信息,不清除下面无法生效
.build();
} else {
int maxAge= 60 * 60;
return originalResponse.newBuilder()
.header("Cache-Control", "public, only-if-cached, max-age=" + maxAge)
.removeHeader("Pragma")
.build();
}
}
private boolean isNetworkConnected() {
ConnectivityManager connectivity = (ConnectivityManager) MyApplication.getInstance()
.getSystemService(Context.CONNECTIVITY_SERVICE);
if (null != connectivity){
NetworkInfo info = connectivity.getActiveNetworkInfo();
if (null != info && info.isConnected()){
if (info.getState() == NetworkInfo.State.CONNECTED){
return true;
}
}
}
return false;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
设置拦截器:
private static OkHttpClient okHttpClient = new OkHttpClient.Builder()
.cache(cache)
.addInterceptor(new CacheInterceptor())
.addNetworkInterceptor(new CacheInterceptor())
.build();
- 1
- 2
- 3
- 4
- 5
注意:addInterceptor和addNetworkInterceptor 需要同时设置。 这两者的区别可以参考Interceptors 拦截器。我只说一下效果,如果你只是想实现在线缓存,那么可以只添加网络拦截器,如果只想实现离线缓存,可以使用只添加应用拦截器。两者都添加,就不用我说了吧。
如果在拦截器中统一配置,则所有的请求都会缓存。但是在实际开发中有些接口需要保证数据的实时性,那么我们就不能统一配置,这时可以这样:
@Headers("Cache-Control: public, max-age=时间秒数")
@GET("weilu/test")
Observable<Test> getData();
- 1
- 2
- 3
我自己找了一些配置,大家可以根据个人需求使用:
1.不需要缓存:Cache-Control: no-cache或Cache-Control: max-age=0
2.如果想先显示数据,在请求。(类似于微博等):Cache-Control: only-if-cached
通过以上配置后通过拦截器中的request.cacheControl().toString() 就可以获取到我们配置的Cache-Control头文件,实现对应的缓存策略。
测试一下:
1.首先我设置@Headers("Cache-Control: public,max-age=30")
2.30秒有效期内请求第一次
3.30内请求第二次(在线缓存)
你可能会说没有什么不同,其实仔细看看就会发现,两次的请求响应时间分别为85.5ms和1.5ms,这说明是直接读取的缓存。同时我们可以查看Monitors中的Network发现并没有请求网络,也同样说明使用的是缓存。
从这里其实也就说明了添加缓存的好处:1.降低了请求的延迟。2.降低网络的频繁请求。
3.30超出后再请求一次结果与第二步一致。
缓存失效,重新请求。
4.关闭网络后超过30s请求一次(离线缓存)
这时发现警告响应失效。这说明已经超过30秒,如果没有超出则没有警告。大家可以自行尝试。
最后感兴趣的可以去我们设置的缓存目录查看一下缓存文件,你一定会有新的发现。
3.okhttp实现token过期刷新
(1)使用拦截器实现,这个具体参看这篇文章,我自己没有试过,提供出来给大家拓展一下思路。
(2)使用RxJava的操作符retryWhen 实现。这个是我在项目中使用的方法,也是重点说明的方法。
首先说明一下我们这边的情况,我们的登录验证使用的是登陆成功后包含在响应头文件中的Set-Cookie,这其中有sessionId,凡是需要登录操作的只需请求时带上他即可。
登录超时时服务器会返回code为401的json来告知我们登录超时。那么其实就很简单了,只需要判断code是否为401,是则登录一下保存新的Cookie,在重新请求一下之前的操作就行了。
下来我来实现以下,至于其他的情况其实大同小异。
首先我们来说明一下retryWhen,retryWhen操作符是在源Observable出现错误或者异常时,通过回调另一个Observable来判断是否重新尝试执行源Observable的逻辑,如果这个Observable没有错误或者异常出现,则就会重新尝试执行源Observable的逻辑,否则就会直接回调执行订阅者的onError方法。
看了上面的概念是不是有点绕,其实意思就和他的名字一样“重试”,而触发的条件是有了“错误”,错误解决,那么再试一次。其实这就和我们需要解决的问题一样,登录失败(触发)–> 重新登录(解决) –> 重试。
首先定义一个父类。
public class TimeOut {
private int timeout;
public int getTimeout() {
return timeout;
}
public void setTimeout(int timeout) {
this.timeout = timeout;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
自定义一个异常,可以不用实现
public class TimeOutException extends Exception{
}
- 1
- 2
简单封装了一下,这样不用重复书写超时判断。
protected Subscription subscription;
public <B extends TimeOut> void toSubscribe(Observable<B> o, Subscriber<B> s) {
unSubscribe();
subscription = o.subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.flatMap(new Func1<B, Observable<B>>() {
@Override
public Observable<B> call(B timeout) {
if(timeout.getTimeout() == 401){
return Observable.error(new TimeOutException()); //超时,则触发retryWhen
}
return Observable.just(timeout);
}
})
.retryWhen(new TimeOutRetry()) //<----
.subscribe(s);
}
public void unSubscribe(){
if (subscription != null)
subscription.unsubscribe();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
TimeOutRetry 类:
public class TimeOutRetry implements Func1<Observable<? extends Throwable>, Observable<?>>{
@Override
public Observable<?> call(Observable<? extends Throwable> observable) {
return observable.flatMap(new Func1<Throwable, Observable<?>>() {
@Override
public Observable<?> call(Throwable throwable) {
if (throwable instanceof TimeOutException) {
KLog.e("-----重登进入-----");
String name = 获取用户名;
String pwd = 获取密码;
if(TextUtils.isEmpty(name) || TextUtils.isEmpty(pwd)){
KLog.e("--- 超时重登未完成 ---");
return Observable.error(throwable);
}
return mApi().login(name, pwd)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.unsubscribeOn(Schedulers.io())
.doOnNext(new Action1<Login>() {
@Override
public void call(Login login) {
KLog.e("--- 登录完成 ---");
}
});
}
return Observable.error(throwable); // 其他异常直接结束
}
});
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
使用:
public void test(){
toSubscribe(mApi.getData(), new Subscriber<Test>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(Test test) {
}
});
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
注意这里的Test类需要继承TimeOut。
结果如图:
看完这么多,我们可以看到这些功能都可以利用拦截器去实现,可见它的强大之处。(补充:说到了拦截器,突然想到了Facebook出的一个强大的Android调试工具stetho,该工具让你可以在谷歌浏览器查看App的布局,preference,网络请求,数据库,一切都是可视化的操作,不需要root你的设备。其中也支持okhttp,使用方法就是添加一个网络拦截器。)还有一些实现,比如https的访问,cookie的同步问题我就不一一去说了,我们可以参考okhttputils与okhttp-OkGo 去实现。好了,本篇到此结束!如果文章中有错误的地方希望指出,多多交流。最后喜欢的点个赞哈!
4.参考
ps:发现文章中图片看的不是很清晰,大家可以右键–>打开图片,来查看大图。
扫码向博主提问
qq_17766199
- 擅长领域:
- Android
- 单元测试
一些你可能需要的okhttp实现的更多相关文章
- 更改手机系统的User-Agent & okhttp
okhttp 和 volley 1. 之前用的是volley,其中一部分功能,比如User-Agent,是系统去处理的,改成okhttp库后,这部分功能需要浏览器自己处理 2. 具体区别可以参考: h ...
- Android使用HTTPS进行IP直连握手失败问题(okHttp)
为什么要使用ip直连这种方式去请求我们的服务器呢?这其实和国内运营伤有关,运营商有时为了利益会将你的域名劫持换成他人的域名,为了防止这种情况的发生通用的解决办法要么联系运营商要么就只能使用ip直连了. ...
- OkHttp 官方Wiki【设计思想】
官方Wiki之Calls 原文位置:https://github.com/square/okhttp/wiki/Calls The HTTP client's job is to accept you ...
- 关于okHttp框架的使用
在之前的项目中,使用传统的HttpClient来返回一个图片信息流的时候总是报错,最后发现是因为传统的传输方式会对流的大小有限制,当超过某个值的时候就会报异常,最后决定使用OkHttp框架来解决这个问 ...
- Okhttp解析—Okhttp概览
Okhttp解析-Okhttp概览 Okhttp作为目前Android使用最为广泛的网络框架之一,我们有必要去深入了解一下,本文是Okhttp解析的第一篇,主要是从宏观上认识Okhttp整个架构是如何 ...
- Android okHttp网络请求之Json解析
前言: 前面两篇文章介绍了基于okHttp的post.get请求,以及文件的上传下载,今天主要介绍一下如何和Json解析一起使用?如何才能提高开发效率? okHttp相关文章地址: Android o ...
- 关于 bind 你可能需要了解的知识点以及使用场景
不看不知道,一看吓一跳,已经整整一个月没有更新 underscore 源码解读系列文章了.前面我们已经完成了 Object ,Array,Collection 上的扩展方法的源码剖析,本文开始来解读 ...
- okhttp封装时,提示 cannot resolve method OkHttpClient setConnectTimeout() 函数
如标题所示,okhttp封装时,提示 cannot resolve method OkHttpClient setConnectTimeout() 函数,有遇到这样现象的朋友吗? 原因:因使用的是 ...
- RxAndroid+Retrofit+MVVM(1)OKHttp
1)Gradlecompile 'com.squareup.okhttp:okhttp:2.4.0'compile 'com.squareup.okio:okio:1.5.0' 2)Get //创建o ...
随机推荐
- Python【time】模块
import timeprint(type(11.234))print("输出结果为时间戳,float类型:",time.time())print("输出结果为本地时间元 ...
- SpringBoot(十三):springboot 小技巧
原文出处: 纯洁的微笑 一些springboot小技巧.小知识点. 初始化数据 我们在做测试的时候经常需要初始化导入一些数据,如何来处理呢?会有两种选择,一种是使用Jpa,另外一种是Spring JD ...
- Java基础-异常(Exception)处理
Java基础-异常(Exception)处理 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.异常的概述 什么是异常?Java代码在运行时期发生的问题就是异常.在Java中,把异 ...
- 转:iOS-CoreLocation:无论你在哪里,我都要找到你!
1.定位 使用步骤: 创建CLLocationManager示例,并且需要强引用它 设置CLLocationManager的代理,监听并获取所更新的位置 启动位置更新 1 2 3 _manager = ...
- Windows10安装配置python2.7+scrapy环境
环境: windows10企业版x64 过程: 下载python-2.7.13.msi,安装,将以下路径添加到 PATH 中: C:\Python27\;C:\Python27\Scripts\; 安 ...
- android 自定义TODO
1.找到setting -> editor -> TODO 2. 正则的写法参考以前的就可以 这样我们就可以自己写一个todo了 3.TODO过滤: 4. 然后选择要展示的TODO 这里就 ...
- 【BZOJ】2006: [NOI2010]超级钢琴
[题意]给定长度为n的整数序列,求长度为[L,R]的前k大区间和的和.n,k<=500000. [算法]堆+贪心+RMQ [题解]考虑暴力是取所有长度为[L,R]的子串的前k大求和,复杂度O(n ...
- UNIX环境高级编程 第10章 信号
SIGSTOP和SIGKILL区别是:前者是使进程暂时停止,即中止,也就是说使进程暂停,将进程挂起,比如你在终端里面执行一个脚本或者程序,执行到一半,你想暂停一下,你按下ctrl+z,就会导致终端发送 ...
- 跨站请求伪造(CSRF)攻击原理解析:比你所想的更危险
跨站请求伪造(CSRF)攻击原理解析:比你所想的更危险 跨站请求伪造(Cross-Site Request Forgery)或许是最令人难以理解的一种攻击方式了,但也正因如此,它的危险性也被人们所低估 ...
- 【比赛游记】THUSC2018酱油记
day -1 早上4:30就要起来去飞机场…… 7点的飞机,10:30就到北京了. 北京的街景并没有我想像的漂亮……大概是因为我在四环外〒▽〒 晚上还有CF div3场,果断的去水了,因为太累就没有打 ...