網路請求元件封裝
前面一篇文章我們講解了 maven私服
的搭建,maven私服在 元件化框架
中有一個很重要的地位就是可以將我們的 lib
庫放到區域網中,供公司其他開發者使用,實現類庫的分享。
下面是這個系列準備實現的一個 元件化實戰專案框架
:
-
筆者打算
從下往上
依次來實現我們專案中的元件, 畢竟地基穩固了,房子才可以搭的很結實 。
注意
:這裡不會對封裝程式碼進行長篇大論,主要還是以 思路點撥
的方式進行,如果需要看完整程式碼的可以移步到github。
GitHub - ByteYuhb/anna_music_app (https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2FByteYuhb%2Fanna_music_app)
-
這篇文章實現的是
一個lib_network
庫:
實現一個元件的封裝前,我們有幾個步驟,這幾個步驟不可或缺,如果上來就直接碼程式碼,最後會讓你從 激情到放棄
的
-
1.先分析需求
-
每個類的封裝都是有了新的需求一步一步實現擴大的,不可能一蹴而就,包括筆者今天講解的
lib_network
類庫,後期也會根據使用者需求一步一步壯大。既然是要實現一個網路請求的封裝庫:主要包括
get
,post
,form表單
請求,檔案上傳和下載
等一些基礎網路功能的實現 -
2.根據需求進行 技術選型
-
技術選型在類庫封裝中也是一個重要步驟,這裡我們只是實現一些網路基礎功能,筆者打算在
HttpUrlConnection
,Volly
和OkHttp
中選擇我們我們的封裝基礎庫
幾個待選型的技術對比:
-
HttpUrlConnection
這個類是一個比較底層的類庫了,如果使用這個類庫來封裝,我們需要實現很多輪子工作,而在另外兩個開源框架 Volly
和 OkHttp
已經實現了這些工作,沒必要重複造輪子,在這裡不考慮。
如果您對庫有更深層次的要求且對自己技術比較自信,可以考慮使用這個類庫去封裝實現,畢竟開源庫也是在這些基礎庫上實現的。
-
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())
}
筆者思路:
首先封裝的是和使用者 直接打交道
的類
:
Request
, Response
和 OkHttpClient
以及非同步情況下的 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;
}
}
這裡面封裝了一個 get
, post
,以及 檔案下載
的請求:
對於業務層只需要呼叫:
CommonOkHttpClient.get(...) CommonOkHttpClient.post(...) CommonOkHttpClient.downloadFile(..)複製程式碼
總結:
對於大部分類庫的封裝都可以使用我們上面的思路,再結合 maven私服
的使用。可以很好的將我們程式碼作為一個元件共享給開發同事使用
元件化道路長遠,這裡我們只是封裝了一個網路請求庫,後面會不定期對其他類庫進行封裝,最後整合成一個完整的元件化框架
連結:https://juejin.cn/post/7119281692350611493
關注我獲取更多知識或者投稿
- 【建議收藏】17個XML佈局小技巧
- Kotlin擴充套件方法進化之Context Receiver
- 網路請求元件封裝
- Flutter 繪製探索 | 箭頭端點的設計
- 在網頁上除錯手機上的APP
- Android應用安全解決方案
- Android應用安全開發之元件安全淺談
- Android 元件化架構設計從原理到實戰
- 涉作業系統技術領域,華為公開安卓應用程式遷移相關專利
- 避坑!!webview如何載入pdf ?
- Hook AMS APT實現集中式登入框架
- Retrofit是如何支援協程的
- 自定義雙向繫結框架-只需一個註解,簡單實用
- Android 10、11 儲存完全適配
- Java註解和註解解析器深耕,架構師必會
- 寫個更牛逼的Transform | Plugin 進階教程
- Android 騰訊 Matrix 原理分析(一):Matrix 概覽
- ART視角 | 如何自動回收native記憶體
- Android Gradle 多渠道打包
- Iterator 這些點你GET到了嗎?