基於okhttp3+retrofit2封裝的網絡庫(含websocket封裝)

Demo及源碼地址請點擊

背景

封裝的網絡庫基於okhttp3+retrofit2+rxandroid+rxjava。目的是單獨封裝處理網絡請求,可供項目中多個module使用,所以Demo中代碼模塊拆的比較細。包含hsjokhttp(網絡庫封裝)、hsjlogger(日誌打印)、bean(對象模塊),只需要關注hsjokhttp(網絡庫封裝) 模塊,其餘兩模塊都比較簡單。庫中包含websocket封裝部分,詳細信息請點擊跳轉至 基於OKHttp的websocket封裝使用

接入方式

1.導入hsjokhttp module。

  1. 注意module中gradle文件裏,對okhttp等一系列第三方庫的依賴不要丟。
	implementation 'com.squareup.okhttp3:okhttp:4.2.2'
    implementation "io.reactivex.rxjava2:rxjava:2.2.0"
    implementation "io.reactivex.rxjava2:rxandroid:2.1.0"
    implementation 'com.squareup.retrofit2:retrofit:2.6.2'
    implementation 'com.squareup.retrofit2:adapter-rxjava2:2.6.2'
    implementation 'com.squareup.retrofit2:converter-gson:2.6.2'

其中依賴的版本號,可結合自己項目的實際情況更新。日誌打印module可以去掉,或者自己調整。我在網絡庫中依賴它是因爲這個logger支持長文本及格式化打印網絡請求等信息,查看非常方便。

  1. 因爲網絡庫中封裝了CacheInterceptor,這個Interceptor裏面有判斷網絡連接狀態,所以需要在module中AndroidManifest.xml文件添加ACCESS_NETWORK_STATE權限。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.hsj.okhttp" >
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
</manifest>

2.接入使用

  1. 在項目的app(主) module的gradle文件中依賴網絡庫及其他相關庫。
	implementation "io.reactivex.rxjava2:rxjava:2.2.0"
    implementation "io.reactivex.rxjava2:rxandroid:2.1.0"
    implementation 'com.squareup.okhttp3:okhttp:4.2.2'
    implementation 'com.squareup.retrofit2:retrofit:2.6.2'
    implementation 'com.squareup.retrofit2:converter-gson:2.6.2'
    implementation project(path: ':hsjokhttp')
    implementation project(path: ':bean')
    implementation project(path: ':hsjlogger')
  1. 新建OkHttpApi接口文件,定義各網絡請求接口。
public interface OkHttpApi {

    //get請求方式
    @GET(NetConstants.URL_GET_TOKEN)
    Observable<ResponseBody> getToken();

    //獲取短信驗證碼
    @POST("code/sms")
    @FormUrlEncoded
    Observable<ResponseBody> getCodeSms(@FieldMap Map<String, String> bean);

    //某維度下的標籤
    @POST("story/tag/dimension/notlogin")
    Observable<ResponseBody> getTag(@Body RequestBody bean);

    //上傳頭像圖片
    @POST("helper/upload/file/resource")
    Observable<ResponseBody> uploadImgFile(@Body MultipartBody multipartBody);
}
  1. 新建BaseSubscribe文件,主要是基於網絡庫創建client,生成實例化網絡請求的API。具體請看完整示例代碼。
public class BaseSubscribe {
    public static MediaType JSON = MediaType.parse("application/json; charset=utf-8");
    private static final String TOKEN = "";
    private static final String USERID = "";

    private static OkHttpApi okHttpApi;

    public static OkHttpApi getOkHttpApi() {
        if (okHttpApi==null) {
            okHttpApi = newApi(NetConstants.BASE_URL, getHeaderParams(TOKEN, USERID), OkHttpApi.class);
        }
        return okHttpApi;
    }

    /**
     * 關鍵方法,可以根據需求產出不同的api
     * @param baseUrl
     * @param params
     * @param apiClass
     * @param <T>
     * @return
     */
    public static <T>T newApi(String baseUrl, Map<String, String> params, Class<T> apiClass) {
        NetBuilder builder = new NetBuilder()
                .setConnectTimeout(HsjNetConstants.DEFAULT_CONNECT_TIMEOUT)
                .setReadTimeout(HsjNetConstants.DEFAULT_READ_TIMEOUT)
                .setWriteTimeout(HsjNetConstants.DEFAULT_WRITE_TIMEOUT)
                .setRetryOnConnectionFailure(true);
        //添加header
        if (params != null && params.size()>0) {
            HeaderInterceptor headerInterceptor = new HeaderInterceptor(params);
            builder.addInterceptor(headerInterceptor);
        }
        //添加緩存
        File cacheFile = new File(App.getAppContext().getExternalCacheDir(), HsjNetConstants.CACHE_NAME);
        Cache cache = new Cache(cacheFile, 1024 * 1024 * 50);
        builder.addCache(cache);

        //添加緩存攔截器
        CacheInterceptor cacheInterceptor = new CacheInterceptor(App.getAppContext());
        builder.addInterceptor(cacheInterceptor);

//        builder.addNetInterceptor(new NetInterceptor());

        return builder.build(baseUrl).create(apiClass);
    }

    private static Map<String, String> getHeaderParams(String token, String userId) {
        String timestamp, signature;

        timestamp = String.valueOf(System.currentTimeMillis() / 1000);

        Map<String, String> params = new HashMap<>();
        if (!TextUtils.isEmpty(token)) {
            params.put("token", token);
        }
        if (!TextUtils.isEmpty(userId) && !"null".equals(userId)) {
            params.put("userId", userId);
        }
        params.put("nounce", "");
        params.put("timestamp", timestamp);
        signature = "";
        params.put("signature", signature);

        return params;
    }

    /**
     * 設置訂閱 和 所在的線程環境
     * @param o
     * @param s
     */
    public static void toSubscribe(Observable<ResponseBody> o, DisposableObserver<ResponseBody> s) {
        o.subscribeOn(Schedulers.io())
                .unsubscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .retry(1)//請求失敗重連次數
                .subscribe(s);
    }

需要注意的是newApi方法,這個方法裏面定義了網絡請求中各種參數添加設置。比如連接等超時、通過攔截器方式添加header信息、緩存文件及大小、緩存攔截器、網絡攔截器等。這個地方可以根據自己的實際需求設置。
若有多個api文件,則需要在此註冊多個api。
header信息,我單獨封裝了一個方法,以Map形式傳入攔截器,一般可以在此添加一些signature信息。

  1. 封裝HsjSubscribe文件,這個是基於api,定義自己的網絡請求方法,一般與api中網絡請求接口一一對應。Demo中的示例支持多種請求方式。Get、Post(body爲JSON格式、body爲Form形式)、文件上傳 等示例。
public class HsjSubscribe {
    /**
     * 獲取標籤
     * @param subscriber
     */
    public static void getTag(DisposableObserver<ResponseBody> subscriber) {
        Map<String,String> map = new HashMap<>();
        map.put("dimension", "4");
        map.put("language", "zh_CN");
        Observable<ResponseBody> observable = BaseSubscribe.getOkHttpApi().getTag(RequestBody.create(new Gson().toJson(map), BaseSubscribe.JSON));
        BaseSubscribe.toSubscribe(observable, subscriber);
    }

    public static void getCodeSms(String mobile, DisposableObserver<ResponseBody> subscriber) {
        Map<String,String> map = new HashMap<>();
        map.put("mobile", mobile);
        Observable<ResponseBody> observable = BaseSubscribe.getOkHttpApi().getCodeSms(map);
        BaseSubscribe.toSubscribe(observable, subscriber);
    }

    /**
     * 獲取標籤
     * @param subscriber
     */
    public static void getToken(DisposableObserver<ResponseBody> subscriber) {
        Observable<ResponseBody> observable = BaseSubscribe.getOkHttpApi().getToken();
        BaseSubscribe.toSubscribe(observable, subscriber);
    }

    /**
     * 上傳頭像接口
     * type 1=image 2=audio
     */
    public static void uploadFileToGetUrl(int type, File file, DisposableObserver<ResponseBody> subscriber) {
        Observable<ResponseBody> observable = BaseSubscribe.getOkHttpApi().uploadImgFile(filesToMultipartBody(file, type));
        BaseSubscribe.toSubscribe(observable, subscriber);
    }

    private static MultipartBody filesToMultipartBody(File file, int type) {
        MultipartBody.Builder builder = new MultipartBody.Builder().setType(MultipartBody.FORM);
        RequestBody requestBody = RequestBody.create(file, MediaType.parse(type == 1 ? "image/*" : "audio/*"));
        builder.addFormDataPart("file", file.getName(), requestBody);
        return builder.build();
    }
}
  1. 定義網絡請求回調類BaseCallback,此類繼承自網路庫中NetCallback,已對請求返回回的參數類型做泛型處理。一般一些需要統一處理的邏輯可以在此處理,比如token失效後需要退出登錄等。
public abstract class BaseCallback<T> extends NetCallback<T> {
    /**
     * 統一處理的邏輯,都在類似這樣的方法中處理。暴露在每個最外層的callback只處理指定場景下的業務邏輯。
     */
    @Override
    public void logout() {

    }
}
  1. 網絡請求。
	public void getToken(View view) {
        HsjLogger.d("getToken");
        HsjSubscribe.getToken(new BaseCallback<Token>() {
            @Override
            public void onSuccess(Token result) {
                HsjLogger.d("--getToken--onSuccess--");
            }

            @Override
            public void onFault(int code, String errorMsg) {
                HsjLogger.d("--getToken--onFault--");
            }

            @Override
            public void netError() {
                HsjLogger.d("--getToken--netError--");
            }
        });
    }

    public void uploadFile(View view) {
        File file = new File(getExternalFilesDir(null) + File.separator + "image/icon_weixin_kefu.png");
        HsjSubscribe.uploadFileToGetUrl(1, file, new BaseCallback<FileUploadResponse>() {
            @Override
            public void onSuccess(FileUploadResponse result) {
                HsjLogger.d("--upload--onSuccess--");
            }

            @Override
            public void onFault(int code, String errorMsg) {
                HsjLogger.d("--upload--onFault--");
            }

            @Override
            public void netError() {
                HsjLogger.d("--upload--netError--");
            }
        });
    }

    public void jsonRequest(View view) {
        HsjSubscribe.getTag(new BaseCallback<List<Tag>>() {

            @Override
            public void onSuccess(List<Tag> result) {
                HsjLogger.d("--onSuccess--");
            }

            @Override
            public void onFault(int code, String errorMsg) {
                HsjLogger.d("--onFault--" + errorMsg);
            }

            @Override
            public void netError() {
                HsjLogger.d("--netError--");
            }
        });
    }

    public void formRequest(View view) {
        HsjSubscribe.getCodeSms("13079079998", new BaseCallback<Object>() {
            @Override
            public void onSuccess(Object result) {
                HsjLogger.d("--login--onSuccess--");
            }

            @Override
            public void onFault(int code, String errorMsg) {
                HsjLogger.d("--login--onFault--");
            }

            @Override
            public void netError() {
                HsjLogger.d("--login--netError--");
            }
        });
    }

以上示例分別是Demo中各類請求示例。如果一個統一的返回體格式是{“success”:true,“code”:“20000”,“message”:“成功”,“data”:{}},目前的封裝已處理到data層級,所以直接傳入回調需要的類型即可。

  1. 記得在主工程AndroidManifest.xml文件中添加網絡請求等權限。
	<uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

3.網絡庫解析

  1. BaseSubscribe類中newApi方法,NetBuilder解析。
public class NetBuilder {

    private Retrofit retrofit;
    private OkHttpClient.Builder okHttpBuilder;
    public NetBuilder() {
        //手動創建一個OkHttpClient並設置超時時間
        okHttpBuilder = new OkHttpClient.Builder();
    }

    /**
     * 連接超時
     * @param outTime
     * @return
     */
    public NetBuilder setConnectTimeout(int outTime) {
        okHttpBuilder.connectTimeout(outTime, TimeUnit.SECONDS);
        return this;
    }

    /**
     * 讀超時
     * @param outTime
     * @return
     */
    public NetBuilder setReadTimeout(int outTime) {
        okHttpBuilder.readTimeout(outTime, TimeUnit.SECONDS);
        return this;
    }

    /**
     * 寫超時
     * @param outTime
     * @return
     */
    public NetBuilder setWriteTimeout(int outTime) {
        okHttpBuilder.writeTimeout(outTime, TimeUnit.SECONDS);
        return this;
    }

    /**
     * 錯誤重連
     * @param retry
     * @return
     */
    public NetBuilder setRetryOnConnectionFailure(boolean retry) {
        okHttpBuilder.retryOnConnectionFailure(retry);
        return this;
    }

    /**
     * 添加攔截器
     * @param interceptor
     * @return
     */
    public NetBuilder addInterceptor(Interceptor interceptor) {
        okHttpBuilder.addInterceptor(interceptor);
        return this;
    }

    /**
     * 添加倒數第二層攔截器
     * @param interceptor
     * @return
     */
    public NetBuilder addNetInterceptor(Interceptor interceptor) {
        okHttpBuilder.addNetworkInterceptor(interceptor);
        return this;
    }

    /**
     * 添加緩存文件
     * @param cache
     * @return
     */
    public NetBuilder addCache(Cache cache) {
        okHttpBuilder.cache(cache);
        return this;
    }

    public Retrofit build(String url) {
        retrofit = new Retrofit.Builder()
                .client(okHttpBuilder.build())
                .addConverterFactory(GsonConverterFactory.create())//json轉換成JavaBean
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .baseUrl(url)
                .build();
        return retrofit;
    }
}

建造者模式實例化OkHttpClient,並提供各類超時設置方法、添加攔截器、以及實例化Retrofit,都在這個類中。

  1. NetCallback類繼承自DisposableObserver。並實現自定義的APICallback。
public abstract class NetCallback<T> extends DisposableObserver<ResponseBody> implements APICallback<T> {
    /**
     * 處理各種業務邏輯及錯誤
     * @param body
     */
    @Override
    public void onNext(ResponseBody body) {
        try {
            String result = body.string();
            HsjLogger.longInfo(result);
            JSONObject jsonObject = new JSONObject(result);
            String resultCode = jsonObject.getString("code");
            if ("20000".equals(resultCode)) {
                Gson gson = new Gson();
                String data = jsonObject.getString("data");
                if (!TextUtils.isEmpty(data)) {
                    HsjLogger.longInfo(data);
                    T t = gson.fromJson(data, getType());
                    onSuccess(t);
                }
            } else if ("00011".equals(resultCode)) {
                //token過期,需要重新登錄
                logout();
            } else {
                String errorMsg = jsonObject.getString("message");
                onFault(0, errorMsg);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 請求出錯,包含網絡錯誤
     * @param e
     */
    @Override
    public void onError(Throwable e) {
        HsjLogger.d(e.getMessage());
        netError();
    }

    @Override
    public void onComplete() {

    }

    private final Type getType() {
        /**獲取第一個泛型類型*/
        ParameterizedType parameterizedType = (ParameterizedType) getClass().getGenericSuperclass();
        return parameterizedType.getActualTypeArguments()[0];
    }
}
public interface APICallback<T> {
    void logout();

    void onSuccess(T result);

    void onFault(int code, String errorMsg);

    void netError();
}

如果是網絡錯誤,包括400、404等等,則會回調onError(Throwable e),所以所有網絡錯誤處理可以放在這裏,也可以像logout() 一樣,拋回BaseCallback處理。只要是http返回code=200後的業務邏輯處理都會在onNext(ResponseBody body) 中。比如token過期,需要重新登錄。

也可以自己重定義APICallback接口,按照自己的業務邏輯刪減接口方法。總之最終網絡請求回調的實現方法+BaseCallback類中實現的方法數=DisposableObserver+APICallback類中的方法數

該庫還可以靈活調整,比如多個api,或者多個callback。我們公司的項目經歷過幾任前後端開發的同學,導致用戶中心模塊、一般業務服務器接口等baseUrl不一樣,請求參數處理方式也不一樣,等等。就涉及到需要多個api(一個baseurl=需要一個api),多個callback(返回結果數據格式也不統一)。

  1. 通過Interceptor添加header。
public class HeaderInterceptor implements Interceptor {
    private Map<String, String> params;

    public HeaderInterceptor(Map<String, String> p) {
        this.params = p;
    }

    @NotNull
    @Override
    public Response intercept(@NotNull Chain chain) throws IOException {
        Request originalRequest = chain.request();
        //設置頭部信息
        Request.Builder requestBuilder = originalRequest.newBuilder();
        if (params != null && params.size() > 0) {
            for (Map.Entry<String, String> entry : params.entrySet()) {
                String value = entry.getValue();
                if (TextUtils.isEmpty(value) || "null".equals(value)) {
                    value = "";
                }
                requestBuilder.addHeader(entry.getKey(), value);
            }
        }

        requestBuilder.method(originalRequest.method(), originalRequest.body());
        Request request = requestBuilder.build();

        //打印請求信息,正式項目中可以註釋掉
        RequestBody requestBody = request.body();
        String body = null;
        if (requestBody != null) {
            Buffer buffer = new Buffer();
            requestBody.writeTo(buffer);

            body = buffer.readString(Charset.forName("UTF-8"));
        }
        HsjLogger.longInfo(String.format("發送請求\nmethod:%s\nurl:%s\nheaders: %sbody:%s",
                request.method(), request.url(), request.headers(), body));

        return chain.proceed(request);
    }
}
  1. 緩存攔截器CacheInterceptor。
public class CacheInterceptor implements Interceptor {
    private Context mContext;
    public CacheInterceptor(Context context) {
        mContext = context;
    }

    @NotNull
    @Override
    public Response intercept(@NotNull Chain chain) throws IOException {
        Request request = chain.request();
        if (!NetUtil.isNetworkConnected(mContext)) {
            request = request.newBuilder()
                    .cacheControl(CacheControl.FORCE_CACHE)
                    .build();
        }
        Response response = chain.proceed(request);
        if (NetUtil.isNetworkConnected(mContext)) {
            int maxAge = 0;
            // 有網絡時 設置緩存超時時間0個小時
            response.newBuilder()
                    .header("Cache-Control", "public, max-age=" + maxAge)
                    .removeHeader(HsjNetConstants.CACHE_NAME)// 清除頭信息,因爲服務器如果不支持,會返回一些干擾信息,不清除下面無法生效
                    .build();
        }  else {
            // 無網絡時,設置超時爲4周
            int maxStale = 60 * 60 * 24 * 28;
            response.newBuilder()
                    .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
                    .removeHeader(HsjNetConstants.CACHE_NAME)
                    .build();
        }
        return response;
    }
}
  1. 關於攔截器。
    攔截器時序圖
    關於攔截器,我借用網上的一張圖,裏面描述的非常清晰。okhttp框架共7層攔截器處理,一般用戶能自定義第一層和倒數第二層網絡處理攔截器。具體的攔截器相關知識,不再這兒做詳細闡述。我在封裝庫中自定義headerInterceptor和CacheInterceptor,都屬於第一層攔截器。在demo中我還自定義了一個NetInterceptor。需要注意的是NetInterceptor中,如果對返回結果進行了處理,則在NetCallback回調中接收不到業務數據。即OnNext方法中返回爲空。

  2. 返回的業務數據處理邏輯。
    NetCallback類中泛型的使用,讓返回的業務數據類型處理方便很多。主要邏輯就是網絡請求時,實例化的callback將需要的返回類型傳遞至NetCallback類。在NetCallback類onNext(ResponseBody body)方法中,通過Gson處理,將返回結果轉爲目標類型,返回給上層調用類。

			String result = body.string();
            JSONObject jsonObject = new JSONObject(result);
            String resultCode = jsonObject.getString("code");
            if ("20000".equals(resultCode)) {
                Gson gson = new Gson();
                String data = jsonObject.getString("data");
                if (!TextUtils.isEmpty(data)) {
                    T t = gson.fromJson(data, getType());
                    onSuccess(t);
                }
            }

getType()方法是獲取該回調類中第一個泛型類型。

	private final Type getType() {
        /**獲取第一個泛型類型*/
        ParameterizedType parameterizedType = (ParameterizedType) getClass().getGenericSuperclass();
        return parameterizedType.getActualTypeArguments()[0];
    }

4.結束語

我會持續更新該網絡封裝庫。並歡迎各位將使用過程中遇到的問題,及使用的不便之處反饋給我,我持續更新在封裝庫中。也歡迎各位有更好的優化的idea,一併提出。謝謝~

Demo及源碼地址請點擊

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章