?? Hi贾惦,我是小余替裆。
本文已收錄到 GitHub · Androider-Planet 中英融。這里有 Android 進(jìn)階成長(zhǎng)知識(shí)體系,關(guān)注公眾號(hào) [小余的自習(xí)室] 夕吻,在成功的路上不迷路!
前言
前面一篇文章我們講解了maven私服
的搭建菩暗,maven私服在組件化框架
中有一個(gè)很重要的地位就是可以將我們的lib
庫(kù)放到局域網(wǎng)中,供公司其他開發(fā)者使用旭蠕,實(shí)現(xiàn)類庫(kù)的分享停团。
下面是這個(gè)系列準(zhǔn)備實(shí)現(xiàn)的一個(gè)組件化實(shí)戰(zhàn)項(xiàng)目框架
:
[圖片上傳失敗...(image-1c133-1676861042632)]
- 筆者打算
從下往上
依次來實(shí)現(xiàn)我們項(xiàng)目中的組件,畢竟地基穩(wěn)固了掏熬,房子才可以搭的很結(jié)實(shí)佑稠。
注意
:這里不會(huì)對(duì)封裝代碼進(jìn)行長(zhǎng)篇大論,主要還是以思路點(diǎn)撥
的方式進(jìn)行旗芬,如果需要看完整代碼的可以移步到github舌胶。
GitHub - ByteYuhb/anna_music_app
- 這篇文章實(shí)現(xiàn)的是
一個(gè)lib_network
庫(kù):
實(shí)現(xiàn)一個(gè)組件的封裝前,我們有幾個(gè)步驟岗屏,這幾個(gè)步驟不可或缺辆琅,如果上來就直接碼代碼漱办,最后會(huì)讓你從激情到放棄
的
-
1.先分析需求
-
每個(gè)類的封裝都是有了新的需求一步一步實(shí)現(xiàn)擴(kuò)大的这刷,不可能一蹴而就,包括筆者今天講解的
lib_network
類庫(kù)娩井,后期也會(huì)根據(jù)用戶需求一步一步壯大暇屋。既然是要實(shí)現(xiàn)一個(gè)網(wǎng)絡(luò)請(qǐng)求的封裝庫(kù):主要包括
get
,post
,form表單
請(qǐng)求洞辣,文件上傳和下載
等一些基礎(chǔ)網(wǎng)絡(luò)功能的實(shí)現(xiàn)
-
-
2.根據(jù)需求進(jìn)行技術(shù)選型
- 技術(shù)選型在類庫(kù)封裝中也是一個(gè)重要步驟咐刨,這里我們只是實(shí)現(xiàn)一些網(wǎng)絡(luò)基礎(chǔ)功能,筆者打算在
HttpUrlConnection
扬霜,Volly
和OkHttp
中選擇我們我們的封裝基礎(chǔ)庫(kù)
- 技術(shù)選型在類庫(kù)封裝中也是一個(gè)重要步驟咐刨,這里我們只是實(shí)現(xiàn)一些網(wǎng)絡(luò)基礎(chǔ)功能,筆者打算在
[圖片上傳失敗...(image-4c98bf-1676861042632)]
幾個(gè)待選型的技術(shù)對(duì)比:
HttpUrlConnection
這個(gè)類是一個(gè)比較底層的類庫(kù)了定鸟,如果使用這個(gè)類庫(kù)來封裝,我們需要實(shí)現(xiàn)很多輪子工作著瓶,而在另外兩個(gè)開源框架Volly
和OkHttp
已經(jīng)實(shí)現(xiàn)了這些工作联予,沒必要重復(fù)造輪子,在這里不考慮。
如果您對(duì)庫(kù)有更深層次的要求且對(duì)自己技術(shù)比較自信沸久,可以考慮使用這個(gè)類庫(kù)去封裝實(shí)現(xiàn)季眷,畢竟開源庫(kù)也是在這些基礎(chǔ)庫(kù)上實(shí)現(xiàn)的。
Volly
這個(gè)類庫(kù)是在HttpUrlConnection
基礎(chǔ)上做的一層封裝卷胯,為了減少用戶使用HttpUrlConnection的復(fù)雜度子刮。
一開始出來是google主推的網(wǎng)絡(luò)請(qǐng)求框架,google希望統(tǒng)一Android網(wǎng)路請(qǐng)求庫(kù)推出的一個(gè)框架窑睁。那為什么后面用的人越來越少了呢挺峡。那就是因?yàn)橄旅嫖覀円f的OkHttp
框架。
OkHttp
看過源碼的都知道担钮,OkHttp
也是在HttpUrlConnecttion
上做的封裝沙郭,其繼承了Volly
的優(yōu)勢(shì),且在Volly
上構(gòu)建了自己的有優(yōu)點(diǎn)裳朋,包括:連接池復(fù)用
病线,重試重定向機(jī)制
,攔截器模式
等鲤嫡、
具體關(guān)于OkHttp
的介紹可以參考我的另外一篇文章:
Android體系課 之 OkHttp你想知道的都在這里了--
筆者最后選擇使用OkHttp
來做我們基礎(chǔ)庫(kù)的封裝工作
-
3.封裝思路
前面通過對(duì)
具體需求分析
送挑,并且也做了技術(shù)選型
接下來就是怎么去實(shí)現(xiàn)這個(gè)封裝?需要封裝哪些類暖眼?
我們來回憶下使用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
是直接和用戶打交道的地方,那我們就從這幾個(gè)類下手依次對(duì)其進(jìn)行封裝:
我們先列出來我們技術(shù)方案:
[圖片上傳失敗...(image-6c1c4e-1676861042632)]
有了上面幾個(gè)分析步驟:接下來我們就來實(shí)現(xiàn)具體的封裝流程:
- 1.
Request
請(qǐng)求的封裝 Request:用戶請(qǐng)求類诫肠,在OkHttp中封裝了我們的業(yè)務(wù)請(qǐng)求信息
這里我們創(chuàng)建一個(gè)CommonRequest類來再次封裝我們的Request
筆者只列出了部分類的框架代碼:完整代碼在github上
public class CommonRequest {
/**創(chuàng)建一個(gè)Post的請(qǐng)求司澎,不包括headers
* @return
*/
public static Request createPostRequest(String url, RequestParams params){
return createPostRequest(url,params,null);
}
/**創(chuàng)建一個(gè)Post的請(qǐng)求,包括headers
* @param url
* @param params body的參數(shù)集合
* @param headers header的參數(shù)集合
* @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();
}
/**創(chuàng)建一個(gè)不包含header的get請(qǐng)求
* @return
*/
public static Request createGetRequest(String url,RequestParams params){
return createGetRequest(url,params,null);
}
/**創(chuàng)建一個(gè)Get的請(qǐng)求栋豫,包括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");
/**文件上傳請(qǐng)求
* @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();
}
/**文件下載請(qǐng)求
* @param url
* @return
*/
public static Request createFileDownLoadRequest(String url,RequestParams params){
return createGetRequest(url,params);
}
/**文件下載請(qǐng)求
* @param url
* @return
*/
public static Request createFileDownLoadRequest(String url){
return createGetRequest(url,null);
}
}
可以看到我們?cè)谶@個(gè)類里面創(chuàng)建了幾個(gè)方法:
- 1.
Get請(qǐng)求
- 2.
Post請(qǐng)求
- 3.
文件上傳
- 4.
文件下載
這幾個(gè)請(qǐng)求挤安,已經(jīng)可以基本滿足我們實(shí)戰(zhàn)項(xiàng)目的要求了
-
response
響應(yīng)的封裝
-
由于response請(qǐng)求是在CallBack中返回的,思路就是丧鸯,自定義業(yè)務(wù)層需要的CallBack蛤铜,盡量讓代碼輕量化
這里創(chuàng)建了兩個(gè)CallBack
類:
- 1.
CommonFileResponse
這個(gè)類主要是由來對(duì)文件類型
的請(qǐng)求回調(diào)進(jìn)行封裝:
CommonFileResponse封裝思路:
1.對(duì)失敗相應(yīng)直接通過業(yè)務(wù)層傳遞下來的Listener回調(diào)給業(yè)務(wù)層失敗結(jié)果
2.對(duì)成功的相應(yīng),我們先將輸入流中的數(shù)據(jù)寫入到文件中丛肢,并在主線程中回調(diào)文件下載進(jìn)度給業(yè)務(wù)層围肥。業(yè)務(wù)層可以在獲取文件結(jié)果的同時(shí),也可以獲取文件下載進(jìn)度蜂怎。
/**
* 專門處理文件的回調(diào)
*/
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 = "";
/**
* 將其它線程的數(shù)據(jù)轉(zhuǎn)發(fā)到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
這個(gè)類用處和我們的Retrofit
類似穆刻,將請(qǐng)求轉(zhuǎn)換為我們需要的類,通過Gson
或者fastJson
等框架處理:
/**
* @author anna
* @function 專門處理JSON的回調(diào)
*/
public class CommonJsonCallback implements Callback {
/**
* the logic layer exception, may alter in different app
*/
protected final String RESULT_CODE = "ecode"; // 有返回則對(duì)于http請(qǐng)求來說是成功的杠步,但還有可能是業(yè)務(wù)邏輯上的錯(cuò)誤
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
/**
* 將其它線程的數(shù)據(jù)轉(zhuǎn)發(fā)到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) {
/**
* 此時(shí)還在非UI線程氢伟,因此要轉(zhuǎn)發(fā)
*/
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 {
/**
* 協(xié)議確定后看這里如何修改
*/
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
請(qǐng)求的封裝
-
OkHttpClient
是我們OkHttp
的核心樞紐撰洗,業(yè)務(wù)層以及核心框架層都需要使用到這個(gè)類,我們來思考下怎么去封裝這個(gè)類:
1.我們使用裝飾器模式:在CommonOkHttpClient
中封裝一個(gè)OkHttpClient
對(duì)象腐芍,通過CommonOkHttpClient
去代理這個(gè)OkHttpClient
對(duì)象
來看下我們封裝的代碼:
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);
//設(shè)置是否支持重定向
mOkHttpClientBuilder.followRedirects(true);
//設(shè)置代理
// mOkHttpClientBuilder.proxy()
mOkHttpClientBuilder.sslSocketFactory(HttpsUtils.initSSLSocketFactory(),
HttpsUtils.initTrustManager());
mOkHttpClient = mOkHttpClientBuilder.build();
}
public static OkHttpClient getOkHttpClient() {
return mOkHttpClient;
}
/**
* 通過構(gòu)造好的Request,Callback去發(fā)送請(qǐng)求
*/
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;
}
}
這里面封裝了一個(gè)get
差导,post
,以及文件下載
的請(qǐng)求:
對(duì)于業(yè)務(wù)層只需要調(diào)用:
CommonOkHttpClient.get(...)
CommonOkHttpClient.post(...)
CommonOkHttpClient.downloadFile(..)
總結(jié):
本篇文章主要以封裝思路的方式進(jìn)行講解猪勇,具體代碼可以移步到github
GitHub - ByteYuhb/anna_music_app
對(duì)于大部分類庫(kù)的封裝都可以使用我們上面的思路设褐,再結(jié)合maven私服
的使用∑玻可以很好的將我們代碼作為一個(gè)組件共享給開發(fā)同事使用
組件化道路長(zhǎng)遠(yuǎn),這里我們只是封裝了一個(gè)網(wǎng)絡(luò)請(qǐng)求庫(kù)助析,后面會(huì)不定期對(duì)其他類庫(kù)進(jìn)行封裝,最后整合成一個(gè)完整的組件化框架