Android 使用Retrofit2.0+OkHttp3.0实现缓存处理+Cookie持久化第三方库

1.Retrofit+OkHttp的缓存机制

1.1.第一点

在响应请求之后在 data/data/<包名>/cache 下建立一个response 文件夹,保存缓存数据。

1.2.第二点

这样我们就可以在请求的时候,如果判断到没有网络,自动读取缓存的数据。

1.3.第三点

同样这也可以实现,在我们没有网络的情况下,重新打开App可以浏览的之前显示过的内容。

1.4.第四点

也就是:判断网络,有网络,则从网络获取,并保存到缓存中,无网络,则从缓存中获取。

1.5.github地址+参考文章

  github地址:http://square.github.io/retrofit/

  参考文章:让我的项目也是用RxJava+OkHttp+Retrofit

  参考文章:Retrofit+RxJava+OkHttp让网络请求变得简单

  最重要的参考文章:使用Retrofit2+OkHttp3实现缓存处理

2.缓存实现方式

2.1.在build.gradle中引入Retrofit 

compile ‘com.squareup.retrofit2:retrofit:2.1.0‘//retrofit
compile ‘com.google.code.gson:gson:2.6.2‘//Gson 库
//下面两个是RxJava 和RxAndroid
compile ‘io.reactivex:rxjava:1.1.0‘
compile ‘io.reactivex:rxandroid:1.1.0‘
compile ‘com.squareup.retrofit2:converter-gson:2.1.0‘//转换器,请求结果转换成Model
compile ‘com.squareup.retrofit2:adapter-rxjava:2.1.0‘//配合Rxjava 使用

2.2.先开启OkHttp缓存

  在Retrofit2.0版本之后,Retrofit底层自动依赖了OkHttp,所以不用重复依赖Okhttp了。

File httpCacheDirectory = new File(MyApp.mContext.getCacheDir(), "responses");
int cacheSize = 10 * 1024 * 1024; // 10 MiB
Cache cache = new Cache(httpCacheDirectory, cacheSize);
OkHttpClient client = new OkHttpClient.Builder()
        .addInterceptor(REWRITE_CACHE_CONTROL_INTERCEPTOR)
        .cache(cache).build();

  这一步设置缓存路径以及缓存大小,其中addInterceptor是添加拦截器,下一步详细讲。

2.3.设置OkHttp拦截器

  主要是拦截操作,包括控制缓存的最大生命值,控制缓存的过期时间。

  两个操作都是在Interceptor中进行的。

  通过CacheControl控制缓存数据。 

CacheControl.Builder cacheBuilder = new CacheControl.Builder();
cacheBuilder.maxAge(0, TimeUnit.SECONDS);//这个是控制缓存的最大生命时间
cacheBuilder.maxStale(365,TimeUnit.DAYS);//这个是控制缓存的过时时间
CacheControl cacheControl = cacheBuilder.build();

  

  设置拦截器。 

Request request = chain.request();
if(!StateUtils.isNetworkAvailable(MyApp.mContext)){
    request = request.newBuilder()
            .cacheControl(cacheControl)
            .build();
}
Response originalResponse = chain.proceed(request);
if (StateUtils.isNetworkAvailable(MyApp.mContext)) {
    int maxAge = 60; // read from cache
    return originalResponse.newBuilder()
            .removeHeader("Pragma")
            .header("Cache-Control", "public ,max-age=" + maxAge)
            .build();
} else {
    int maxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks stale
    return originalResponse.newBuilder()
            .removeHeader("Pragma")
            .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
            .build();
}
如果.maxAge(0,TimeUnit.SECONDS)设置的时间比拦截器长是不起效果,如果设置比拦截器设置的时间短就会以这个时间为主,我觉得是为了方便控制。.maxStale(365, TimeUnit.DAYS)设置的是过时时间,我觉得okthhp缓存分成了两个来考虑,一个是为了请求时直接拿缓存省流量,一个是为了下次进入应用时可以直接拿缓存。

2.4.真实案例例子。

public class RetrofitFactory {

    private static final Object Object = new Object();
    /**
     * 缓存机制
     * 在响应请求之后在 data/data/<包名>/cache 下建立一个response 文件夹,保持缓存数据。
     * 这样我们就可以在请求的时候,如果判断到没有网络,自动读取缓存的数据。
     * 同样这也可以实现,在我们没有网络的情况下,重新打开App可以浏览的之前显示过的内容。
     * 也就是:判断网络,有网络,则从网络获取,并保存到缓存中,无网络,则从缓存中获取。
     * https://werb.github.io/2016/07/29/%E4%BD%BF%E7%94%A8Retrofit2+OkHttp3%E5%AE%9E%E7%8E%B0%E7%BC%93%E5%AD%98%E5%A4%84%E7%90%86/
     */
    //这里是设置拦截器,供下面的函数调用,辅助作用。
    private static final Interceptor cacheControlInterceptor = new Interceptor() {
      
        @Override
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();
            if (!NetWorkUtil.isNetworkConnected(InitApp.AppContext)) {
                request = request.newBuilder().cacheControl(CacheControl.FORCE_CACHE).build();
            }

            Response originalResponse = chain.proceed(request);
            if (NetWorkUtil.isNetworkConnected(InitApp.AppContext)) {
                // 有网络时 设置缓存为默认值
                String cacheControl = request.cacheControl().toString();
                return originalResponse.newBuilder()
                        .header("Cache-Control", cacheControl)
                        .removeHeader("Pragma") // 清除头信息,因为服务器如果不支持,会返回一些干扰信息,不清除下面无法生效
                        .build();
            } else {
                // 无网络时 设置超时为1周
                int maxStale = 60 * 60 * 24 * 7;
                return originalResponse.newBuilder()
                        .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
                        .removeHeader("Pragma")
                        .build();
            }
        }
    };
    private volatile static Retrofit retrofit;

    //这个人函数供外部调用,当请求数据时来调用
    @NonNull
    public static Retrofit getRetrofit() {
        synchronized (Object) {
            if (retrofit == null) {
                // 指定缓存路径,缓存大小 50Mb
                Cache cache = new Cache(new File(InitApp.AppContext.getCacheDir(), "HttpCache"),
                        1024 * 1024 * 50);

                // Cookie 持久化
                ClearableCookieJar cookieJar =
                        new PersistentCookieJar(new SetCookieCache(), new SharedPrefsCookiePersistor(InitApp.AppContext));

                OkHttpClient.Builder builder = new OkHttpClient.Builder()
                        .cookieJar(cookieJar)
                        .cache(cache)
                        .addInterceptor(cacheControlInterceptor)
                        .connectTimeout(10, TimeUnit.SECONDS)
                        .readTimeout(15, TimeUnit.SECONDS)
                        .writeTimeout(15, TimeUnit.SECONDS)
                        .retryOnConnectionFailure(true);

                // Log 拦截器
                if (BuildConfig.DEBUG) {
                    builder = SDKManager.initInterceptor(builder);
                }

                retrofit = new Retrofit.Builder()
                        .baseUrl(INewsApi.HOST)
                        .client(builder.build())
                        .addConverterFactory(GsonConverterFactory.create())
                        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                        .build();
            }
            return retrofit;
        }
    }
}

  通过这样,我们就可以直接使用同一个Retrofit请求方法。

  无论是最新数据还是换成数据,都可以转化为我们需要的对象,直接使用。

  这里的SDK也是一个方便自己调试的拦截器,实现方法如下: 

public class SDKManager {
    public static void initStetho(Context context){
        Stetho.initializeWithDefaults(context);
    }

    public static OkHttpClient.Builder initInterceptor(OkHttpClient.Builder builder){
        HttpLoggingInterceptor interceptor=new HttpLoggingInterceptor();
        interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        builder.addInterceptor(interceptor);
        return builder;
    }
}

  这里的HttpLoggingInterceptor是okhttp3中自带的一个谷歌浏览器调试方法类。

  比较简单实用。

  所以这里就是将缓存控制的拦截器以及日志拦截器加到OkHttpClient.Builder中了。

  将Cookie持久化也加到OkHttpClient.Builder中了。

  就是要用的的方法加到这个OkHttpClient.Builder中就行了。

3.Cookie持久化的第三方库使用方法

3.1.Cookie持久化的第三方库==>PersisitentCookieJar

  github地址:https://github.com/franmontiel/PersistentCookieJar

  参考文章:Android关于Https中Cookie的使用(PersistentCookieJar)

  关于Cookie可以参考这篇文章:深入解析Cookie技术。

  关于鸿洋大神封装的okhttputils也提供的cookie的持久化管理工具。

3.2.引入第三方包

  在根目录的build.gradle加入如下支持: 

allprojects {
    repositories {
        ...
        maven { url "https://jitpack.io" }
    }
}

  然后在项目依赖的build.gradle中添加如下代码:

dependencies {
    compile ‘com.github.franmontiel:PersistentCookieJar:v1.0.1‘
}

3.3.使用方法==>so easy.

  首先需要在初始化时加入以下代码:

ClearableCookieJar cookieJar =
                new PersistentCookieJar(new SetCookieCache(), new SharedPrefsCookiePersistor(context));  

  然后在初始化OkHttpClient调用cookieJar,如下代码:

OkHttpClient okHttpClient = new OkHttpClient.Builder()
                .cookieJar(cookieJar)
                .build();  

  然后,服务器就可以发送Cookie给我们,我们进行永久保存(或者临时保存)

  下一次请求时,服务器即可拿到Cookie进行数据查询操作了。

4.用Retrofit写一个网络请求

上面讲OkHttpClient.Builder设置完毕后,用到了Retrofit来请求。

  

4.1.创建一个Retrofit实例。  

Retrofit retrofit = new Retrofit.Builder()
                        .baseUrl(INewsApi.HOST)
                        .client(builder.build())
                        .addConverterFactory(GsonConverterFactory.create())
                        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                        .build();

  这里配置了接口baseUrl+addConverterFactory

  baseUrl是我们请求的基地址。

  addConverterFactory是默认提供的Gson转换器,写这个即可。

  addCallAdapterFactory是默认提供的适配器工厂回调类,写这个即可。

4.2.创建一个实际接口。 

public interface IJokeApi {

    /**
     * 获取段子正文内容
     * http://www.toutiao.com/api/article/feed/?category=essay_joke&as=A115C8457F69B85&cp=585F294B8845EE1
     */
    @GET("api/article/feed/?category=essay_joke")
    Observable<JokeContentBean> getJokeContent(
            @Query("max_behot_time") String maxBehotTime,
            @Query("as") String as,
            @Query("cp") String cp);

    /**
     * 获取段子评论
     * http://m.neihanshequ.com/api/get_essay_comments/?group_id=编号&count=数量&offset=偏移量
     */
    @GET("http://m.neihanshequ.com/api/get_essay_comments/?count=20")
    @Headers({"User-Agent:" + Constant.USER_AGENT_MOBILE})
    Observable<JokeCommentBean> getJokeComment(
            @Query("group_id") String groupId,
            @Query("offset") int offset);
}
说明:定义了一个方法getJokeContent,使用get请求方式,加上@GET 标签,标签后面是这个接口的 尾址,完整的地址应该是 baseUrl+尾址 ,参数 使用@Query标签,如果参数多的话可以用@QueryMap标签,接收一个Map

4.3.用Retrofit创建接口实例的方法,如何调用接口中的方法进行网络请求。

  数据怎么回调呢?

  这里用了一个订阅关系Observable。

  加入RxJava后的网络请求,返回不再是一个Call,而是一个Observable。

  在Activity或者Fragment传入一个Subscriber建立订阅关系,就可以在onNext中处理结果了。

Subscription subscription = movieService.getTop250(0,20)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<MovieSubject>() {
@Override
 public void onCompleted() { 

 }
@Override
public void onError(Throwable e) { 

}
@Override
 public void onNext(MovieSubject movieSubject) {
        mMovieAdapter.setMovies(movieSubject.subjects);
        mMovieAdapter.notifyDataSetChanged();
   }
});
RxJava 的好处是帮我处理线程之间的切换,我们可以在指定订阅的在哪个线程,观察在哪个线程。我们可以通过操作符进行数据变换。整个过程都是链式的,简化逻辑。其中FlatMap 操作符 还可以解除多层嵌套的问题。总之,RxJava 很强大,能帮我处理很多复杂的场景,如果熟练使用的话,那么能提升我们的开发效率。

  如果无聊的话可以看看原理:

  参考文章:给Android开发者的RxJava详解。

  参考文章:关于RxJava最友好地文章。

4.4.用Retrofit创建接口的实际方法。

 @Override
    public void doLoadData(){
        Map<String, String> map = ToutiaoUtil.getAsCp();

        RetrofitFactory.getRetrofit().create(IJokeApi.class).getJokeContent(time, map.get(Constant.AS), map.get(Constant.CP))
                .subscribeOn(Schedulers.io())
                .map(new Function<JokeContentBean, List<JokeContentBean.DataBean.GroupBean>>() {
                    @Override
                    public List<JokeContentBean.DataBean.GroupBean> apply(@NonNull JokeContentBean jokeContentBean) throws Exception {
                        List<JokeContentBean.DataBean> data = jokeContentBean.getData();
                        for (JokeContentBean.DataBean dataBean : data) {
                            groupList.add(dataBean.getGroup());
                        }
                        time = jokeContentBean.getNext().getMax_behot_tim() + "";
                        return groupList;
                    }
                })
                .compose(view.<List<JokeContentBean.DataBean.GroupBean>>bindToLife())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Consumer<List<JokeContentBean.DataBean.GroupBean>>() {
                    @Override
                    public void accept(@NonNull List<JokeContentBean.DataBean.GroupBean> groupBeen) 

throws Exception {
                        if (groupBeen.size() > 0) {
                            doSetAdapter();
                        } else {
                            doShowNoMore();
                        }
                    }
                }, ErrorAction.error());

    }

  这是实际请求方法。

  说白了,Retrofit.create是返回一个Observable<T>对象。

  因为在接口API中已经定义。

  所以这里用订阅关系处理回调。

  以前常用的方法是Call的回调,一个onSuccess表示网络成功请求,一个onFailure表示网络请求失败。

  太low了。

  现在用Observable<T>来进行类似的操作。

  • 这里Observable.subscribeOn==>指定了被观察者执行的线程环境
  • map==>使用map操作来完成类型转换,前者转换成后者。
  • compose()==>方便多个流重复利用一系列操作符(这个我也不是特别理解)  
  • observeOn(Android...MainThread)==>将后面执行的线程环境切换为主线程,但这一句还在io线程
  • subscribe(...)==>执行在主线程,创建观察者,作为事件传递的终点处理事件

  关于Rxjava操作符compose()的理解,可以参考这篇文章。 

5.关于RxJava的不理解的地方

5.1.在用Retrofit返回的Observable<T>中调用了一个compose方法。 

 .compose(view.<List<JokeContentBean.DataBean.GroupBean>>bindToLife())

5.2.然后这个方法定义在IBaseListView中。

 /**
     * 绑定生命周期
     */
    <T> LifecycleTransformer<T> bindToLife();

5.3.执行bindToLife()地方在BaseFragment中。 

 /**
     * 绑定生命周期
     */
    @Override
    public <T> LifecycleTransformer<T> bindToLife() {
        return bindUntilEvent(FragmentEvent.DESTROY);
    }

  bindUntilEvent方法定义在RxFragment中。

5.4.在BaseListFragment中实现了LazyLoadFragment懒加载中的fetchData抽象函数。

    @Override
    public void fetchData() {
        observable = RxBus.getInstance().register(BaseListFragment.TAG);
        observable.subscribe(new Consumer<Integer>() {
            @Override
            public void accept(@NonNull Integer integer) throws Exception {
                adapter.notifyDataSetChanged();
            }
        });
    }

  这里的方法和JokeContentPresenter处理器中请求数据的方法中的一段代码及其相似。

5.5.在BaseListFragment中重写了onDestroy()

    @Override
    public void onDestroy() {
        RxBus.getInstance().unregister(BaseListFragment.TAG, observable);
        super.onDestroy();
    }
时间: 2024-08-28 16:38:02

Android 使用Retrofit2.0+OkHttp3.0实现缓存处理+Cookie持久化第三方库的相关文章

Retrofit2.0通俗易懂的学习姿势,Retrofit2.0 + OkHttp3 + Gson + RxJava

Retrofit2.0通俗易懂的学习姿势,Retrofit2.0 + OkHttp3 + Gson + RxJava Retrofit,因为其简单与出色的性能,也是受到很多人的青睐,但是他和以往的通信框架还是有点区别,不过放心,因为他本身还是挺简单的,所有我相信你看完这篇文章,对基本的请求是没什么问题的,其实现在网上这样的文章也有很多了,好了,那我们直接开车吧! 一.相关资料 Github:https://github.com/square/retrofit 官网文档:http://square

android finish和system.exit(0)的区别

finish是Activity的类,仅仅针对Activity,当调用finish()时,只是将活动推向后台,并没有立即释放内存,活动的资源并没有被清理:当调用System.exit(0)时,杀死了整个进程, 这时候活动所占的资源也会被释放. 在开发android应用时,常常通过按返回键(即keyCode == KeyEvent.KEYCODE_BACK)就能关闭程序,其实大多情况下该应用还在任务里运行着,其实这不是我们想要的结果. 我们可以这样做,当用户点击自定义的退出按钮或返回键时(需要捕获动

导入项目的时候报错Error:Could not find com.android.support.constraint:constraint-layout:1.0.0-alpha7

问题描述 今天在导入项目的时候报错: Error:Could not find com.android.support.constraint:constraint-layout:1.0.0-alpha7. 原因是:没有下载相应版本的ConstraintLayout. 解决方案 工具栏上选择 Tools --> Android -->SDK Manager 切换到SDK Tools选项,在右下角处勾选 Show Package Details 在Support Repository下的Const

This Android SDK requires Android Developer Toolkit version 23.0.0 or above

This Android SDK requires Android Developer Toolkit version 23.0.0 or above. Current version is 12.0.0.v201106281929-138431. Please update ADT to the latest version 22.0.0 一般这种原因是误更新了SDK,导致SDK版本过高,ECLIPSE提示需要更新ADT. 解决办法如下: 找到android-sdk-windows\tools

Skipping &#39;Android SDK Tools, revision 24.0.2&#39;; it depends on &#39;Android SDK Platform-tools, revision 20&#39; which was not installed.

前几天,同事问我eclipse android sdk怎么不能更新. 更新界面是显示(mirrors.neusoft.edu.cn:80),但是不能更新. 问题描述如下: URL not found: F:\Android\android-sdk\temp\samples-19_r05.zip (拒绝访问.) Skipping 'Android SDK Tools, revision 24.0.2'; it depends on 'Android SDK Platform-tools, revi

Android 百度地图 SDK v3.0.0 (二) 定位与结合方向传感器

在上一篇博客中,我们成功把地图导入了我们的项目.本篇我们准备为地图添加:第一,定位功能:第二,与方向传感器结合,通过旋转手机进行道路的方向确认.有了这两个功能,地图已经可以为我服务了~~~~ 效果图: 好了,可以代码,为了方便,我把所有的按钮都放到了menu菜单中. 1.初次启动定位 [java] view plaincopy /** * 定位的客户端 */ private LocationClient mLocationClient; /** * 定位的监听器 */ public MyLoca

[转] This Android SDK requires Android Developer Toolkit version 23.0.0 or above

问题描述: 上一篇讲到解决Appcompat_V7问题要降低adt版本,于是就换旧版本22.3.0啊,重新打开Eclipse,立马弹出: This Android SDK requires Android Developer Toolkit version 23.0.0 or above.  Current version is 12.0.0.v201106281929-138431. 一般这种原因是误更新了SDK,导致SDK版本过高,ECLIPSE提示需要更新ADT. 于是我觉得是我的sdk版

Eclipse中通过Android模拟器调用OpenGL ES2.0函数操作步骤

原文地址: Eclipse中通过Android模拟器调用OpenGL ES2.0函数操作步骤 - 网络资源是无限的 - 博客频道 - CSDN.NET http://blog.csdn.net/fengbingchun/article/details/11192189   1.  先按照http://blog.csdn.net/fengbingchun/article/details/10439281中操作搭建好基本的Android开发环境: 2.  打开Eclipse,-->Window-->

hadoop1.0 TaskTracker因为分布式缓存导致内存泄露的一次问题排查

上周五同事到公司说凌晨的时候有值班同事打电话给他,有部分job卡住了,运行了很长时间都没运行完成,由于是凌晨,他没来得及详细的查看日志,简单的把有问题的tasktracker重启了一下,只有一个节点的TaskTracker进程停掉,让我查一下具体是什么问题.以下是排查过程: 1.登陆到停掉TT进程的处理机 (1).查看磁盘空间 磁盘没有出现空间不足的情况. (2).top查看负载和内存使用情况: 根据上图看出内存和负载都不算高,也不存在僵尸进程. 2.查看进程日志 1.log4j日志: 2014