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


    既然选择了,便不顾风雨兼程。Just follow yourself.
  • 相关阅读:
    Fiddler 捕获HTTPS流量
    Fiddler 弱网测试
    Fiddler Composer设计器, Fitter过滤器,断点
    Fiddler Statistics统计,Inspector检查器,AutoResponder自动响应器
    Fiddler Session List会话列表、命令行与状态栏
    Fiddler菜单栏工具栏
    HTTP协议请求与响应详解
    Postman 运用Cookie
    Postman 文件上传请求
    Postman 头域操作
  • 原文地址:https://www.cnblogs.com/Jason-Jan/p/8010795.html
Copyright © 2020-2023  润新知