背景
封裝的網絡庫基於okhttp3+retrofit2+rxandroid+rxjava。目的是單獨封裝處理網絡請求,可供項目中多個module使用,所以Demo中代碼模塊拆的比較細。包含hsjokhttp(網絡庫封裝)、hsjlogger(日誌打印)、bean(對象模塊),只需要關注hsjokhttp(網絡庫封裝) 模塊,其餘兩模塊都比較簡單。庫中包含websocket封裝部分,詳細信息請點擊跳轉至 基於OKHttp的websocket封裝使用 。
接入方式
1.導入hsjokhttp module。
- 注意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支持長文本及格式化打印網絡請求等信息,查看非常方便。
- 因爲網絡庫中封裝了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.接入使用
- 在項目的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')
- 新建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);
}
- 新建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信息。
- 封裝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();
}
}
- 定義網絡請求回調類BaseCallback,此類繼承自網路庫中NetCallback,已對請求返回回的參數類型做泛型處理。一般一些需要統一處理的邏輯可以在此處理,比如token失效後需要退出登錄等。
public abstract class BaseCallback<T> extends NetCallback<T> {
/**
* 統一處理的邏輯,都在類似這樣的方法中處理。暴露在每個最外層的callback只處理指定場景下的業務邏輯。
*/
@Override
public void logout() {
}
}
- 網絡請求。
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層級,所以直接傳入回調需要的類型即可。
- 記得在主工程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.網絡庫解析
- 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,都在這個類中。
- 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(返回結果數據格式也不統一)。
- 通過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);
}
}
- 緩存攔截器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;
}
}
-
關於攔截器。
關於攔截器,我借用網上的一張圖,裏面描述的非常清晰。okhttp框架共7層攔截器處理,一般用戶能自定義第一層和倒數第二層網絡處理攔截器。具體的攔截器相關知識,不再這兒做詳細闡述。我在封裝庫中自定義headerInterceptor和CacheInterceptor,都屬於第一層攔截器。在demo中我還自定義了一個NetInterceptor。需要注意的是NetInterceptor中,如果對返回結果進行了處理,則在NetCallback回調中接收不到業務數據。即OnNext方法中返回爲空。 -
返回的業務數據處理邏輯。
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,一併提出。謝謝~