天天看點

優雅封裝Retrofit+RxJava聯網的統一管理類

Retrofit的簡單用法在上一篇文章 分分鐘使用Retrofit+Rxjava實作網絡請求

已經做過介紹了,今天就不贅述了。

今天主要分享一下如何結合Rxjava,封裝一個RetrofitManager管理類,統一管理聯網操作。

《一》讓我們先來看看封裝後的用法:
RetrofitManager.getInstance().getRequestService().getWeather("北京")
                            .compose(RxSchedulers.io_main())
                            .subscribeWith(new DisposableObserver<Object>() {
                                @Override
                                public void onNext(Object result) {                                
                                    Log.e("TAG", "result=" + result.toString());
                                }

                                @Override
                                public void onError(Throwable e) {
                                    Log.e("TAG", "onError=" + e.getMessage());
                                }

                                @Override
                                public void onComplete() {
                                    Log.e("TAG", "onComplete");
                                }
                            });
           

封裝後的用法大家看到了,鍊式調用,一步到位,非常簡潔明了。接下來我就帶着大家一步步封裝一個RetrofitManager。

《二》封裝Retrofit+Rxjava的管理類RetrofitManager

(1)在app的build.gradle下配置Retrofit和Rxjava相關的依賴包

//rxandroid
    implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
    //rxjava
    implementation 'io.reactivex.rxjava2:rxjava:2.1.10'
    //retrofit
    implementation "com.squareup.retrofit2:retrofit:2.4.0"
    //gsonConverter
    implementation "com.squareup.retrofit2:converter-gson:2.4.0"
    //rxjavaAdapter
    implementation "com.squareup.retrofit2:adapter-rxjava2:2.4.0"
    //retrofit log列印
    implementation 'com.squareup.okhttp3:logging-interceptor:3.4.1'
           

(小提醒: Android Studio3.0之後的依賴,由compile變成了implementation。)

(2)①建立RetrofitManager類,提供單例

public class RetrofitManager {
    /**
     * 擷取單例
     */
    private static RetrofitManager mInstance;
       public static RetrofitManager getInstance() {
        if (mInstance == null) {
            synchronized (RetrofitManager.class) {
                if (mInstance == null) {
                    mInstance = new RetrofitManager();
                }
            }
        }
        return mInstance;
    }
}
           

②配置OkHttp,建構Retrofit對象

private static final long DEFAULT_TIMEOUT = 60L;
  public Retrofit getRetrofit() {
        if (retrofit == null) {
            synchronized (RetrofitManager.class) {
                if (retrofit == null) {
                    OkHttpClient mClient = new OkHttpClient.Builder()
                            //添加公共查詢參數
                            //.addInterceptor(new CommonQueryParamsInterceptor())
                            //.addInterceptor(new MutiBaseUrlInterceptor())
                            //添加header
                            .addInterceptor(new HeaderInterceptor())
                            .addInterceptor(new LoggingInterceptor())//添加請求攔截(可以在此處列印請求資訊和響應資訊)
                            .connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
                            .writeTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
                            .readTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
                          //添加https證書,如果有srca.cer的證書,則可以通過sslSocketFactory()配置
                          //.sslSocketFactory(getSSLSocketFactory(context, "srca.cer"))
                            .build();
                    retrofit = new Retrofit.Builder()
                            .baseUrl(BASE_URL)//基礎URL 建議以 / 結尾
                            .addConverterFactory(GsonConverterFactory.create())//設定 Json 轉換器
                            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())//RxJava 擴充卡
                            .client(mClient)
                            .build();
                }
            }
        }
        return retrofit;
    }

 /**
     * 實作https請求
     */
    private static SSLSocketFactory getSSLSocketFactory(Context context, String name) {


        if (context == null) {
            throw new NullPointerException("context == null");
        }

        //CertificateFactory用來證書生成
        CertificateFactory certificateFactory;
        InputStream inputStream = null;
        Certificate certificate;

        try {
            inputStream = context.getResources().getAssets().open(name);
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {

            certificateFactory = CertificateFactory.getInstance("X.509");
            certificate = certificateFactory.generateCertificate(inputStream);

            //Create a KeyStore containing our trusted CAs
            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            keyStore.load(null, null);
            keyStore.setCertificateEntry(name, certificate);

            //Create a TrustManager that trusts the CAs in our keyStore
            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(keyStore);

            //Create an SSLContext that uses our TrustManager
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());
            return sslContext.getSocketFactory();

        } catch (Exception e) {

        }
        return null;
    }
           

③通過代理的方式,建立ApiServe接口的執行個體。

public ApiService getRequestService() {
        return getRetrofit().create(ApiService.class);
    }
           

ApiService是一個自己定義的interface,所有的網絡請求接口的配置,都在此接口内完成。網絡請求URL的配置可以參考

Retrofit請求參數的配置
interface ApiService {
    //擷取北京的天氣資訊
//    "https://www.sojson.com/open/api/weather/json.shtml?city=" + "北京"
    @GET("weather/json.shtml")
    Observable<Object> getWeather(@Query("city")String city);
    //上傳檔案
   @POST("upload/")
    Observable<UserAvatarBean> uploadFile(@Body RequestBody body);
}
           

④Header的配置

/**
     * 添加請求頭需要攜帶的參數
     */
    public class HeaderInterceptor implements Interceptor {
        @Override
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();
            Request requestBuilder = request.newBuilder()
                    .addHeader("Connection", HEADER_CONNECTION)
                    .addHeader("token", "token-value")
                    .method(request.method(), request.body())
                    .build();
            return chain.proceed(requestBuilder);
        }
    }
           

⑤Retrofit的log日志列印

/**
     * log列印:參考:http://blog.csdn.net/csdn_lqr/article/details/61420753
     */
    public class LoggingInterceptor implements Interceptor {
        @Override
        public Response intercept(Chain chain) throws IOException {
            //這個chain裡面包含了request和response,是以你要什麼都可以從這裡拿
            Request request = chain.request();
            long t1 = System.nanoTime();//請求發起的時間
            String method = request.method();
            JSONObject jsonObject = new JSONObject();
            if ("POST".equals(method) || "PUT".equals(method)) {
                if (request.body() instanceof FormBody) {
                    FormBody body = (FormBody) request.body();
                    if (body != null) {
                        for (int i = 0; i < body.size(); i++) {
                            try {
                                jsonObject.put(body.name(i), body.encodedValue(i));
                            } catch (JSONException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                    Log.e("request", String.format("發送請求 %s on %s  %nRequestParams:%s%nMethod:%s",
                            request.url(), chain.connection(), jsonObject.toString(), request.method()));
                } else {
                    Buffer buffer = new Buffer();
                    RequestBody requestBody = request.body();
                    if (requestBody != null) {
                        request.body().writeTo(buffer);
                        String body = buffer.readUtf8();
                        Log.e("request", String.format("發送請求 %s on %s  %nRequestParams:%s%nMethod:%s",
                                request.url(), chain.connection(), body, request.method()));
                    }
                }
            } else {
                Log.e("request", String.format("發送請求 %s on %s%nMethod:%s",
                        request.url(), chain.connection(), request.method()));
            }
            Response response = chain.proceed(request);
            long t2 = System.nanoTime();//收到響應的時間
            ResponseBody responseBody = response.peekBody(1024 * 1024);
            Log.e("request",
                    String.format("Retrofit接收響應: %s %n傳回json:【%s】 %n耗時:%.1fms",
                            response.request().url(),
                            responseBody.string(),
                            (t2 - t1) / 1e6d
                    ));
            return response;
        }

    }
           

看一下日志列印的效果,有了日志列印,我們就能輕松的調試每個網絡請求了。

優雅封裝Retrofit+RxJava聯網的統一管理類

image.png

⑥設定離線時緩存,我們可以添加一個CacheInterceptor,在沒網絡的時候,取緩存的response 。在這裡緩存的位置在Android/data/包名/files/okhttpCache...目錄下。

OkHttpClient mClient = new OkHttpClient.Builder()
                         添加離線緩存
                     .cache(new Cache(File(context.getExternalFilesDir("okhttpCache"), ""), 14 * 1024 * 100))
                     .addInterceptor(new CacheInterceptor())
                     .addNetworkInterceptor(new CacheInterceptor())//必須要有,否則會傳回504
                     .build();    

           
/**
     * 設定緩存的攔截器
     */
    public class CacheInterceptor implements Interceptor {

        @Override
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();
            if (!NetUtils.isNetworkConnected(MyApplication.getContext())) {
                request = request.newBuilder().cacheControl(CacheControl.FORCE_CACHE).build();
            }
            Response response = chain.proceed(request);
            if (NetUtils.isNetworkConnected(MyApplication.getContext())) {
                String cacheControl = request.cacheControl().toString();
                Elog.e("Tag", "有網");
                return response.newBuilder().header("Cache-Control", cacheControl)
                        .removeHeader("Pragma").build();
            } else {
                Elog.e("Tag", "無網");
                return response.newBuilder().header("Cache-Control", "public, only-if-cached, max-stale=" + "60 * 60 * 24 * 7")
                        .removeHeader("Pragma").build();
            }
        }
    }
           

判斷網絡狀态,需要添權重限:

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"></uses-permission>
           

下圖為關閉網絡時,擷取到的離線的資料:

優雅封裝Retrofit+RxJava聯網的統一管理類
優雅封裝Retrofit+RxJava聯網的統一管理類
《三》OkHttp的攔截器Interceptor

無論是上面添加header,還是處理log日志列印,或是設定緩存,配置一些公共請求參數等等,都是通過添加攔截器addInterceptor()來實作的,是以攔截器有多重要,就不用我多說了啦~

先舉個簡單的栗子,了解一下攔截器是個什麼東西?

官方介紹:攔截器是一種能夠監控,重寫,重試調用的強大機制。攔截發出的請求和傳入的響應的日志.

打個比方:镖局押着一箱元寶走過一個山間小路,突然從山上下來一群山賊攔住了镖局的去路,将镖局身上值錢的東西搜刮幹淨後将其放行。其中山賊相當于攔截器,镖局相當于一個正在執行任務的網絡請求,請求中的參數就是镖局攜帶的元寶。攔截器可以将網絡請求攜帶的參數進行修改驗證,然後放行。這裡面其實設計了AOP程式設計的思想(

面向切面程式設計

)。

詳細了解可參考:

OkHttp攔截器 Interceptors 攔截器 手把手帶你深入剖析 Retrofit 2.0 源碼

附上RetrofitManager的完整代碼(包括Retrofit檔案的上傳):

package com.zongxueguan.naochanle_android.net.retrofit;

import com.zongxueguan.naochanle_android.global.API;
import com.zongxueguan.naochanle_android.retrofitrx.ApiService;
import com.zongxueguan.naochanle_android.util.UserConstants;
import com.zxg.framework.library.common.log.Elog;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.TimeUnit;

import okhttp3.CacheControl;
import okhttp3.FormBody;
import okhttp3.HttpUrl;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.logging.HttpLoggingInterceptor;
import okio.Buffer;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;

/**
 * Created by JoJo on 2018/4/24.
 * wechat:18510829974
 * description:
 */
public class RetrofitManager {
    /**
     * 請求接口執行個體對象
     */
    private static RetrofitManager mInstance;
    private static final long DEFAULT_TIMEOUT = 60L;
    private Retrofit retrofit = null;
    //請求頭資訊
    private final String HEADER_CONNECTION = "keep-alive";

    public static RetrofitManager getInstance() {
        if (mInstance == null) {
            synchronized (RetrofitManager.class) {
                if (mInstance == null) {
                    mInstance = new RetrofitManager();
                }
            }
        }
        return mInstance;
    }

    public Retrofit getRetrofit() {
        if (retrofit == null) {
            synchronized (RetrofitManager.class) {
                if (retrofit == null) {
                    OkHttpClient mClient = new OkHttpClient.Builder()
                            //添加公告查詢參數
//                          .addInterceptor(new CommonQueryParamsInterceptor())
//                          .addInterceptor(new MutiBaseUrlInterceptor())
//                          添加離線緩存
//                          .cache(new Cache(File(context.getExternalFilesDir("okhttpCache"), ""), 14 * 1024 * 100))
//                          .addInterceptor(new CacheInterceptor())
//                          .addNetworkInterceptor(new CacheInterceptor())//必須要有,否則會傳回504    
                            .addInterceptor(new HeaderInterceptor())
                            .addInterceptor(new LoggingInterceptor())//添加請求攔截(可以在此處列印請求資訊和響應資訊)
                            .connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
                            .writeTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
                            .readTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
                            .build();
                    retrofit = new Retrofit.Builder()
                            .baseUrl(API.getInstance().BASE_API_URL)//基礎URL 建議以 / 結尾
                            .addConverterFactory(GsonConverterFactory.create())//設定 Json 轉換器
                            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())//RxJava 擴充卡
                            .client(mClient)
                            .build();
                }
            }
        }
        return retrofit;
    }

    public ApiService getRequestService() {
        return getRetrofit().create(ApiService.class);
    }

    /**
     * 設定公共查詢參數
     */
    public class CommonQueryParamsInterceptor implements Interceptor {

        @Override
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();
            HttpUrl url = request.url().newBuilder()
                    .addQueryParameter("paramsA", "a")
                    .addQueryParameter("paramsB", "b")
                    .build();
            return chain.proceed(request.newBuilder().url(url).build());
        }
    }

   /**
     * 添加請求頭需要攜帶的參數
     */
    public class HeaderInterceptor implements Interceptor {
        @Override
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();
            Request requestBuilder = request.newBuilder()
                    .addHeader("Connection", HEADER_CONNECTION)
                    .addHeader("token", "token-value")
                    .method(request.method(), request.body())
                    .build();
            return chain.proceed(requestBuilder);
        }
    }

      /**
     * 設定緩存的攔截器
     */
    public class CacheInterceptor implements Interceptor {

        @Override
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();
            if (!NetUtils.isNetworkConnected(MyApplication.getContext())) {
                request = request.newBuilder().cacheControl(CacheControl.FORCE_CACHE).build();
            }
            Response response = chain.proceed(request);
            if (NetUtils.isNetworkConnected(MyApplication.getContext())) {
                String cacheControl = request.cacheControl().toString();
                Elog.e("Tag", "有網");
                return response.newBuilder().header("Cache-Control", cacheControl)
                        .removeHeader("Pragma").build();
            } else {
                Elog.e("Tag", "無網");
                return response.newBuilder().header("Cache-Control", "public, only-if-cached, max-stale=" + "60 * 60 * 24 * 7")
                        .removeHeader("Pragma").build();
            }
        }
    }

    /**
     * log列印:http://blog.csdn.net/csdn_lqr/article/details/61420753
     */
    public class LoggingInterceptor implements Interceptor {
        @Override
        public Response intercept(Chain chain) throws IOException {
            //這個chain裡面包含了request和response,是以你要什麼都可以從這裡拿
            Request request = chain.request();
            long t1 = System.nanoTime();//請求發起的時間
            String method = request.method();
            JSONObject jsonObject = new JSONObject();
            if ("POST".equals(method) || "PUT".equals(method)) {
                if (request.body() instanceof FormBody) {
                    FormBody body = (FormBody) request.body();
                    if (body != null) {
                        for (int i = 0; i < body.size(); i++) {
                            try {
                                jsonObject.put(body.name(i), body.encodedValue(i));
                            } catch (JSONException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                    Elog.e("request", String.format("發送請求 %s on %s  %nRequestParams:%s%nMethod:%s",
                            request.url(), chain.connection(), jsonObject.toString(), request.method()));
                } else {
                    Buffer buffer = new Buffer();
                    RequestBody requestBody = request.body();
                    if (requestBody != null) {
                        request.body().writeTo(buffer);
                        String body = buffer.readUtf8();
                        Elog.e("request", String.format("發送請求 %s on %s  %nRequestParams:%s%nMethod:%s",
                                request.url(), chain.connection(), body, request.method()));
                    }
                }
            } else {
                Elog.e("request", String.format("發送請求 %s on %s%nMethod:%s",
                        request.url(), chain.connection(), request.method()));
            }
            Response response = chain.proceed(request);
            long t2 = System.nanoTime();//收到響應的時間
            ResponseBody responseBody = response.peekBody(1024 * 1024);
            Elog.e("request",
                    String.format("Retrofit接收響應: %s %n傳回json:【%s】 %n耗時:%.1fms",
                            response.request().url(),
                            responseBody.string(),
                            (t2 - t1) / 1e6d
                    ));
            return response;
        }

    }

    /**
     * 列印log日志:該攔截器用于記錄應用中的網絡請求的資訊
     */
    private HttpLoggingInterceptor getHttpLogingInterceptor() {
        HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
            @Override
            public void log(String message) {
                //包含所有的請求資訊
                //如果收到響應是json才列印
                if ("{".equals(message) || "[".equals(message)) {
                    Log.d("TAG", "收到響應: " + message);
                }
                Log.d("TAG", "message=" + message);
            }
        });
        httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        return httpLoggingInterceptor;
    }

    private String BASE_URL_OTHER = "http://wthrcdn.etouch.cn/";

    /**
     * 添加可以處理多個Baseurl的攔截器:http://blog.csdn.net/qq_36707431/article/details/77680252
     * Retrofit(OKHttp)多BaseUrl情況下url實時自動替換完美解決方法:https://www.2cto.com/kf/201708/663977.html

//     http://wthrcdn.etouch.cn/weather_mini?city=北京
//    @Headers({"url_name:other"})
//    @GET("weather_mini")
//    Observable<WeatherEntity> getMessage(@Query("city") String city);
     */
    private class MutiBaseUrlInterceptor implements Interceptor {

        @Override
        public Response intercept(Chain chain) throws IOException {
            //擷取request
            Request request = chain.request();
            //從request中擷取原有的HttpUrl執行個體oldHttpUrl
            HttpUrl oldHttpUrl = request.url();
            //擷取request的建立者builder
            Request.Builder builder = request.newBuilder();
            //從request中擷取headers,通過給定的鍵url_name
            List<String> headerValues = request.headers("url_name");
            if (headerValues != null && headerValues.size() > 0) {
                //如果有這個header,先将配置的header删除,是以header僅用作app和okhttp之間使用
                builder.removeHeader("url_name");
                //比對獲得新的BaseUrl
                String headerValue = headerValues.get(0);
                HttpUrl newBaseUrl = null;
                if ("other".equals(headerValue)) {
                    newBaseUrl = HttpUrl.parse(BASE_URL_OTHER);
//                } else if ("other".equals(headerValue)) {
//                    newBaseUrl = HttpUrl.parse(BASE_URL_PAY);
                } else {
                    newBaseUrl = oldHttpUrl;
                }
                //在oldHttpUrl的基礎上重建新的HttpUrl,修改需要修改的url部分
                HttpUrl newFullUrl = oldHttpUrl
                        .newBuilder()
                        .scheme("http")//更換網絡協定,根據實際情況更換成https或者http
                        .host(newBaseUrl.host())//更換主機名
                        .port(newBaseUrl.port())//更換端口
                        .removePathSegment(0)//移除第一個參數v1
                        .build();
                //重建這個request,通過builder.url(newFullUrl).build();
                // 然後傳回一個response至此結束修改
                Elog.e("Url", "intercept: " + newFullUrl.toString());
                return chain.proceed(builder.url(newFullUrl).build());
            }
            return chain.proceed(request);
        }
    }

    /**
     * Retrofit上傳檔案
     *
     * @param mImagePath
     * @return
     */
    public RequestBody getUploadFileRequestBody(String mImagePath) {
        File file = new File(mImagePath);
        //建構body
        RequestBody requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM)
                .addFormDataPart("file", file.getName(), RequestBody.create(MediaType.parse("multipart/form-data"), file))
                .build();
        return requestBody;
    }
}

           

繼續閱讀