網(wǎng)絡(luò)請(qǐng)求框架算是android體系當(dāng)中一個(gè)比較重要的部分柔昼,在android歷史中關(guān)于網(wǎng)絡(luò)的演進(jìn)也經(jīng)歷了幾個(gè)階段病往,到目前為止,比較通用的網(wǎng)絡(luò)請(qǐng)求框架就是OkHttp + Retrofit +RxJava+Gson,當(dāng)然,關(guān)于這個(gè)組合使用網(wǎng)上也有很多句喜,但是,那個(gè)畢竟是別人的東西沟于,大部分時(shí)候只有適合自己的才是最好的咳胃,所以,自己封裝一個(gè)網(wǎng)絡(luò)請(qǐng)求框架就顯得比較重要了旷太,廢話不多說(shuō)展懈,直接動(dòng)手開(kāi)干。
網(wǎng)絡(luò)請(qǐng)求框架封裝大概流程如下:
(1)封裝OkHttp實(shí)例
(2)封裝OkHttp的攔截請(qǐng)求
(3)封裝Retrofit實(shí)例
(4)封裝Retrofit的注解通用類
(5)增加請(qǐng)求失敗重試功能
(6)封裝返回結(jié)果
(7)對(duì)上述封裝進(jìn)行組裝形成一個(gè)完整的框架
第一步:封裝OkHttp實(shí)例
public class OkHttpClientUtil {
// 是否開(kāi)啟攔截供璧,默認(rèn)情況下開(kāi)啟
private boolean mIsIntercept = true;
// 設(shè)置數(shù)據(jù)讀取超時(shí)時(shí)間
private long mReadTimeOut = 20000;
// 設(shè)置網(wǎng)絡(luò)連接超時(shí)時(shí)間
private long mConnectTimeOut = 20000;
// 設(shè)置寫(xiě)入服務(wù)器的超時(shí)時(shí)間
private long mWriteTimeOut = 20000;
private static volatile OkHttpClientUtil okHttpUtil;
static OkHttpClientUtil getOkHttpUtil() {
if (okHttpUtil == null) {
synchronized (OkHttpClientUtil.class) {
if (okHttpUtil == null) {
okHttpUtil = new OkHttpClientUtil();
}
}
}
return okHttpUtil;
}
/**
* 私有構(gòu)造函數(shù)存崖,保證全局唯一
*/
private OkHttpClientUtil(){
}
// 設(shè)置數(shù)據(jù)讀取超時(shí)時(shí)間
OkHttpClientUtil setTimeOutTime(long timeout) {
mReadTimeOut = timeout;
return this;
}
// 設(shè)置網(wǎng)絡(luò)連接超時(shí)時(shí)間
OkHttpClientUtil setConnectTime(long timeout) {
mConnectTimeOut = timeout;
return this;
}
// 設(shè)置寫(xiě)入服務(wù)器的超時(shí)時(shí)間
OkHttpClientUtil setWriteTime(long timeout) {
mWriteTimeOut = timeout;
return this;
}
// 設(shè)置攔截器
OkHttpClientUtil setIntercept(boolean isIntercept) {
this.mIsIntercept = isIntercept;
return this;
}
// 設(shè)置Build方法
public OkHttpClient build() {
OkHttpClient.Builder okHttpClient = new OkHttpClient.Builder();
okHttpClient.readTimeout(mReadTimeOut, TimeUnit.MILLISECONDS);
okHttpClient.connectTimeout(mConnectTimeOut, TimeUnit.MILLISECONDS);
okHttpClient.writeTimeout(mWriteTimeOut, TimeUnit.MILLISECONDS);
// 默認(rèn)開(kāi)啟請(qǐng)求的打印信息數(shù)據(jù),在每次發(fā)布版本的時(shí)候可以手動(dòng)關(guān)閉
if (mIsIntercept) {
okHttpClient.addInterceptor(new HttpRequestInterceptor());
}
return okHttpClient.build();
}
}
第二步:封裝OkHttp的攔截請(qǐng)求
/**
* author: zhoufan
* data: 2021/7/27 14:39
* content: 攔截每次發(fā)出的請(qǐng)求并打印出來(lái)
*/
public class HttpRequestInterceptor implements Interceptor {
@NotNull
@Override
public Response intercept(@NotNull Chain chain) throws IOException {
Request request = chain.request();
long startTime = System.currentTimeMillis();
okhttp3.Response response = chain.proceed(chain.request());
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
okhttp3.MediaType mediaType = response.body().contentType();
String content = response.body().string();
Log.d("request", "請(qǐng)求地址:| " + request.toString());
if (request.body() != null) {
printParams(request.body());
}
Log.d("request", "請(qǐng)求體返回:| Response:" + content);
Log.d("request", "----------請(qǐng)求耗時(shí):" + duration + "毫秒----------");
return response.newBuilder().body(okhttp3.ResponseBody.create(mediaType, content)).build();
}
private void printParams(RequestBody body) {
Buffer buffer = new Buffer();
try {
body.writeTo(buffer);
Charset charset = Charset.forName("UTF-8");
MediaType contentType = body.contentType();
if (contentType != null) {
charset = contentType.charset(UTF_8);
}
String params = buffer.readString(charset);
Log.d("request", "請(qǐng)求參數(shù): | " + params);
} catch (IOException e) {
e.printStackTrace();
}
}
}
第三步:封裝Retrofit實(shí)例
/**
* author: zhoufan
* data: 2021/7/27 14:45
* content: 對(duì)Retrofit進(jìn)行封裝
*/
public class RetrofitClientUtil {
// 網(wǎng)絡(luò)請(qǐng)求的baseUrl
private String mBaseUrl;
// 設(shè)置數(shù)據(jù)解析器
private Converter.Factory mBaseFactory;
// 新增數(shù)據(jù)解析器
private Converter.Factory mAddFactory;
// 設(shè)置網(wǎng)絡(luò)請(qǐng)求適配器
private CallAdapter.Factory mCallFactory;
// 設(shè)置OkHttpClient
private OkHttpClient mOkHttpClient;
private static volatile RetrofitClientUtil retrofitUtil;
public static RetrofitClientUtil getRetrofitUtil() {
if (retrofitUtil == null) {
synchronized (RetrofitClientUtil.class) {
if (retrofitUtil == null) {
retrofitUtil = new RetrofitClientUtil();
}
}
}
return retrofitUtil;
}
private RetrofitClientUtil() {
mBaseUrl = HttpRequestConstants.BASE_URL;
// 默認(rèn)基礎(chǔ)數(shù)據(jù)類型的解析
mBaseFactory = ScalarsConverterFactory.create();
// RxJava來(lái)處理Call返回值
mCallFactory = RxJava3CallAdapterFactory.create();
}
// 設(shè)置BaseUrl
public RetrofitClientUtil setBaseUrl(String baseUrl) {
this.mBaseUrl = baseUrl;
return this;
}
// 設(shè)置數(shù)據(jù)解析
public RetrofitClientUtil addConverterFactory(Converter.Factory factory) {
this.mAddFactory = factory;
return this;
}
// 設(shè)置網(wǎng)絡(luò)請(qǐng)求適配器
public RetrofitClientUtil addCallAdapterFactory(CallAdapter.Factory factory) {
this.mCallFactory = factory;
return this;
}
// 設(shè)置寫(xiě)入服務(wù)器的超時(shí)時(shí)間
public RetrofitClientUtil setOkHttpClient(OkHttpClient okHttpClient) {
this.mOkHttpClient = okHttpClient;
return this;
}
// 設(shè)置Build方法
public Retrofit build() {
Retrofit.Builder builder = new Retrofit.Builder();
builder.baseUrl(mBaseUrl);
builder.addConverterFactory(mBaseFactory);
if (mAddFactory!=null){
builder.addConverterFactory(mAddFactory);
}
builder.addCallAdapterFactory(mCallFactory);
builder.client(mOkHttpClient);
return builder.build();
}
}
第四步:封裝Retrofit的注解通用類
public interface ApiService {
// baseUrl = http://www.5mins-sun.com:8081/
// 例如:http://www.baidu.com不使用baseUrl
@GET
Observable<String> getPath(@Url String url);
// GET請(qǐng)求(無(wú)參) api = news
// http://www.5mins-sun.com:8081/news
@GET("{api}")
Observable<String> getData(@Path("api") String api);
// GET請(qǐng)求(帶參) api = news
// http://www.5mins-sun.com:8081/news?name=admin&pwd=123456
@GET("{api}")
Observable<String> getData(@Path("api") String api, @QueryMap TreeMap<String, Object> map);
// POST請(qǐng)求(無(wú)參)
@POST("{api}")
Observable<String> postData(@Path(value = "api", encoded = true) String api);
// POST請(qǐng)求,以RequestBody方式提交 api = news
// http://www.5mins-sun.com:8081/news
// RequestBody:
// {
// "albumID": 2,
// "sectionID": 16
// }
@POST("{api}")
Observable<String> postData(@Path(value = "api", encoded = true) String api, @Body RequestBody requestBody);
// Post請(qǐng)求,以表單方式提交
// http://www.5mins-sun.com:8081/news
// user=admin
// pwd=123456
@FormUrlEncoded
@POST("{api}")
Observable<ResponseBody> postData(@Path("api") String api, @FieldMap Map<String, String> maps);
// Post請(qǐng)求(帶數(shù)組)
@FormUrlEncoded
@POST("{api}")
Observable<ResponseBody> postData(@Path("api") String api, @FieldMap Map<String, String> maps, @Query("meta[]") String... linked);
// 上傳單個(gè)文件
@Multipart
@POST("{api}/")
Observable<String> upload(@Path("api") String api, @PartMap Map<String, RequestBody> maps, @Part MultipartBody.Part file);
// 上傳多個(gè)文件
@Multipart
@POST("{api}/")
Observable<String> uploadMultipart(@Path("api") String api, @PartMap Map<String, RequestBody> maps, @Part List<MultipartBody.Part> params);
}
第五步:增加請(qǐng)求失敗重試功能
/**
* author: zhoufan
* data: 2021/7/29 11:39
* content: 設(shè)置重新連接
*/
public class RetryFunction implements Function<Observable<Throwable>, ObservableSource<?>> {
// 可重試次數(shù)
private int maxConnectCount = 3;
// 當(dāng)前已重試次數(shù)
private int currentRetryCount = 0;
// 重試等待時(shí)間
private int waitRetryTime = 0;
@Override
public ObservableSource<?> apply(Observable<Throwable> throwableObservable) throws Throwable {
// 參數(shù)Observable<Throwable>中的泛型 = 上游操作符拋出的異常嗜傅,可通過(guò)該條件來(lái)判斷異常的類型
return throwableObservable.flatMap(new Function<Throwable, ObservableSource<?>>() {
@Override
public ObservableSource<?> apply(@NonNull Throwable throwable) throws Exception {
// 輸出異常信息
LogUtil.d(HttpRequestConstants.TAG, "發(fā)生異常 = " + throwable.toString());
/**
* 需求1:根據(jù)異常類型選擇是否重試
* 即金句,當(dāng)發(fā)生的異常 = 網(wǎng)絡(luò)異常 = IO異常 才選擇重試
*/
if (throwable instanceof IOException) {
Log.d(HttpRequestConstants.TAG, "屬于IO異常,需重試");
/**
* 需求2:限制重試次數(shù)
* 即吕嘀,當(dāng)已重試次數(shù) < 設(shè)置的重試次數(shù)违寞,才選擇重試
*/
if (currentRetryCount < maxConnectCount) {
// 記錄重試次數(shù)
currentRetryCount++;
Log.d(HttpRequestConstants.TAG, "重試次數(shù) = " + currentRetryCount);
/**
* 需求2:實(shí)現(xiàn)重試
* 通過(guò)返回的Observable發(fā)送的事件 = Next事件,從而使得retryWhen()重訂閱偶房,最終實(shí)現(xiàn)重試功能
*
* 需求3:延遲1段時(shí)間再重試
* 采用delay操作符 = 延遲一段時(shí)間發(fā)送趁曼,以實(shí)現(xiàn)重試間隔設(shè)置
*
* 需求4:遇到的異常越多,時(shí)間越長(zhǎng)
* 在delay操作符的等待時(shí)間內(nèi)設(shè)置 = 每重試1次棕洋,增多延遲重試時(shí)間1s
*/
// 設(shè)置等待時(shí)間
waitRetryTime = 1000 + currentRetryCount * 1000;
Log.d(HttpRequestConstants.TAG, "等待時(shí)間 =" + waitRetryTime);
return Observable.just(1).delay(waitRetryTime, TimeUnit.MILLISECONDS);
} else {
// 若重試次數(shù)已 > 設(shè)置重試次數(shù)挡闰,則不重試
// 通過(guò)發(fā)送error來(lái)停止重試(可在觀察者的onError()中獲取信息)
return Observable.error(new Throwable("重試次數(shù)已超過(guò)設(shè)置次數(shù) = " + currentRetryCount + ",即 不再重試"));
}
}
// 若發(fā)生的異常不屬于I/O異常掰盘,則不重試
// 通過(guò)返回的Observable發(fā)送的事件 = Error事件 實(shí)現(xiàn)(可在觀察者的onError()中獲取信息)
else {
return Observable.error(new Throwable("發(fā)生了非網(wǎng)絡(luò)異常(非I/O異常)"));
}
}
});
}
}
第六步:封裝返回結(jié)果
/**
* author: zhoufan
* data: 2021/7/27 16:59
* content: 處理接口返回的請(qǐng)求結(jié)果
*/
public class RxJavaObserver implements Observer<String> {
private HttpRequestCallback mCallBack;
private int mType;
private Context mContext;
RxJavaObserver(Context context, int type, HttpRequestCallback callBack) {
this.mCallBack = callBack;
this.mContext = context;
this.mType = type;
}
@Override
public void onSubscribe(@NonNull Disposable d) {
}
/**
* 接口請(qǐng)求成功
*/
@Override
public void onNext(@NonNull String s) {
try {
if (HttpTool.isJsonObject(s)) {
JSONObject jsonObject = new JSONObject(s);
if (jsonObject.has("resultStatus")) {
String response = jsonObject.getString("resultStatus");
if (response.toUpperCase().equals("SUCCESS")) {
if (jsonObject.has("returnData")) {
mCallBack.onRequestSuccess(jsonObject.getString("returnData"), mType);
} else {
if (jsonObject.has("errCode") && jsonObject.has("errMsg")) {
String code = jsonObject.getString("errCode");
String errMsg = jsonObject.getString("errMsg");
mCallBack.onRequestSuccess(errMsg, mType);
}
}
} else {
String errMsg = jsonObject.getString("errMsg");
String code = jsonObject.getString("errCode");
mCallBack.onRequestFail(errMsg, code, mType);
}
}
} else {
mCallBack.onRequestSuccess(s, mType);
}
} catch (JSONException e) {
e.printStackTrace();
}
}
/**
* 接口請(qǐng)求失敗
*/
@Override
public void onError(@NonNull Throwable e) {
// 1.檢查網(wǎng)絡(luò)設(shè)置
if (!HttpTool.hasNetwork(mContext)) {
MyToast.showCenterSortToast(mContext, mContext.getResources().getString(R.string.connect_error));
onComplete();
mCallBack.onRequestNetFail(mType);
return;
}
// 2.非網(wǎng)絡(luò)錯(cuò)誤摄悯,接口請(qǐng)求錯(cuò)誤
mCallBack.onRequestFail(e.getMessage(), "0000", mType);
}
/**
* 接口請(qǐng)求完成
*/
@Override
public void onComplete() {
}
}
第七步:對(duì)上述封裝進(jìn)行組裝形成一個(gè)完整的框架
/**
* author: zhoufan
* data: 2021/7/27 16:41
* content: 對(duì)應(yīng)用層暴露的調(diào)用類
*/
public class HttpRetrofitRequest extends HttpRetrofit implements IHttpRequest {
private ApiService apiService;
// 是否添加通用參數(shù)
private boolean mIsAddCommonParams;
private volatile static HttpRetrofitRequest INSTANCES;
public static HttpRetrofitRequest getInstances() {
if (INSTANCES == null) {
synchronized (HttpRetrofitRequest.class) {
if (INSTANCES == null) {
INSTANCES = new HttpRetrofitRequest();
}
}
}
return INSTANCES;
}
private HttpRetrofitRequest() {
apiService = getApiManager();
}
/**
* 設(shè)置通用參數(shù)
*/
public void isAddCommonParams(boolean isAddCommonParams) {
this.mIsAddCommonParams = isAddCommonParams;
}
// Get請(qǐng)求(使用Path形式)
@Override
public void mHttpGetPath(Context context, String url, int type, HttpRequestCallback callBack) {
Observable<String> observable = apiService.getPath(url);
observable.retryWhen(new RetryFunction()).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new RxJavaObserver(context, type, callBack));
}
// GET請(qǐng)求(無(wú)參)
@Override
public void mHttpGet(Context context, String api, int type, HttpRequestCallback callBack) {
Observable<String> observable = apiService.getData(api);
observable.retryWhen(new RetryFunction()).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new RxJavaObserver(context, type, callBack));
}
// Get請(qǐng)求(帶參)
@Override
public void mHttpGet(Context context, String api, TreeMap map, int type, HttpRequestCallback callBack) {
if (mIsAddCommonParams) {
HttpTool.getTreeCrc(map);
}
Observable<String> observable = apiService.getData(api, map);
observable.retryWhen(new RetryFunction()).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new RxJavaObserver(context, type, callBack));
}
// Post請(qǐng)求(無(wú)參)
@Override
public void mHttpPost(Context context, String api, int type, HttpRequestCallback callBack) {
Observable<String> observable = apiService.postData(api);
observable.retryWhen(new RetryFunction()).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new RxJavaObserver(context, type, callBack));
}
// Post請(qǐng)求(帶參)
// 以RequestBody方式提交
@Override
public void mHttpPost(Context context, String api, TreeMap map, int type, HttpRequestCallback callBack) {
if (mIsAddCommonParams) {
HttpTool.getTreeCrc(map);
}
Gson gson = new Gson();
String param = gson.toJson(map);
RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), param);
Observable<String> observable = apiService.postData(api, requestBody);
observable.retryWhen(new RetryFunction()).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new RxJavaObserver(context, type, callBack));
}
// Post請(qǐng)求(包含數(shù)組)
@Override
public void mHttpPost(Context context, String api, TreeMap map, String[] data, int type, HttpRequestCallback callBack) {
if (mIsAddCommonParams) {
HttpTool.getTreeCrc(map);
}
Observable<String> observable = apiService.postData(api, map, data);
observable.retryWhen(new RetryFunction()).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new RxJavaObserver(context, type, callBack));
}
// 單文件上傳
@Override
public void mHttpFile(Context context, String api, File file, String fileKey, TreeMap map, int type, HttpRequestCallback callBack) {
// 生成單個(gè)文件
RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), file);
MultipartBody.Part body = MultipartBody.Part.createFormData(fileKey, file.getName(), requestFile);
if (mIsAddCommonParams) {
HttpTool.getTreeCrc(map);
}
Map<String, RequestBody> mapValue = new HashMap<>();
for (Object key : map.keySet()) {
mapValue.put(key.toString(), HttpTool.convertToRequestBody(map.get(key).toString()));
}
Observable<String> observable = apiService.upload(api, mapValue, body);
observable.retryWhen(new RetryFunction()).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new RxJavaObserver(context, type, callBack));
}
// 多文件上傳
@Override
public void mHttpMultiFile(Context context, String api, List<File> list, List<String> listFileName, TreeMap map, int type, HttpRequestCallback callBack) {
//生成多個(gè)文件并添加到集合中
List<MultipartBody.Part> params = new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), list.get(i));
MultipartBody.Part body = MultipartBody.Part.createFormData(listFileName.get(i), list.get(i).getName(), requestFile);
params.add(body);
}
if (mIsAddCommonParams) {
HttpTool.getTreeCrc(map);
}
Map<String, RequestBody> mapValue = new HashMap<>();
for (Object key : map.keySet()) {
mapValue.put(key.toString(), HttpTool.convertToRequestBody(Objects.requireNonNull(map.get(key)).toString()));
}
// 發(fā)送異步請(qǐng)求
Observable<String> observable = apiService.uploadMultipart(api, mapValue, params);
observable.retryWhen(new RetryFunction()).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new RxJavaObserver(context, type, callBack));
}
}
當(dāng)然,在基本流程之外還有一些輔助類:
請(qǐng)求接口回調(diào)愧捕,用于應(yīng)用層對(duì)返回結(jié)果做出對(duì)應(yīng)的展示
/**
* author: zhoufan
* data: 2021/7/27 16:30
* content: 請(qǐng)求接口回調(diào)
*/
public interface HttpRequestCallback {
void onRequestNetFail(int type);
void onRequestSuccess(String result, int type);
void onRequestFail(String value, String failCode, int type);
}
網(wǎng)絡(luò)框架封裝的常量類奢驯,比如baseUrl等等的位置
/**
* author: zhoufan
* data: 2021/7/27 14:56
* content: 網(wǎng)絡(luò)請(qǐng)求的常量類
*/
public class HttpRequestConstants {
public static final String BASE_URL = "http://www.5mins-sun.com:8081/";
public static final String SECRET = "5af012069a2fc4.49876009";
public static final String APP_KEY = "5af012069a2fa";
public static final String SIGN_METHOD_MD5 = "MD5";
public static final String SIGN_METHOD_HMAC = "HMAC";
public static final String CHARSET_UTF8 = "UTF-8";
public static final String TAG = "retrofit";
}
使用Retrofit作為我們的底層網(wǎng)絡(luò)請(qǐng)求
/**
* author: zhoufan
* data: 2021/7/27 15:09
* content: 對(duì)外暴露方法供外面調(diào)用
*/
public class HttpRetrofit {
// 設(shè)置數(shù)據(jù)讀取超時(shí)時(shí)間
public HttpRetrofit setTimeOutTime(long timeout) {
OkHttpClientUtil.getOkHttpUtil().setTimeOutTime(timeout);
return this;
}
// 設(shè)置網(wǎng)絡(luò)連接超時(shí)時(shí)間
public HttpRetrofit setConnectTime(long timeout) {
OkHttpClientUtil.getOkHttpUtil().setConnectTime(timeout);
return this;
}
// 設(shè)置寫(xiě)入服務(wù)器的超時(shí)時(shí)間
public HttpRetrofit setWriteTime(long timeout) {
OkHttpClientUtil.getOkHttpUtil().setWriteTime(timeout);
return this;
}
// 設(shè)置攔截器
public HttpRetrofit setIntercept(boolean isIntercept) {
OkHttpClientUtil.getOkHttpUtil().setIntercept(isIntercept);
return this;
}
// 設(shè)置BaseUrl
public HttpRetrofit setBaseUrl(String baseUrl) {
RetrofitClientUtil.getRetrofitUtil().setBaseUrl(baseUrl);
return this;
}
// 設(shè)置數(shù)據(jù)解析
public HttpRetrofit addConverterFactory(Converter.Factory factory) {
RetrofitClientUtil.getRetrofitUtil().addConverterFactory(factory);
return this;
}
/**
* 生成請(qǐng)求接口的實(shí)例
* @return ApiService
*/
public ApiService getApiManager(){
return RetrofitClientUtil.getRetrofitUtil().setOkHttpClient(OkHttpClientUtil.getOkHttpUtil().build()).build().create(ApiService.class);
}
}
然后在我們的Application里面進(jìn)行初始化設(shè)置
public class IApplication extends Application {
private static Context mContext;
public static Context getContext() {
return mContext;
}
@Override
public void onCreate() {
super.onCreate();
mContext = getApplicationContext();
HttpUtils.getInstance().httpRequest(HttpRetrofitRequest.getInstances());
}
}
網(wǎng)絡(luò)請(qǐng)求框架的工具類
/**
* Created by zhoufan on 2018/1/3.
* 工具類 (添加通用參數(shù)、加密等等)
*/
public class HttpTool {
public static RequestBody convertToRequestBody(String param) {
RequestBody requestBody = RequestBody.create(MediaType.parse("text/plain"), param);
return requestBody;
}
public static String signTopRequest(Map<String, Object> params, String secret, String signMethod) throws IOException {
// 第一步:檢查參數(shù)是否已經(jīng)排序
String[] keys = params.keySet().toArray(new String[0]);
Arrays.sort(keys);
// 第二步:把所有參數(shù)名和參數(shù)值串在一起
StringBuilder query = new StringBuilder();
for (String key : keys) {
Object value = params.get(key);
if (value instanceof String) {
if (!TextUtils.isEmpty(key) && !TextUtils.isEmpty((String) value)) {
query.append(key).append(value);
}
} else if (value instanceof File) {
String strVal = getFileContent((File) value);
query.append(key).append(strVal);
params.remove(key);
}
}
// 第三步:使用MD5/HMAC加密
byte[] bytes;
if (HttpRequestConstants.SIGN_METHOD_HMAC.equals(signMethod)) {
bytes = encryptHMAC(query.toString(), secret);
} else {
bytes = encryptMD5(query.toString() + secret);
}
// 第四步:把二進(jìn)制轉(zhuǎn)化為大寫(xiě)的十六進(jìn)制
return byte2hex(bytes);
}
public static byte[] encryptHMAC(String data, String secret) throws IOException {
byte[] bytes = null;
try {
SecretKey secretKey = new SecretKeySpec(secret.getBytes(HttpRequestConstants.CHARSET_UTF8), "HmacMD5");
Mac mac = Mac.getInstance(secretKey.getAlgorithm());
mac.init(secretKey);
bytes = mac.doFinal(data.getBytes(HttpRequestConstants.CHARSET_UTF8));
} catch (GeneralSecurityException gse) {
throw new IOException(gse.toString());
}
return bytes;
}
public static byte[] encryptMD5(String data) throws IOException {
byte[] md5Byte = null;
try {
MessageDigest md = MessageDigest.getInstance("MD5"); // 創(chuàng)建一個(gè)md5算法對(duì)象
byte[] messageByte = data.getBytes("UTF-8");
md5Byte = md.digest(messageByte); // 獲得MD5字節(jié)數(shù)組,16*8=128位
} catch (Exception e) {
e.printStackTrace();
}
return md5Byte;
}
public static String byte2hex(byte[] bytes) {
StringBuilder sign = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(bytes[i] & 0xFF);
if (hex.length() == 1) {
sign.append("0");
}
sign.append(hex.toLowerCase());
}
return sign.toString();
}
public static TreeMap getTreeCrc(TreeMap maps) {
try {
maps.put("app_key", HttpRequestConstants.APP_KEY);
maps.put("sign_method", "md5");
maps.put("format", "json");
maps.put("timestamp", timeStamp2Date());
maps.put("sign", signTopRequest(maps, HttpRequestConstants.SECRET, HttpRequestConstants.SIGN_METHOD_MD5));
} catch (Exception e) {
e.printStackTrace();
}
return maps;
}
// 時(shí)間戳轉(zhuǎn)換
private static String timeStamp2Date() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
sdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));//設(shè)置TimeZone為上海時(shí)間
Date now = new Date();//獲取本地時(shí)間
try {
now = sdf.parse(sdf.format(now));//將本地時(shí)間轉(zhuǎn)換為轉(zhuǎn)換時(shí)間為東八區(qū)
} catch (ParseException e) {
e.printStackTrace();
}
return sdf.format(now);
}
// 檢測(cè)是否有網(wǎng)絡(luò)
@SuppressLint("MissingPermission")
public static boolean hasNetwork(Context mContext) {
// 得到連接管理器對(duì)象
ConnectivityManager connectivityManager = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
if (activeNetworkInfo != null && activeNetworkInfo.isConnected()) {
if (activeNetworkInfo.getType() == (ConnectivityManager.TYPE_WIFI)) {
return true;
} else if (activeNetworkInfo.getType() == (ConnectivityManager.TYPE_MOBILE)) {
return true;
}
} else {
return false;
}
return false;
}
// 將文件進(jìn)行SHA1加密
public static String getFileContent(File file) {
try {
StringBuffer sb = new StringBuffer();
MessageDigest digest = MessageDigest.getInstance("SHA-1");
FileInputStream fin = new FileInputStream(file);
int len = -1;
byte[] buffer = new byte[1024];//設(shè)置輸入流的緩存大小 字節(jié)
//將整個(gè)文件全部讀入到加密器中
while ((len = fin.read(buffer)) != -1) {
digest.update(buffer, 0, len);
}
//對(duì)讀入的數(shù)據(jù)進(jìn)行加密
byte[] bytes = digest.digest();
for (byte b : bytes) {
// 數(shù)byte 類型轉(zhuǎn)換為無(wú)符號(hào)的整數(shù)
int n = b & 0XFF;
// 將整數(shù)轉(zhuǎn)換為16進(jìn)制
String s = Integer.toHexString(n);
// 如果16進(jìn)制字符串是一位次绘,那么前面補(bǔ)0
if (s.length() == 1) {
sb.append("0" + s);
} else {
sb.append(s);
}
}
return sb.toString();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 2 * 判斷字符串是否可以轉(zhuǎn)化為json對(duì)象
* 3 * @param content
* 4 * @return
* 5
*/
public static boolean isJsonObject(String content) {
if (content == null || TextUtils.isEmpty(content))
return false;
try {
JSONObject jsonObject = new JSONObject(content);
return true;
} catch (Exception e) {
return false;
}
}
}
最終對(duì)外暴露的類
public class HttpUtils {
private static IHttpRequest mHttpRequest;
/**
* 生成唯一實(shí)例
*/
private static volatile HttpUtils mInstance;
public static HttpUtils getInstance(){
if (mInstance==null){
synchronized (HttpUtils.class){
if (mInstance==null){
mInstance = new HttpUtils();
}
}
}
return mInstance;
}
/**
* 初始化決定使用哪一套網(wǎng)絡(luò)請(qǐng)求框架
* @param httpRequest 默認(rèn)為OkHttp + retrofit + rxJava
*/
public static void setInitHttpRequest(IHttpRequest httpRequest){
mHttpRequest = httpRequest;
}
/**
* 更換使用的框架
* @param httpRequest 重新設(shè)置的網(wǎng)絡(luò)框架
* @return
*/
public HttpUtils httpRequest(IHttpRequest httpRequest){
mHttpRequest = httpRequest;
return this;
}
/**
* Get請(qǐng)求(使用Path形式)
* @param context 上下文
* @param url 請(qǐng)求的url地址瘪阁,不使用baseUrl
* @param type 請(qǐng)求的標(biāo)識(shí)
* @param callBack 結(jié)果返回接口
*/
public void executePathGet(Context context, String url, int type, HttpRequestCallback callBack){
mHttpRequest.mHttpGetPath(context,url,type,callBack);
}
/**
* GET請(qǐng)求(無(wú)參)
* @param context 上下文
* @param api 請(qǐng)求的api
* @param type 請(qǐng)求的標(biāo)識(shí)
* @param callBack 結(jié)果返回接口
*/
public void executeGet(Context context, String api, int type, HttpRequestCallback callBack){
mHttpRequest.mHttpGet(context,api,type,callBack);
}
/**
* Get請(qǐng)求(帶參)
* @param context 上下文
* @param api 請(qǐng)求的api
* @param map 請(qǐng)求的參數(shù)
* @param type 請(qǐng)求的標(biāo)識(shí)
* @param callBack 結(jié)果返回接口
*/
public void executeGet(Context context, String api, TreeMap map, int type, HttpRequestCallback callBack){
mHttpRequest.mHttpGet(context,api,map,type,callBack);
}
/**
* Post請(qǐng)求(無(wú)參)
* @param context 上下文
* @param api 請(qǐng)求的api
* @param type 請(qǐng)求的標(biāo)識(shí)
* @param callBack 結(jié)果返回接口
*/
public void executePost(Context context, String api, int type, HttpRequestCallback callBack){
mHttpRequest.mHttpPost(context,api,type,callBack);
}
/**
* Post請(qǐng)求(帶參)
* 以RequestBody方式提交
* @param context 上下文
* @param api 請(qǐng)求的api
* @param map 請(qǐng)求的參數(shù)
* @param type 請(qǐng)求的標(biāo)識(shí)
* @param callBack 結(jié)果返回接口
*/
public void executePost(Context context, String api, TreeMap map, int type, HttpRequestCallback callBack){
mHttpRequest.mHttpPost(context,api,map,type,callBack);
}
/**
* Post請(qǐng)求(包含數(shù)組)
* @param context 上下文
* @param api 請(qǐng)求的api
* @param treeMap 請(qǐng)求的參數(shù)
* @param data 請(qǐng)求的數(shù)組
* @param type 請(qǐng)求的標(biāo)識(shí)
* @param callBack 結(jié)果返回接口
*/
public void executePost(Context context, String api, TreeMap treeMap, String[] data, int type, HttpRequestCallback callBack){
mHttpRequest.mHttpPost(context,api,treeMap,data,type,callBack);
}
/**
* 單文件上傳
* @param context 上下文
* @param api 請(qǐng)求的api
* @param file 上傳的文件
* @param fileKey 上傳的文件對(duì)應(yīng)的key
* @param map 請(qǐng)求的參數(shù)
* @param type 請(qǐng)求的標(biāo)識(shí)
* @param callBack 結(jié)果返回接口
*/
public void executeFile(Context context, String api, File file, String fileKey, TreeMap map, int type, HttpRequestCallback callBack){
mHttpRequest.mHttpFile(context, api, file, fileKey, map, type, callBack);
}
/**
* 多文件上傳
* @param context 上下文
* @param api 請(qǐng)求的api
* @param list 上傳的文件集合
* @param fileList 上傳的文件集合對(duì)應(yīng)的key
* @param map 請(qǐng)求的參數(shù)
* @param type 請(qǐng)求的標(biāo)識(shí)
* @param callBack 結(jié)果返回接口
*/
public void executeMultiFile(Context context, String api, List<File> list, List<String> fileList, TreeMap map, int type, HttpRequestCallback callBack){
mHttpRequest.mHttpMultiFile(context, api, list, fileList, map, type, callBack);
}
}
請(qǐng)求方式的通用接口
/**
* author: zhoufan
* data: 2021/7/27 16:29
* content: 請(qǐng)求接口的方式
*/
public interface IHttpRequest {
// Get請(qǐng)求(使用Path形式)
void mHttpGetPath(Context context, String url, int type, HttpRequestCallback mCallBack);
// GET請(qǐng)求(無(wú)參)
void mHttpGet(Context context, String api, int type, HttpRequestCallback mCallBack);
// Get請(qǐng)求(帶參)
void mHttpGet(Context context, String api, TreeMap map, int type, HttpRequestCallback mCallBack);
// Post請(qǐng)求(無(wú)參)
void mHttpPost(Context context, String api, int type, HttpRequestCallback mCallBack);
// Post請(qǐng)求(帶參)
// 以RequestBody方式提交
void mHttpPost(Context context, String api, TreeMap map, int type, HttpRequestCallback mCallBack);
// Post請(qǐng)求(包含數(shù)組)
void mHttpPost(Context context, String api, TreeMap treeMap, String[] data, int type, HttpRequestCallback mCallBack);
// 單文件上傳
void mHttpFile(Context context, String api, File file, String fileKey, TreeMap map, int type, HttpRequestCallback mCallBack);
// 多文件上傳
void mHttpMultiFile(Context context, String api, List<File> list, List<String> fileList, TreeMap map, int type, HttpRequestCallback mCallBack);
}
完整的目錄結(jié)構(gòu)為:
-- ApiService
-- HttpRequestCallback
-- HttpRequestConstants
-- HttpRequestInterceptor
-- HttpRetrofit
-- HttpRetrofitRequest
-- HttpTool
-- HttpUtils
-- IHttpRequest
-- OkHttpClientUtil
-- RetrofitClientUtil
-- RetryFunction
-- RxJavaObserver
當(dāng)然撒遣,別忘記在清單文件里面設(shè)置網(wǎng)絡(luò)權(quán)限哦
<uses-permission android:name="android.permission.INTERNET" />
最后需要注意的是,千萬(wàn)別導(dǎo)入錯(cuò)誤的包管跺,我引入的依賴為
// okHttp網(wǎng)絡(luò)請(qǐng)求框架
// define a BOM and its version
api(platform("com.squareup.okhttp3:okhttp-bom:4.9.1"))
// define any required OkHttp artifacts without version
api("com.squareup.okhttp3:okhttp")
api("com.squareup.okhttp3:logging-interceptor")
//retrofit網(wǎng)絡(luò)請(qǐng)求框架
api 'com.squareup.retrofit2:retrofit:2.9.0'
//retrofit添加Json解析返回?cái)?shù)據(jù)
api 'com.squareup.retrofit2:converter-scalars:2.9.0'
api 'com.squareup.retrofit2:converter-gson:2.9.0'
//retrofit添加RxJava支持
api 'com.squareup.retrofit2:adapter-rxjava3:2.9.0'
api "io.reactivex.rxjava3:rxjava:3.0.13"
api 'io.reactivex.rxjava3:rxandroid:3.0.0'
最后看下效果
/**
* 網(wǎng)絡(luò)測(cè)試類
*/
class NetWorkActivity : AppCompatActivity(), HttpRequestCallback {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_net_work)
}
// Get請(qǐng)求(使用Path形式)
fun requestOne(view: View) {
HttpUtils.getInstance().executePathGet(this, "http://www.baidu.com/", 0, this)
}
// Post請(qǐng)求(帶參以RequestBody方式提交)
fun requestFive(view: View) {
val treeMap = TreeMap<String,Any>()
HttpUtils.getInstance().executePost(this, "/app/get_version_info", treeMap,0, this)
}
// 網(wǎng)絡(luò)連接失敗
override fun onRequestNetFail(type: Int) {
}
// 接口請(qǐng)求成功
override fun onRequestSuccess(result: String?, type: Int) {
textView.text = result
}
// 接口請(qǐng)求失敗
override fun onRequestFail(value: String?, failCode: String?, type: Int) {
}
}
在這里我只測(cè)試了Get請(qǐng)求(使用Path形式)和Post請(qǐng)求(帶參以RequestBody方式提交)义黎,其他的大家可以根據(jù)自己的需求自行測(cè)試。