歡迎訪問我的個人博客 ,原文鏈接:http://wensibo.net/2017/09/05/retrofit/ ,未經允許不得轉載!
今天是九月的第四天了,學校也正式開學,趁着大學最后一年的這大好時光,抓緊時間趕快學習新知識吧!今天想要與大家一起分享的是Retrofit,由於網上已經有許多講解Retrofit使用的文章了,本篇文章只會給一個小小的示例,以這個示例作為入口分析其源碼,同樣也會貼上流程圖,以免迷路。話不多說,我們開始吧!!!
關於Retrofit
簡介
Retrofit是近來十分火熱的一個網絡請求開源庫,Android開發者使用的網絡請求開源庫從最早的HttpClient與HttpURLConnection到2013年Google官方推出的Volley,接着就到了現在很火的OKHttp,最后才到了Retrofit。網絡請求開源庫的演變也正是移動互聯網下用戶對網絡需求的真實寫照。有哪個用戶不想使用APP的時候網絡加載速度更快,更省流量,更加安全呢?也就是基於用戶的這些需求,才有了許多開源庫的不斷迭代,而Retrofit可以說正是當下最適合開發者使用的網絡請求開源庫之一。
何出此言呢?首先它是由大名鼎鼎的square公司出品的,或許你不知道square公司,但你應該認識Jake Wharton,不過他最近已經到谷歌去了,倘若你連他都不知道,那你應該使用過他開發的這些開源庫:OkHttp
,picasso
,butterknife
,RxAndroid
等等,可以說Retrofit是由一個十分厲害的公司開發和維護的,所以你大可以放心地在你的項目中使用。
什么場景下適合使用呢?
盡管Retrofit十分強大,但是他卻不一定適合所有場景,正所謂術業有專攻,我們也不必大材小用,如果是一些頻繁但是訪問量很小的網絡請求,那么Volley就足以對付了,接下來我列舉一下Retrofit普遍的使用場景。
- 服務器后台遵循
RESTful API
的設計風格。如果你對這種風格不熟悉,建議你看看阮一峰大神的這篇文章,或者向你的后台小伙伴請教一番。 - 項目中使用了RxJava。如果你的項目中使用了RxJava,那么使用Retrofit絕對會讓你的開發效率翻倍。
- 項目中的網絡數據請求量比較大。如果你的應用經常會有數據量比較大的網絡請求,那么使用Retrofit也會很有效,因為Retrofit底層的實現是使用OKHttp,而OKHttp就是適用於這種場景的。
如果你符合以上三種情況,當然是選擇Retrofit啦!
一個簡單的栗子
說了這么多,我們就通過下面這個栗子來看看他究竟好在哪里?
需要說明的是:這個例子是用來獲取干貨集中營API上面的數據
1、首先定義一個常量用來描述要訪問的服務器主機的地址
public class GankConfig {
public static final String HOST = "http://gank.io/api/";
}
2、定義返回數據的bean類
public class GankData {
public List<String> category;
public Result results;
public class Result{
@SerializedName("Android")
public List<Gank> androidList;
@SerializedName("休息視頻")
public List<Gank> restVideoList;
@SerializedName("iOS")
public List<Gank> iosList;
@SerializedName("福利")
public List<Gank> meiZiList;
@SerializedName("拓展資源")
public List<Gank> extendResourceList;
@SerializedName("瞎推薦")
public List<Gank> suggestionList;
@SerializedName("App")
public List<Gank> appList;
@SerializedName("前端")
public List<Gank> webList;
}
}
3、定義要訪問的接口
public interface GankRetrofit {
//這里以獲取指定日期的內容為例子
@GET("day/{year}/{month}/{day}")
GankData getDailyData(@Path("year") int year, @Path("month") int month, @Path("day") int day);
}
4、用單例模式創建一個Retrofit客戶端
public class GankRetrofitClient {
private volatile static GankRetrofit gankRetrofit;
private static Retrofit retrofit;
private GankRetrofitClient(){}
static{
Gson date_gson = new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").create();
retrofit = new Retrofit.Builder()
.baseUrl(GankConfig.HOST)
.addConverterFactory(GsonConverterFactory.create(date_gson))//添加一個轉換器,將gson數據轉換為bean類
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())//添加一個適配器,與RxJava配合使用
.build();
}
public static GankRetrofit getGankRetrofitInstance() {
if (gankRetrofit==null){
synchronized (GankRetrofitClient.class){
if (gankRetrofit==null){
gankRetrofit=retrofit.create(GankRetrofit.class);
}
}
}
return gankRetrofit;
}
}
5、使用Retrofit進行網絡請求
GankData data= GankRetrofitClient.getGankRetrofitInstance().getDailyData(2017, 9, 1);
源碼解析
從Builder模式創建實例開始看起
首先我們先從上面的第4步開始解析源碼,有下面這段代碼:
retrofit = new Retrofit.Builder()
.baseUrl(GankConfig.HOST)
.addConverterFactory(GsonConverterFactory.create(date_gson))//添加一個轉換器,將gson數據轉換為bean類
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())//添加一個適配器,與RxJava配合使用
.build();
很明顯這個是使用了Builder模式,接下來我們一步一步來看里面做了什么?首先是Builder()。
public Builder() {
this(Platform.get());
}
Builder(Platform platform) {
this.platform = platform;
//添加轉換器,請見下面關於addConverterFactory()的講解
converterFactories.add(new BuiltInConverters());
}
構造方法中的參數是Platform的靜態方法get(),接下來就看看get()。
private static final Platform PLATFORM = findPlatform();
static Platform get() {
return PLATFORM;
}
private static Platform findPlatform() {
try {
Class.forName("android.os.Build");
if (Build.VERSION.SDK_INT != 0) {
return new Android();
}
} catch (ClassNotFoundException ignored) {
}
try {
Class.forName("java.util.Optional");
return new Java8();
} catch (ClassNotFoundException ignored) {
}
return new Platform();
}
}
可以看到,Retrofit支持多平台,包括Android與JAVA8,它會根據不同的平台設置不同的線程池。
先來看看到目前為止我們分析到哪里了
接下來看一下baseUrl()方法。
public Builder baseUrl(String baseUrl) {
checkNotNull(baseUrl, "baseUrl == null");
HttpUrl httpUrl = HttpUrl.parse(baseUrl);
if (httpUrl == null) {
throw new IllegalArgumentException("Illegal URL: " + baseUrl);
}
return baseUrl(httpUrl);
}
很容易理解,baseUrl()是配置服務器的地址的,如果為空,那么就會拋出異常。
接着是addConverterFactory()
private final List<Converter.Factory> converterFactories = new ArrayList<>();
public Builder addConverterFactory(Converter.Factory factory) {
converterFactories.add(checkNotNull(factory, "factory == null"));
return this;
}
大家是不是還記得剛才在Builder()方法初始化的時候,有這樣一行代碼:
converterFactories.add(new BuiltInConverters());
可以看到,converterFactories在初始化的時候就已經添加了一個默認的Converter,那我們手動添加的這個GsonConverter是干什么用的呢?
public final class GsonConverterFactory extends Converter.Factory {
public static GsonConverterFactory create() {
return create(new Gson());
}
public static GsonConverterFactory create(Gson gson) {
return new GsonConverterFactory(gson);
}
private final Gson gson;
private GsonConverterFactory(Gson gson) {
if (gson == null) throw new NullPointerException("gson == null");
this.gson = gson;
}
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
Retrofit retrofit) {
TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
return new GsonResponseBodyConverter<>(gson, adapter);
}
@Override
public Converter<?, RequestBody> requestBodyConverter(Type type,
Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
return new GsonRequestBodyConverter<>(gson, adapter);
}
}
其實這個Converter主要的作用就是將HTTP返回的數據解析成Java對象,我們常見的網絡傳輸數據有Xml、Gson、protobuf等等,而GsonConverter就是將Gson數據轉換為我們的Java對象,而不用我們重新去解析這些Gson數據。
接着看addCallAdapterFactory()
private final List<CallAdapter.Factory> adapterFactories = new ArrayList<>();
public Builder addCallAdapterFactory(CallAdapter.Factory factory) {
adapterFactories.add(checkNotNull(factory, "factory == null"));
return this;
}
可以看到,CallAdapter同樣也被一個List維護,也就是說用戶可以添加多個CallAdapter,那Retrofit總得有一個默認的吧,默認的是什么呢?請看接下來的build()。
最后看一下build()
public Retrofit build() {
//檢驗baseUrl
if (baseUrl == null) {
throw new IllegalStateException("Base URL required.");
}
//創建一個call,默認情況下使用okhttp作為網絡請求器
okhttp3.Call.Factory callFactory = this.callFactory;
if (callFactory == null) {
callFactory = new OkHttpClient();
}
Executor callbackExecutor = this.callbackExecutor;
if (callbackExecutor == null) {
callbackExecutor = platform.defaultCallbackExecutor();
}
List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories);
//添加一個默認的callAdapter
adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));
List<Converter.Factory> converterFactories = new ArrayList<>(this.converterFactories);
return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories,
callbackExecutor, validateEagerly);
}
首先Retrofit會新建一個call,其實質就是OKHttp,作用就是網絡請求器;接着在上一點中我們困惑的callAdapter也已經能夠得到解決了,首先Retrofit有一個默認的callAdapter,請看下面這段代碼:
adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));
CallAdapter.Factory defaultCallAdapterFactory(Executor callbackExecutor) {
if (callbackExecutor != null) {
return new ExecutorCallAdapterFactory(callbackExecutor);
}
return DefaultCallAdapterFactory.INSTANCE;
}
final class ExecutorCallAdapterFactory extends CallAdapter.Factory {
final Executor callbackExecutor;
ExecutorCallAdapterFactory(Executor callbackExecutor) {
this.callbackExecutor = callbackExecutor;
}
@Override
public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
if (getRawType(returnType) != Call.class) {
return null;
}
final Type responseType = Utils.getCallResponseType(returnType);
return new CallAdapter<Object, Call<?>>() {
@Override public Type responseType() {
return responseType;
}
@Override public Call<Object> adapt(Call<Object> call) {
return new ExecutorCallbackCall<>(callbackExecutor, call);
}
};
}
static final class ExecutorCallbackCall<T> implements Call<T> {
final Executor callbackExecutor;
final Call<T> delegate;
ExecutorCallbackCall(Executor callbackExecutor, Call<T> delegate) {
this.callbackExecutor = callbackExecutor;
this.delegate = delegate;
}
@Override public void enqueue(final Callback<T> callback) {
if (callback == null) throw new NullPointerException("callback == null");
delegate.enqueue(new Callback<T>() {
@Override public void onResponse(Call<T> call, final Response<T> response) {
callbackExecutor.execute(new Runnable() {
@Override public void run() {
if (delegate.isCanceled()) {
// Emulate OkHttp's behavior of throwing/delivering an IOException on cancellation.
callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled"));
} else {
callback.onResponse(ExecutorCallbackCall.this, response);
}
}
});
}
@Override public void onFailure(Call<T> call, final Throwable t) {
callbackExecutor.execute(new Runnable() {
@Override public void run() {
callback.onFailure(ExecutorCallbackCall.this, t);
}
});
}
});
}
@Override public boolean isExecuted() {
return delegate.isExecuted();
}
@Override public Response<T> execute() throws IOException {
return delegate.execute();
}
@Override public void cancel() {
delegate.cancel();
}
@Override public boolean isCanceled() {
return delegate.isCanceled();
}
@SuppressWarnings("CloneDoesntCallSuperClone") // Performing deep clone.
@Override public Call<T> clone() {
return new ExecutorCallbackCall<>(callbackExecutor, delegate.clone());
}
@Override public Request request() {
return delegate.request();
}
}
}
可以看到默認的callAdapter是ExecutorCallAdapterFactory。callAdapter其實也是運用了適配器模式,其實質就是網絡請求器Call的適配器,而在Retrofit中Call就是指OKHttp,那么CallAdapter就是用來將OKHttp適配給不同的平台的,在Retrofit中提供了四種CallAdapter,分別如下:
- ExecutorCallAdapterFactory(默認使用)
- GuavaCallAdapterFactory
- Java8CallAdapterFactory
- RxJavaCallAdapterFactory
為什么要提供如此多的適配器呢?首先是易於擴展,例如用戶習慣使用什么適配器,只需要添加即可使用;再者RxJava如此火熱,因為其切換線程十分的方便,不需要手動使用handler切換線程,而Retrofit使用了支持RxJava的適配器之后,功能也會更加強大。
綜上我們已經將使用Builder模式創建出來的Retrofit實例分析完畢了,我們只需要對相關的功能進行配置即可,Retrofit負責接收我們配置的功能然后進行對象的初始化,這個也就是Builder模式屏蔽掉創建對象的復雜過程的好處。現在我們再次用流程圖來梳理一下剛才的思路。
網絡請求接口的創建
我最初使用Retrofit的時候覺得有一個地方十分神奇,如下:
GankRetrofit gankRetrofit=retrofit.create(GankRetrofit.class);
GankData data= gankRetrofit.getDailyData(2017, 9, 1);
要想解惑,首先得對動態代理有所了解,如果你對動態代理還不是很清楚,請點擊這里了解動態代理的原理,之后再接着往下看。
我們就以這里為切入點開始分析吧!首先是create()
public <T> T create(final Class<T> service) {
Utils.validateServiceInterface(service);
if (validateEagerly) {
eagerlyValidateMethods(service);
}
//重點看這里
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
new InvocationHandler() {
private final Platform platform = Platform.get();
@Override public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// If the method is a method from Object then defer to normal invocation.
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
if (platform.isDefaultMethod(method)) {
return platform.invokeDefaultMethod(method, service, proxy, args);
}
//下面就會講到哦
ServiceMethod<Object, Object> serviceMethod =
(ServiceMethod<Object, Object>) loadServiceMethod(method);
//下一小節講到哦
OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
//下兩個小節講哦
return serviceMethod.callAdapter.adapt(okHttpCall);
}
});
}
我們主要看Proxy.newProxyInstance方法,它接收三個參數,第一個是一個類加載器,其實哪個類的加載器都無所謂,這里為了方便就選擇了我們所定義的借口的類加載器;第二個參數是我們定義的接口的class對象,第三個則是一個InvocationHandler匿名內部類。
那大家應該會有疑問了,這個newProxyInstance到底有什么用呢?其實他就是通過動態代理生成了網絡請求接口的代理類,代理類生成之后,接下來我們就可以使用ankRetrofit.getDailyData(2017, 9, 1);
這樣的語句去調用getDailyData方法,當我們調用這個方法的時候就會被動態代理攔截,直接進入InvocationHandler的invoke方法。下面就來講講它。
invoke方法
它接收三個參數,第一個是動態代理,第二個是我們要調用的方法,這里就是指getDailyData
,第三個是一個參數數組,同樣的這里就是指2017, 9, 1
,收到方法名和參數之后,緊接着會調用loadServiceMethod方法來生產過一個ServiceMethod對象,這里的一個ServiceMethod對象就對應我們在網絡接口里定義的一個方法,相當於做了一層封裝。接下來重點來看loadServiceMethod方法。
loadServiceMethod方法
ServiceMethod<?, ?> loadServiceMethod(Method method) {
ServiceMethod<?, ?> result = serviceMethodCache.get(method);
if (result != null) return result;
synchronized (serviceMethodCache) {
result = serviceMethodCache.get(method);
if (result == null) {
result = new ServiceMethod.Builder<>(this, method).build();
serviceMethodCache.put(method, result);
}
}
return result;
}
它調用了ServiceMethod類,而ServiceMethod也使用了Builder模式,直接先看Builder方法。
Builder(Retrofit retrofit, Method method) {
this.retrofit = retrofit;
//獲取接口中的方法名
this.method = method;
//獲取方法里的注解
this.methodAnnotations = method.getAnnotations();
//獲取方法里的參數類型
this.parameterTypes = method.getGenericParameterTypes();
//獲取接口方法里的注解內容
this.parameterAnnotationsArray = method.getParameterAnnotations();
}
再來看build方法
public ServiceMethod build() {
callAdapter = createCallAdapter();
responseType = callAdapter.responseType();
if (responseType == Response.class || responseType == okhttp3.Response.class) {
throw methodError("'"
+ Utils.getRawType(responseType).getName()
+ "' is not a valid response body type. Did you mean ResponseBody?");
}
responseConverter = createResponseConverter();
for (Annotation annotation : methodAnnotations) {
parseMethodAnnotation(annotation);
}
if (httpMethod == null) {
throw methodError("HTTP method annotation is required (e.g., @GET, @POST, etc.).");
}
if (!hasBody) {
if (isMultipart) {
throw methodError(
"Multipart can only be specified on HTTP methods with request body (e.g., @POST).");
}
if (isFormEncoded) {
throw methodError("FormUrlEncoded can only be specified on HTTP methods with "
+ "request body (e.g., @POST).");
}
}
int parameterCount = parameterAnnotationsArray.length;
parameterHandlers = new ParameterHandler<?>[parameterCount];
for (int p = 0; p < parameterCount; p++) {
Type parameterType = parameterTypes[p];
if (Utils.hasUnresolvableType(parameterType)) {
throw parameterError(p, "Parameter type must not include a type variable or wildcard: %s",
parameterType);
}
Annotation[] parameterAnnotations = parameterAnnotationsArray[p];
if (parameterAnnotations == null) {
throw parameterError(p, "No Retrofit annotation found.");
}
parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
}
if (relativeUrl == null && !gotUrl) {
throw methodError("Missing either @%s URL or @Url parameter.", httpMethod);
}
if (!isFormEncoded && !isMultipart && !hasBody && gotBody) {
throw methodError("Non-body HTTP method cannot contain @Body.");
}
if (isFormEncoded && !gotField) {
throw methodError("Form-encoded method must contain at least one @Field.");
}
if (isMultipart && !gotPart) {
throw methodError("Multipart method must contain at least one @Part.");
}
return new ServiceMethod<>(this);
}
代碼稍微有點長,但是思路很清晰,主要的工作有
1、首先對注解的合法性進行檢驗,例如,HTTP的請求方法是GET還是POST,如果不是就會拋出異常;
2、根據方法的返回值類型和方法注解從Retrofit對象的的callAdapter列表和Converter列表中分別獲取到該方法對應的callAdapter和Converter;
3、將傳遞進來的參數與注解封裝在parameterHandlers中,為后面的網絡請求做准備。
先用流程圖梳理一下剛才的思路:
分析到這里,我們總算是明白了最初的兩行代碼原來干了這么多事情,J神真的是流弊啊!接下來我們就來看一下網絡請求部分。
使用OkHttpCall進行網絡請求
回頭看一下上一小節講解create方法時我們有這一行代碼:
OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
他將我們剛才得到的serviceMethod與我們實際傳入的參數傳遞給了OkHttpCall,接下來就來瞧瞧這個類做了些什么?
final class OkHttpCall<T> implements Call<T> {
private final ServiceMethod<T, ?> serviceMethod;
private final Object[] args;
private volatile boolean canceled;
// All guarded by this.
private okhttp3.Call rawCall;
private Throwable creationFailure; // Either a RuntimeException or IOException.
private boolean executed;
OkHttpCall(ServiceMethod<T, ?> serviceMethod, Object[] args) {
this.serviceMethod = serviceMethod;
this.args = args;
}
}
很可惜,我們好像沒有看到比較有用的東西,只是將傳進來的參數進行了賦值,那我們就接着看create方法中的最后一行吧!
callAdapter的使用
create方法的最后一行是這樣的:
return serviceMethod.callAdapter.adapt(okHttpCall);
最后是調用了callAdapter的adapt方法,上面我們講到Retrofit在決定使用什么callAdapter的時候是看我們在接口中定義的方法的返回值的,而在我們的例子中使用的是RxJava2CallAdapter
,因此我們就直接看該類中的adapt方法吧!
@Override
public Object adapt(Call<R> call) {
Observable<Response<R>> responseObservable = isAsync
? new CallEnqueueObservable<>(call)
: new CallExecuteObservable<>(call);
Observable<?> observable;
if (isResult) {
observable = new ResultObservable<>(responseObservable);
} else if (isBody) {
observable = new BodyObservable<>(responseObservable);
} else {
observable = responseObservable;
}
if (scheduler != null) {
observable = observable.subscribeOn(scheduler);
}
if (isFlowable) {
return observable.toFlowable(BackpressureStrategy.LATEST);
}
if (isSingle) {
return observable.singleOrError();
}
if (isMaybe) {
return observable.singleElement();
}
if (isCompletable) {
return observable.ignoreElements();
}
return observable;
}
首先在adapt方法中會先判斷是同步請求還是異步請求,這里我們以同步請求為例,直接看CallExecuteObservable。
final class CallExecuteObservable<T> extends Observable<Response<T>> {
private final Call<T> originalCall;
CallExecuteObservable(Call<T> originalCall) {
this.originalCall = originalCall;
}
@Override protected void subscribeActual(Observer<? super Response<T>> observer) {
// Since Call is a one-shot type, clone it for each new observer.
Call<T> call = originalCall.clone();
observer.onSubscribe(new CallDisposable(call));
boolean terminated = false;
try {
//重點看這里
Response<T> response = call.execute();
if (!call.isCanceled()) {
observer.onNext(response);
}
if (!call.isCanceled()) {
terminated = true;
observer.onComplete();
}
} catch (Throwable t) {
Exceptions.throwIfFatal(t);
if (terminated) {
RxJavaPlugins.onError(t);
} else if (!call.isCanceled()) {
try {
observer.onError(t);
} catch (Throwable inner) {
Exceptions.throwIfFatal(inner);
RxJavaPlugins.onError(new CompositeException(t, inner));
}
}
}
}
private static final class CallDisposable implements Disposable {
private final Call<?> call;
CallDisposable(Call<?> call) {
this.call = call;
}
@Override public void dispose() {
call.cancel();
}
@Override public boolean isDisposed() {
return call.isCanceled();
}
}
}
在subscribeActual方法中去調用了OKHttpCall的execute方法開始進行網絡請求,網絡請求完畢之后,會通過RxJava的操作符對返回來的數據進行轉換,並進行線程的切換,至此,Retrofit的一次使用也就結束了。最后我們再用一張完整的流程圖總結上述的幾個過程。
后記
相信通過上面的詳解,大家對Retrofit應該有了一個比較全面的認識,與其說它是一個網絡請求框架不如說他做了一層封裝,使得我們能夠更方便的間接使用了RxJava與OkHttp。從某種意義上來講我們從源碼中更應該學習其對設計模式的正確運用,使得整個框架的耦合度大大降低,調用者也使用得更加簡潔。最后希望這篇文章能夠對大家的面試有所幫助!