code小生,一個(gè)專(zhuān)注 Android 領(lǐng)域的技術(shù)平臺
公眾號回復 Android 加入我的安卓技術(shù)群
作者:xchengDroid
鏈接:https://github.com/xchengDroid/retrofit-helper
聲明:本文來(lái)自
xchengDroid投稿,轉發(fā)等請聯(lián)系原作者授權
Retrofit是很多android開(kāi)發(fā)者都在使用的Http請求庫!他負責網(wǎng)絡(luò )請求接口的封裝,底層實(shí)現是OkHttp,它的一個(gè)特點(diǎn)是包含了特別多注解,方便簡(jiǎn)化你的代碼量,CallAdapter.Factory 和Converter.Factory可以很靈活的擴展你的請求。我們在使用的時(shí)候還是需要封裝一層便于我們使用,retrofit-helper的作用就是再次簡(jiǎn)化你的請求。
| 描述 | 相關(guān)類(lèi)和方法 |
|---|---|
| 回調函數中直接處理請求結果,無(wú)需再次判斷是否成功 | Callback2.onSuccess(Call2<T> call2, T response) |
| 請求開(kāi)始和結束監聽(tīng) | Callback2.onStart(Call2<T> call2) 和Callback2.onCompleted(Call2<T> call2, @Nullable Throwable t, boolean canceled); |
| 全局維護多個(gè)Retrofit實(shí)例 | RetrofitFactory.DEFAULT 和 RetrofitFactory.OTHERS |
| 統一處理解析結果 | Callback2.parseResponse(Call2<T> call2, Response<T> response)和 Callback2.parseThrowable(Call2<T> call2, Throwable t) |
| 全局取消某個(gè)請求 | CallManager.getInstance().cancel( yourTag ) |
| 攔截器監聽(tīng)下載和上傳進(jìn)度 | ProgressInterceptor 、ProgressListener |
| 單獨指定某個(gè)請求的日志級別 | HttpLoggingInterceptor |
2.1 RetrofitFactory全局管理retrofit實(shí)例
DEFAULT 靜態(tài)變量管理默認常用的的retrofit對象,OTHERS 管理其他多個(gè)不同配置的retrofit
/**
* 創(chuàng )建時(shí)間:2018/4/3
* 編寫(xiě)人: chengxin
* 功能描述:管理全局的Retrofit實(shí)例
*/
public final class RetrofitFactory {
/**
* 緩存不同配置的retrofit集合,如不同的url ,converter等
*/
public static final Map<String, Retrofit> OTHERS = new ConcurrentHashMap<>(2);
/**
* 全局的Retrofit對象
*/
public static volatile Retrofit DEFAULT;private RetrofitFactory() {
}
public static <T> T create(Class<T> service) {
//確保多線(xiàn)程的情況下retrofit不為空或者被修改了
Retrofit retrofit = DEFAULT;
Utils.checkState(retrofit != null, "DEFAULT == null");
return retrofit.create(service);
}
/**
* @param name 獲取 OTHERS 中指定名字的retrofit
*/
public static <T> T create(String name, Class<T> service) {
Utils.checkNotNull(name, "name == null");
Retrofit retrofit = OTHERS.get(name);
Utils.checkState(retrofit != null,
String.format("retrofit named with '%s' was not found , have you put it in OTHERS ?", name));
return retrofit.create(service);
}
}
2.2 Call2接口繼承retrofit.Call 重載 enqueue(Callback<T> callback)方法
enqueue(@Nullable Object tag, Callback2<T> callback2) 方法傳入請求的tag標記此請求,tag標簽就是取消請求所需要的
/**
* 創(chuàng )建時(shí)間:2018/4/8
* 編寫(xiě)人: chengxin
* 功能描述:添加重載方法{@link Call2#enqueue(Object, Callback2)}方法
*/
public interface Call2<T> extends retrofit2.Call<T> {
/**
* @param tag 請求的tag,用于取消請求使用
* @param callback2 請求的回調
*/
void enqueue(@Nullable Object tag, Callback2<T> callback2);
Call2<T> clone();
}
2.3 Callback2 統一處理回調
請求開(kāi)始、成功處理、失敗處理、成功回調、失敗回調、請求結束在此統一處理,各方法可以根據業(yè)務(wù)的不同自行重寫(xiě),例如:可以重寫(xiě)parseResponse方法根據不通的http code做不同的提示描述 或者
重寫(xiě)parseThrowable方法處理各種Throwable
@UiThread
public abstract class Callback2<T> {public abstract void onStart(Call2<T> call2);
public Result<T> parseResponse(Call2<T> call2, Response<T> response) {
T body = response.body();
if (response.isSuccessful()) {
if (body != null) {
return Result.success(body);
} else {
return Result.error(new HttpError("暫無(wú)數據", response));
}
}
final String msg;
switch (response.code()) {
case 400:
msg = "參數錯誤";
break;
case 401:
msg = "身份未授權";
break;
case 403:
msg = "禁止訪(fǎng)問(wèn)";
break;
case 404:
msg = "地址未找到";
break;
default:
msg = "服務(wù)異常";
}
return Result.error(new HttpError(msg, response));
}
/**
* 統一解析Throwable對象轉換為HttpError對象。如果為HttpError,
* 則為{@link retrofit2.Converter#convert(Object)}內拋出的異常
*
* @param call2 call
* @param t Throwable
* @return HttpError result
*/
public HttpError parseThrowable(Call2<T> call2, Throwable t) {
if (t instanceof HttpError) {
//用于convert函數直接拋出異常接收
return (HttpError) t;
} else if (t instanceof UnknownHostException) {
return new HttpError("網(wǎng)絡(luò )異常", t);
} else if (t instanceof ConnectException) {
return new HttpError("網(wǎng)絡(luò )異常", t);
} else if (t instanceof SocketException) {
return new HttpError("服務(wù)異常", t);
} else if (t instanceof SocketTimeoutException) {
return new HttpError("響應超時(shí)", t);
} else {
return new HttpError("請求失敗", t);
}
}
public abstract void onError(Call2<T> call2, HttpError error);
public abstract void onSuccess(Call2<T> call2, T response);
/**
* @param t 請求失敗的錯誤信息
* @param canceled 請求是否被取消了
*/
public abstract void onCompleted(Call2<T> call2, @Nullable Throwable t, boolean canceled);
}
2.4 HttpError 統一處理異常錯誤
HttpError類(lèi)中有兩個(gè)成員屬性msg 被body,msg是保存錯誤的描述信息等,body可以保存異常的具體信息或者原始的json等,onError(Call2<T> call2, HttpError error)回調方法可以根據body的具體信息做二次處理。
/**
* 通用的錯誤信息,一般請求是失敗只需要彈出一些錯誤信息即可,like{@link retrofit2.HttpException}
* Created by chengxin on 2017/6/22.
*/
public final class HttpError extends RuntimeException {
private static final long serialVersionUID = -134024482758434333L;
/**
* 展示在前端的錯誤描述信息
*/
public String msg;/**
* <p>
* 請求失敗保存失敗信息,for example:
* <li>BusiModel: {code:xxx,msg:xxx} 業(yè)務(wù)錯誤信息</li>
* <li>original json: 原始的json</li>
* <li>{@link retrofit2.Response}:錯誤響應體->Response<?></li>
* <li>Throwable: 拋出的異常信息</li>
* </p>
*/
public final transient Object body;
public HttpError(String msg) {
this(msg, null);
}
public HttpError(String msg, @Nullable Object body) {
super(msg);
if (body instanceof Throwable) {
initCause((Throwable) body);
}
//FastPrintWriter#print(String str)
this.msg = msg != null ? msg : "null";
this.body = body;
}
/**
* 保證和msg一致
*/
public String getMessage() {
return msg;
}
public String toString() {
return "HttpError {msg="
+ msg
+ ", body="
+ body
+ '}';
}
}
2.5 ExecutorCallAdapterFactory返回Call2請求適配器
處理請求接口方法返回為Call2的請求適配器工廠(chǎng)類(lèi)
public final class ExecutorCallAdapterFactory extends CallAdapter.Factory {public static final CallAdapter.Factory INSTANCE = new ExecutorCallAdapterFactory();
private ExecutorCallAdapterFactory() {
}
/**
* Extract the raw class type from {@code type}. For example, the type representing
* {@code List<? extends Runnable>} returns {@code List.class}.
*/
public static Class<?> getRawType(Type type) {
return CallAdapter.Factory.getRawType(type);
}
public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
if (getRawType(returnType) != Call2.class) {
return null;
}
if (!(returnType instanceof ParameterizedType)) {
throw new IllegalArgumentException(
"Call return type must be parameterized as Call2<Foo> or Call2<? extends Foo>");
}
final Type responseType = getParameterUpperBound(0, (ParameterizedType) returnType);
final Executor callbackExecutor = retrofit.callbackExecutor();
if (callbackExecutor == null) throw new AssertionError();
return new CallAdapter<Object, Call<?>>() {
public Type responseType() {
return responseType;
}
public Call<Object> adapt(Call<Object> call) {
return new ExecutorCallbackCall2<>(callbackExecutor, call);
}
};
}
}
2.6 ExecutorCallbackCall2 繼承Call2代理OkHttpCall處理UI回調
裝飾者模式代理OkHttpCall的所有方法,線(xiàn)程調度處理 Callback2 的回調方法在主線(xiàn)程執行
final class ExecutorCallbackCall2<T> implements Call2<T> {
private final Executor callbackExecutor;
private final Call<T> delegate;/**
* The executor used for {@link Callback} methods on a {@link Call}. This may be {@code null},
* in which case callbacks should be made synchronously on the background thread.
*/
ExecutorCallbackCall2(Executor callbackExecutor, Call<T> delegate) {
this.callbackExecutor = callbackExecutor;
this.delegate = delegate;
}
public void enqueue(final Callback<T> callback) {
throw new UnsupportedOperationException("please call enqueue(Object tag, Callback2<T> callback2)");
}
public void enqueue(@Nullable Object tag, final Callback2<T> callback2) {
Utils.checkNotNull(callback2, "callback2==null");
CallManager.getInstance().add(this, tag != null ? tag : "NO_TAG");
callbackExecutor.execute(new Runnable() {
public void run() {
if (!isCanceled()) {
callback2.onStart(ExecutorCallbackCall2.this);
}
}
});
delegate.enqueue(new Callback<T>() {
public void onResponse(Call<T> call, final Response<T> response) {
callbackExecutor.execute(new Runnable() {
public void run() {
callResult(callback2, response, null);
}
});
}
public void onFailure(Call<T> call, final Throwable t) {
callbackExecutor.execute(new Runnable() {
public void run() {
callResult(callback2, null, t);
}
});
}
});
}
private void callResult(Callback2<T> callback2, @Nullable Response<T> response, @Nullable Throwable failureThrowable) {
try {
if (!isCanceled()) {
//1、獲取解析結果
Result<T> result;
if (response != null) {
result = callback2.parseResponse(this, response);
Utils.checkNotNull(result, "result==null");
} else {
Utils.checkNotNull(failureThrowable, "failureThrowable==null");
HttpError error = callback2.parseThrowable(this, failureThrowable);
result = Result.error(error);
}
//2、回調成功失敗
if (result.isSuccess()) {
callback2.onSuccess(this, result.body());
} else {
callback2.onError(this, result.error());
}
}
callback2.onCompleted(this, failureThrowable, isCanceled());
} finally {
CallManager.getInstance().remove(this);
}
}
public boolean isExecuted() {
return delegate.isExecuted();
}
public Response<T> execute() throws IOException {
return delegate.execute();
}
public void cancel() {
delegate.cancel();
}
public boolean isCanceled() {
return delegate.isCanceled();
}
("CloneDoesntCallSuperClone") // Performing deep clone.
public Call2<T> clone() {
return new ExecutorCallbackCall2<>(callbackExecutor, delegate.clone());
}
public Request request() {
return delegate.request();
}
}
2.7 CallManager統一管理請求,取消請求
全局保存所有的請求,添加 、刪除請求,取消某個(gè)某些匹配tag的請求??梢栽贏(yíng)ctivity 或Fragment的銷(xiāo)毀方法中調用CallManager.getInstance().cancel( yourTag )
/**
* 創(chuàng )建時(shí)間:2018/5/31
* 編寫(xiě)人: chengxin
* 功能描述:全局管理Call請求管理,just like {@link okhttp3.Dispatcher}
*/
public final class CallManager implements ActionManager<Call<?>> {
@GuardedBy("this")
private final List<CallTag> callTags = new ArrayList<>(4);
private volatile static CallManager instance;private CallManager() {
}
public static CallManager getInstance() {
if (instance == null) {
synchronized (CallManager.class) {
if (instance == null) {
instance = new CallManager();
}
}
}
return instance;
}
public synchronized void add(Call<?> call, Object tag) {
Utils.checkState(!contains(call), "Call<?> " + call + " is already added.");
callTags.add(new CallTag(call, tag));
}
/**
* 當call結束時(shí)移除
*
* @param call Retrofit Call
*/
public synchronized void remove(Call<?> call) {
if (callTags.isEmpty())
return;
for (int index = 0; index < callTags.size(); index++) {
if (call == callTags.get(index).call) {
//like okhttp3.Headers#removeAll(String name)
//remove(int index) 方法優(yōu)于 remove(Object o),無(wú)需再次遍歷
callTags.remove(index);
break;
}
}
}
/**
* 取消并移除對應tag的call,確保Call被取消后不再被引用,
* 結合{@link #remove(Call)}方法雙保險
*
* @param tag call對應的tag
*/
public synchronized void cancel(final @Nullable Object tag) {
if (callTags.isEmpty())
return;
if (tag != null) {
for (int index = 0; index < callTags.size(); index++) {
CallTag callTag = callTags.get(index);
if (callTag.tag.equals(tag)) {
callTag.call.cancel();
callTags.remove(index);
index--;
}
}
} else {
for (CallTag callTag : callTags) {
callTag.call.cancel();
}
callTags.clear();
}
}
public synchronized boolean contains(Call<?> call) {
for (CallTag callTag : callTags) {
if (call == callTag.call) {
return true;
}
}
return false;
}
/**
* 保存call和tag
*/
final static class CallTag {
private final Call<?> call;
private final Object tag;
CallTag(Call<?> call, Object tag) {
Utils.checkNotNull(call == null, "call==null");
Utils.checkNotNull(tag == null, "tag==null");
this.call = call;
this.tag = tag;
}
}
}
2.8 ProgressInterceptor 攔截器監聽(tīng)下載和上傳進(jìn)度
繼承okhttp3.Interceptor ,構造方法中傳入ProgressListener監聽(tīng)進(jìn)度
/**
* 創(chuàng )建時(shí)間:2018/8/2
* 編寫(xiě)人: chengxin
* 功能描述:上傳或下載進(jìn)度監聽(tīng)攔截器
*/
public class ProgressInterceptor implements Interceptor {private final ProgressListener mProgressListener;
public ProgressInterceptor(ProgressListener progressListener) {
Utils.checkNotNull(progressListener, "progressListener==null");
this.mProgressListener = progressListener;
}
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
RequestBody requestBody = request.body();
//判斷是否有上傳需求
if (requestBody != null && requestBody.contentLength() > 0) {
Request.Builder builder = request.newBuilder();
RequestBody newRequestBody = new ProgressRequestBody(requestBody, mProgressListener, request);
request = builder.method(request.method(), newRequestBody).build();
}
Response response = chain.proceed(request);
ResponseBody responseBody = response.body();
if (responseBody != null && responseBody.contentLength() > 0) {
Response.Builder builder = response.newBuilder();
ResponseBody newResponseBody = new ProgressResponseBody(responseBody, mProgressListener, request);
response = builder.body(newResponseBody).build();
}
return response;
}
}
2.9 HttpLoggingInterceptor 可以單獨指定某個(gè)請求的日志級別
構造OkhttpClient時(shí)添加此攔截器,在請求的服務(wù)方法中添加注解
@Headers("LogLevel:NONE") 或 @Headers("LogLevel:BASIC") 或 @Headers("LogLevel:HEADERS") 或@Headers("LogLevel:BODY")
@FormUrlEncoded
@Headers("LogLevel:HEADERS")
@POST("user/login")
Call2<LoginInfo> getLogin(@Field("username") String username, @Field("password") String password);
3.1 初始化全局Retrofit對象
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://wanandroid.com/")
.callFactory(new OkHttpClient.Builder()
.addNetworkInterceptor(httpLoggingInterceptor)
.build())
//必須添加此adapter 用于構建處理回調
.addCallAdapterFactory(ExecutorCallAdapterFactory.INSTANCE)
//添加自定義json解析器
.addConverterFactory(GsonConverterFactory.create())
.build();
RetrofitFactory.DEFAULT = retrofit;
//可以添加多個(gè),如:
RetrofitFactory.OTHERS.put("other",otherRetrofit);
3.2 添加請求服務(wù)接口
下面為登錄的 post請求
@FormUrlEncoded
@POST("user/login")
Call2<LoginInfo> getLogin(@Field("username") String username, @Field("password") String password);
3.3 添加ILoadingView,用于開(kāi)啟和結束動(dòng)畫(huà)
Activity 或者Fragment 可以繼承 ILoadingView接口實(shí)現開(kāi)始和結束動(dòng)畫(huà)
public interface ILoadingView {
/**
* 顯示加載
*/
void showLoading();/**
* 隱藏加載
*/
void hideLoading();
}
3.4 添加AnimCallback 處理動(dòng)畫(huà)
這里重寫(xiě)parseThrowable處理一些Callback2中為未處理的異常
public abstract class AnimCallback<T> extends Callback2<T> {
private ILoadingView mLoadingView;public AnimCallback(@Nullable ILoadingView loadingView) {
this.mLoadingView = loadingView;
}
public void onStart(Call2<T> call2) {
if (mLoadingView != null)
mLoadingView.showLoading();
}
public void onCompleted(Call2<T> call2, @Nullable Throwable t, boolean canceled) {
if (canceled)
return;
if (mLoadingView != null)
mLoadingView.hideLoading();
}
public HttpError parseThrowable(Call2<T> call2, Throwable t) {
HttpError filterError;
if (t instanceof JsonSyntaxException) {
filterError = new HttpError("解析異常", t);
} else {
filterError = super.parseThrowable(call2, t);
}
return filterError;
}
}
3.5 發(fā)起請求
RetrofitFactory.create(ApiService.class)
.getLogin("xxxxx", "123456")
.enqueue(hashCode(), new AnimCallback<LoginInfo>(this) {
@Override
public void onError(Call2<LoginInfo> call2, HttpError error) {
//處理失敗
}
public void onSuccess(Call2<LoginInfo> call2, LoginInfo response) {
//處理成功 如保存登錄信息等
}
});
//在onDestor中取消未結束的請求
@Override
protected void onDestroy() {
super.onDestroy();
//hashCode() 能保證唯一性,取消當前頁(yè)面所發(fā)起的所有請求,只要
// enqueue(tag, callback2) 傳入的是對應的hashCode() 即可
CallManager.getInstance().cancel(hashCode());
}
4.1 構建retrofit是需要ExecutorCallAdapterFactory實(shí)例,否則無(wú)法處理返回為Call2的服務(wù)接口
4.2 Callback2的回調函數均在主線(xiàn)程執行,如果調用了Call2.cancel()方法,除了onCompleted()方法會(huì )執行外其他回調方法都不會(huì )執行
implementation "com.xcheng:retrofit-helper:1.0.0"
retrofit-helper GitHub地址:
https://github.com/xchengDroid/retrofit-helper
Copyright 2019 xchengDroid
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
聯(lián)系客服