Android 使用Retrofit2.0+OkHttp3.0實現緩存處理+Cookie持久化第三方庫

1.Retrofit+OkHttp的緩存機制

 1.1.第一點html

在響應請求以後在 data/data/<包名>/cache 下創建一個response 文件夾,保存緩存數據。

 

 

1.2.第二點java

這樣咱們就能夠在請求的時候,若是判斷到沒有網絡,自動讀取緩存的數據。

 

 

1.3.第三點react

一樣這也能夠實現,在咱們沒有網絡的狀況下,從新打開App能夠瀏覽的以前顯示過的內容。

 

 

1.4.第四點android

也就是:判斷網絡,有網絡,則從網絡獲取,並保存到緩存中,無網絡,則從緩存中獲取。

 

 

1.5.github地址+參考文章git

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

  參考文章:讓個人項目也是用RxJava+OkHttp+Retrofitweb

  參考文章:Retrofit+RxJava+OkHttp讓網絡請求變得簡單api

  最重要的參考文章:使用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();
    }
相關文章
相關標籤/搜索