網路請求元件封裝

語言: CN / TW / HK

前面一篇文章我們講解了 maven私服 的搭建,maven私服在 元件化框架 中有一個很重要的地位就是可以將我們的 lib 庫放到區域網中,供公司其他開發者使用,實現類庫的分享。

下面是這個系列準備實現的一個 元件化實戰專案框架

  • 筆者打算 從下往上 依次來實現我們專案中的元件, 畢竟地基穩固了,房子才可以搭的很結實

注意 :這裡不會對封裝程式碼進行長篇大論,主要還是以 思路點撥 的方式進行,如果需要看完整程式碼的可以移步到github。

GitHub - ByteYuhb/anna_music_app   (http://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2FByteYuhb%2Fanna_music_app)

  • 這篇文章實現的是 一個lib_network 庫:

實現一個元件的封裝前,我們有幾個步驟,這幾個步驟不可或缺,如果上來就直接碼程式碼,最後會讓你從 激情到放棄

  • 1.先分析需求

    • 每個類的封裝都是有了新的需求一步一步實現擴大的,不可能一蹴而就,包括筆者今天講解的 lib_network 類庫,後期也會根據使用者需求一步一步壯大。

      既然是要實現一個網路請求的封裝庫:主要包括 get , postform表單 請求, 檔案上傳和下載 等一些基礎網路功能的實現

  • 2.根據需求進行 技術選型

    • 技術選型在類庫封裝中也是一個重要步驟,這裡我們只是實現一些網路基礎功能,筆者打算在 HttpUrlConnectionVollyOkHttp 中選擇我們我們的封裝基礎庫

幾個待選型的技術對比:

  • HttpUrlConnection

這個類是一個比較底層的類庫了,如果使用這個類庫來封裝,我們需要實現很多輪子工作,而在另外兩個開源框架 VollyOkHttp 已經實現了這些工作,沒必要重複造輪子,在這裡不考慮。

如果您對庫有更深層次的要求且對自己技術比較自信,可以考慮使用這個類庫去封裝實現,畢竟開源庫也是在這些基礎庫上實現的。

  • Volly

這個類庫是在 HttpUrlConnection 基礎上做的一層封裝,為了減少使用者使用HttpUrlConnection的複雜度。

一開始出來是google主推的網路請求框架,google希望統一Android網路請求庫推出的一個框架。那為什麼後面用的人越來越少了呢。那就是因為下面我們要說的 OkHttp 框架。

  • OkHttp

看過原始碼的都知道, OkHttp 也是在 HttpUrlConnecttion 上做的封裝,其繼承了 Volly 的優勢,且在 Volly 上構建了自己的有優點,包括: 連線池複用重試重定向機制攔截器模式 等、

筆者最後選擇使用 OkHttp 來做我們基礎庫的封裝工作

  • 3.封裝思路

    前面通過對 具體需求分析 ,並且也做了 技術選型

接下來就是怎麼去實現這個封裝?需要封裝哪些類?

我們來回憶下使用OkHttp的方式:

fun testOkHttp(){
val client = OkHttpClient()
val r1:RequestBody = formBodyBuilder.build()
val request = Request.Builder().get().url("http://host:port/api").build()
val response = client.newCall(request).execute() print(response.body()?.bytes())
}

筆者思路: 首先封裝的是和使用者 直接打交道 的類

RequestResponseOkHttpClient 以及非同步情況下的 Callback 是直接和使用者打交道的地方,那我們就從這幾個類下手依次對其進行封裝:

我們先列出來我們技術方案:

有了上面幾個分析步驟:接下來我們就來實現具體的封裝流程:

  • 1. Request 請求的封裝 Request :使用者請求類,在OkHttp中封裝了我們的業務請求資訊

這裡我們建立一個 CommonRequest 類來再次封裝我們的Request

筆者只列出了部分類的框架程式碼:完整程式碼在github上

public class CommonRequest {    /**建立一個Post的請求,不包括headers
* @return
*/
public static Request createPostRequest(String url, RequestParams params){ return createPostRequest(url,params,null);
} /**建立一個Post的請求,包括headers
* @param url
* @param params body的引數集合
* @param headers header的引數集合
* @return
*/
public static Request createPostRequest(String url, RequestParams params, RequestParams headers){
FormBody.Builder mFormBodyBuilder = new FormBody.Builder(); if(params!=null){ for(Map.Entry<String,String> entry:params.urlParams.entrySet()){
mFormBodyBuilder.add(entry.getKey(),entry.getValue());
}
}
Headers.Builder mHeadersBuilder = new Headers.Builder(); if(headers!=null){ for(Map.Entry<String,String> entry:headers.urlParams.entrySet()){
mHeadersBuilder.add(entry.getKey(),entry.getValue());
}
} return new Request.Builder()
.url(url)
.headers(mHeadersBuilder.build())
.post(mFormBodyBuilder.build())
.build();
} /**建立一個不包含header的get請求
* @return
*/
public static Request createGetRequest(String url,RequestParams params){ return createGetRequest(url,params,null);
} /**建立一個Get的請求,包括headers
* @return
*/
public static Request createGetRequest(String url,RequestParams params,RequestParams headers){ StringBuilder stringBuilder = new StringBuilder(url).append("?"); if(params != null){ for(Map.Entry<String,String> entry:params.urlParams.entrySet()){
stringBuilder.append(entry.getKey()).append("=").append(entry.getValue());
}
}
Headers.Builder mHeadersBuilder = new Headers.Builder(); if(headers!=null){ for(Map.Entry<String,String> entry:headers.urlParams.entrySet()){
mHeadersBuilder.add(entry.getKey(),entry.getValue());
}
} return new Request.Builder()
.url(stringBuilder.toString())
.headers(mHeadersBuilder.build())
.get()
.build();
} private static final MediaType FILE_TYPE = MediaType.parse("application/octet-stream"); /**檔案上傳請求
* @return
*/
public static Request createMultiPostRequest(String url,RequestParams params){
MultipartBody.Builder requestBuilder = new MultipartBody.Builder();
requestBuilder.setType(MultipartBody.FORM); if(params != null){ for (Map.Entry<String, Object> entry : params.fileParams.entrySet()) { if (entry.getValue() instanceof File) {
requestBuilder.addPart(Headers.of("Content-Disposition","form-data; name="" + entry.getKey() + """),
RequestBody.create(FILE_TYPE, (File) entry.getValue()));
}else if (entry.getValue() instanceof String) {
requestBuilder.addPart(Headers.of("Content-Disposition", "form-data; name="" + entry.getKey() + """),
RequestBody.create(null, (String) entry.getValue()));
}
}
} return new Request.Builder().url(url).post(requestBuilder.build()).build();
} /**檔案下載請求
* @param url
* @return
*/
public static Request createFileDownLoadRequest(String url,RequestParams params){ return createGetRequest(url,params);
} /**檔案下載請求
* @param url
* @return
*/
public static Request createFileDownLoadRequest(String url){ return createGetRequest(url,null);
}
}

可以看到我們在這個類裡面建立了幾個方法:

  • 1. Get請求

  • 2. Post請求

  • 3. 檔案上傳

  • 4. 檔案下載

這幾個請求,已經可以基本滿足我們實戰專案的要求了

response

由於response請求是在CallBack中返回的,思路就是,自定義業務層需要的CallBack,儘量讓程式碼輕量化

這裡建立了兩個 CallBack 類:

  • 1. CommonFileResponse

這個類主要是由來對 檔案型別 的請求回撥進行封裝:

CommonFileResponse封裝思路:

  • 1.對失敗相應直接通過業務層傳遞下來的Listener回撥給業務層失敗結果

  • 2.對成功的相應,我們先將輸入流中的資料寫入到檔案中,並在主執行緒中回撥檔案下載進度給業務層。業務層可以在獲取檔案結果的同時,也可以獲取檔案下載進度。

/**
* 專門處理檔案的回撥
*/public class CommonFileCallBack implements Callback { /**
* the java layer exception, do not same to the logic error
*/
protected final int NETWORK_ERROR = -1; // the network relative error
protected final int IO_ERROR = -2; // the JSON relative error
protected final String EMPTY_MSG = ""; /**
* 將其它執行緒的資料轉發到UI執行緒
*/
private static final int PROGRESS_MESSAGE = 0x01; private Handler mDeliveryHandler; private DisposeDownloadListener mListener; private String mFilePath; private int mProgress; public CommonFileCallBack(DisposeDataHandle handle){ this.mListener = (DisposeDownloadListener) handle.mListener; this.mFilePath = handle.mSource; this.mDeliveryHandler = new Handler(Looper.getMainLooper()){ @Override
public void handleMessage(@NonNull Message msg) { switch (msg.what) { case PROGRESS_MESSAGE:
mListener.onProgress((int) msg.obj); break;
}
}
};
} @Override
public void onFailure(Call call, IOException e) {
mDeliveryHandler.post(new Runnable() { @Override
public void run() {
mListener.onFailure(new OkHttpException(NETWORK_ERROR, e));
}
});
} @Override
public void onResponse(Call call, Response response) throws IOException { final File file = handleResponse(response);
mDeliveryHandler.post(new Runnable() { @Override
public void run() { if (file != null) {
mListener.onSuccess(file);
} else {
mListener.onFailure(new OkHttpException(IO_ERROR, EMPTY_MSG));
}
}
});
} private File handleResponse(Response response) {
... while ((length = inputStream.read(buffer)) != -1) {
fos.write(buffer, 0, length);
currentLength += length;
mProgress = (int) (currentLength / sumLength * 100);
mDeliveryHandler.obtainMessage(PROGRESS_MESSAGE, mProgress).sendToTarget();
}
fos.flush();
} catch (Exception e) {
file = null;
} finally {
...
} return file;
}


}

2. CommonJsonResponse

這個類用處和我們的 Retrofit 類似,將請求轉換為我們需要的類,通過 Gson 或者 fastJson 等框架處理:

/**
* @author anna
* @function 專門處理JSON的回撥
*/public class CommonJsonCallback implements Callback { /**
* the logic layer exception, may alter in different app
*/
protected final String RESULT_CODE = "ecode"; // 有返回則對於http請求來說是成功的,但還有可能是業務邏輯上的錯誤
protected final int RESULT_CODE_VALUE = 0; protected final String ERROR_MSG = "emsg"; protected final String EMPTY_MSG = ""; /**
* the java layer exception, do not same to the logic error
*/
protected final int NETWORK_ERROR = -1; // the network relative error
protected final int JSON_ERROR = -2; // the JSON relative error
protected final int OTHER_ERROR = -3; // the unknow error


/**
* 將其它執行緒的資料轉發到UI執行緒
*/
private Handler mDeliveryHandler; private DisposeDataListener mListener; private Class<?> mClass; public CommonJsonCallback(DisposeDataHandle handle) { this.mListener = handle.mListener; this.mClass = handle.mClass; this.mDeliveryHandler = new Handler(Looper.getMainLooper());
} @Override
public void onFailure(final Call call, final IOException ioexception) { /**
* 此時還在非UI執行緒,因此要轉發
*/
mDeliveryHandler.post(new Runnable() { @Override
public void run() {
mListener.onFailure(new OkHttpException(NETWORK_ERROR, ioexception));
}
});
} @Override
public void onResponse(final Call call, final Response response) throws IOException { final String result = response.body().string();
mDeliveryHandler.post(new Runnable() { @Override
public void run() {
handleResponse(result);
}
});
} private void handleResponse(Object responseObj) { if (responseObj == null || responseObj.toString().trim().equals("")) {
mListener.onFailure(new OkHttpException(NETWORK_ERROR, EMPTY_MSG)); return;
} try { /**
* 協議確定後看這裡如何修改
*/
JSONObject result = new JSONObject(responseObj.toString()); if (mClass == null) {
mListener.onSuccess(result);
} else { Object obj = new Gson().fromJson(responseObj.toString(), mClass); if (obj != null) {
mListener.onSuccess(obj);
} else {
mListener.onFailure(new OkHttpException(JSON_ERROR, EMPTY_MSG));
}
}
} catch (Exception e) {
mListener.onFailure(new OkHttpException(OTHER_ERROR, e.getMessage()));
e.printStackTrace();
}
}
}
OkHttpClient

OkHttpClient 是我們 OkHttp 的核心樞紐,業務層以及核心框架層都需要使用到這個類,我們來思考下怎麼去封裝這個類:

1.我們使用裝飾器模式:在 CommonOkHttpClient 中封裝一個 OkHttpClient 物件,通過 CommonOkHttpClient 去代理這個 OkHttpClient 物件

來看下我們封裝的程式碼:

public class CommonOkHttpClient {    private static final int TIME_OUT = 30;    private static OkHttpClient mOkHttpClient;    static {
OkHttpClient.Builder mOkHttpClientBuilder = new OkHttpClient.Builder();
mOkHttpClientBuilder.hostnameVerifier(new HostnameVerifier() { @Override
public boolean verify(String hostname, SSLSession session) { return true;
}
}); //新增自定義的攔截器
mOkHttpClientBuilder.addInterceptor(new Interceptor() { @Override
public Response intercept(Chain chain) throws IOException { Request request = chain.request()
.newBuilder()
.addHeader("User-Agent","anna-movie")
.build(); return chain.proceed(request);
}
});
mOkHttpClientBuilder.cookieJar(new SimpleCookieJar());
mOkHttpClientBuilder.connectTimeout(TIME_OUT, TimeUnit.SECONDS);
mOkHttpClientBuilder.readTimeout(TIME_OUT, TimeUnit.SECONDS);
mOkHttpClientBuilder.writeTimeout(TIME_OUT, TimeUnit.SECONDS); //設定是否支援重定向
mOkHttpClientBuilder.followRedirects(true); //設定代理
// mOkHttpClientBuilder.proxy()
mOkHttpClientBuilder.sslSocketFactory(HttpsUtils.initSSLSocketFactory(),
HttpsUtils.initTrustManager());
mOkHttpClient = mOkHttpClientBuilder.build();
} public static OkHttpClient getOkHttpClient() { return mOkHttpClient;
} /**
* 通過構造好的Request,Callback去傳送請求
*/
public static Call get(Request request, DisposeDataHandle handle) { Call call = mOkHttpClient.newCall(request);
call.enqueue(new CommonJsonCallback(handle)); return call;
} public static Call post(Request request, DisposeDataHandle handle) { Call call = mOkHttpClient.newCall(request);
call.enqueue(new CommonJsonCallback(handle)); return call;
} public static Call downloadFile(Request request, DisposeDataHandle handle) { Call call = mOkHttpClient.newCall(request);
call.enqueue(new CommonFileCallBack(handle)); return call;
}
}

這裡面封裝了一個 getpost ,以及 檔案下載 的請求:

對於業務層只需要呼叫:

CommonOkHttpClient.get(...)
CommonOkHttpClient.post(...)
CommonOkHttpClient.downloadFile(..)複製程式碼

總結:

對於大部分類庫的封裝都可以使用我們上面的思路,再結合 maven私服 的使用。可以很好的將我們程式碼作為一個元件共享給開發同事使用

元件化道路長遠,這裡我們只是封裝了一個網路請求庫,後面會不定期對其他類庫進行封裝,最後整合成一個完整的元件化框架

連結:http://juejin.cn/post/7119281692350611493

關注我獲取更多知識或者投稿