前言
用了Retrofit這麼久 ,感覺Retrofit的架構設計牛逼,是學習設計思想和設計模式的不二典範。但今天不是來詳細分析這個框架的原理,主要簡單瞭解下Retrofit的CallAdapter.Factory及怎麼通過CallAdapter.Factory兼容OkHttp和RxJava。
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(new GsonBuilder().create()))
.addCallAdapterFactory(RxJava2CallAdapterFactory.createAsync())
.baseUrl("https://api.douban.com/v2/").client(httpClient.build())
.build();
BookService service = retrofit.create(BookService.class);
爲了使Retrofit支持數據返回自動解析成實體,添加:
addConverterFactory(GsonConverterFactory.create(new GsonBuilder().create()))
爲了使Retrofit支持Rxjava使用,添加:
addCallAdapterFactory(RxJava2CallAdapterFactory.createAsync())
本次框架版本是V2.0以上,衆所周知,Retrofit的底層網絡請求使用就是OkHttp,而OkHttp的底層是基於線程池和Sokcet實現,並不是基於HttpURLConnection,對於這些框架的的詳細使用、優缺點、底層原理這裏就不多說了。
知識回顧
OkHttp的簡單用法
異步請求--對應enqueue方法
OkHttpClient.Builder builder = new OkHttpClient.Builder();
OkHttpClient build = builder.build();
Request.Builder builder1 = new Request.Builder();
builder1.url("https://api.douban.com/v2/book/search?q=金瓶梅&tag=&start=0&count=1");
Request request = builder1.build();
// build.newCall(request).execute();
build.newCall(request).enqueue(new okhttp3.Callback() {
@Override
public void onFailure(okhttp3.Call call, IOException e) {
//運行在子線程
}
@Override
public void onResponse(okhttp3.Call call, final okhttp3.Response response) throws IOException {
//運行在子線程
}
});
注意:callback是運行在子線程,是因爲使用了線程池執行異步調用 ,不能更新UI
同步請求--對應execute方法
OkHttpClient.Builder builder = new OkHttpClient.Builder();
OkHttpClient build = builder.build();
Request.Builder builder1 = new Request.Builder();
builder1.url("https://api.douban.com/v2/book/search?q=金瓶梅&tag=&start=0&count=1");
Request request = builder1.build();
try {
okhttp3.Response response = build.newCall(request).execute();
} catch (IOException e) {
e.printStackTrace();
}
上面由於是在主線程執行網絡 請求,會報如下錯誤:
android.os.NetworkOnMainThreadException
at android.os.StrictMode$AndroidBlockGuardPolicy.onNetwork(StrictMode.ja
at java.net.Inet6AddressImpl.lookupHostByName(Inet6AddressImpl.java:86)
at java.net.Inet6AddressImpl.lookupAllHostAddr(Inet6AddressImpl.java:74)
//省略...
Retrofit的簡單用法
同okhttp一樣也有異步和同步的操作
先定義一個數據接口
public interface BookService {
//https://api.douban.com/v2/book/search?q=金瓶梅&tag=&start=0&count=1
/**
* Retrofit網絡請求測試
* @param name
* @param tag
* @param start
* @param count
* @return
*/
@GET("book/search")
Call<Book> getSearchBook(@Query("q") String name,
@Query("tag") String tag,
@Query("start") int start,
@Query("count") int count);
/**
* Retrofit和Rxjava結合網絡請求測試
* @param name
* @param tag
* @param start
* @param count
* @return
*/
@GET("book/search")
Observable<Book> getSearchBook2(@Query("q") String name,
@Query("tag") String tag, @Query("start") int start,
@Query("count") int count);
}
異步請求--對應enqueue方法
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.douban.com/v2/")
.addConverterFactory(GsonConverterFactory.create(new GsonBuilder().create()))
.build();
BookService service = retrofit.create(BookService.class);
Call<Book> call = service.getSearchBook("金瓶梅", null, 0, 1);
call.enqueue(new Callback<Book>() {
@Override
public void onResponse(Call<Book> call, Response<Book> response) {
//運行在主線程
}
@Override
public void onFailure(Call<Book> call, Throwable t) {
//運行主線程
}
});
通過上面知道,使用Retrofit 不需要考慮線程調度及數據解析的問題,Retrofit已經給我們做好了,真是省事的框架。
注意:callback是運行在主線程(最後使用了Handler切換到了主線程) ,可以能更新UI
同步請求--對應execute方法
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.douban.com/v2/")
.addConverterFactory(GsonConverterFactory.create(new GsonBuilder().create()))
.build();
BookService service = retrofit.create(BookService.class);
//同步
Call<Book> call = service.getSearchBook("金瓶梅", null, 0, 1);
try {
Response<Book> execute = call.execute();
} catch (IOException e) {
e.printStackTrace();
}
和OkHttp一樣在主線程執行網絡請求也會報錯NetworkOnMainThreadException,爲了使同步請求不報錯,只能使用子線程執行上面代碼。
Retrofit和Rxjava組合的簡單用法
如果你熟練使用Rxjava,又想用Rxjava進行網絡請求,Retrofit已經提供了完美的支持,只需要添加一行代碼就支持了,只不過接口返回類型不是Call,而是被觀察者Observable。
由於Rxjava是異步的,它又是怎麼完美結合Retrofit的同步和異步請求的呢?後面會說到,這可是本篇的重點。我們先來看看Retrofit和Rxjava組合的常規用法:
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(new GsonBuilder().create()))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.baseUrl("https://api.douban.com/v2/").client(httpClient.build())
.build();
BookService service = retrofit.create(BookService.class);
io.reactivex.Observable<Book> observable = service.getSearchBook2("金瓶梅", null, 0, 1);//創建觀察者
observable.subscribeOn(Schedulers.io())//請求數據的事件發生在io線程
.observeOn(AndroidSchedulers.mainThread())//請求完成後在主線程更顯UI
.subscribe(new io.reactivex.Observer<Book>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
}
@Override
public void onNext(@NonNull Book book) {
}
@Override
public void onError(@NonNull Throwable e) {
}
@Override
public void onComplete() {
}
});
到這裏我先簡單說下RxJava的線程調度:
create() , just() , from() 等 -- 事件產生
map() , flapMap() , scan() , filter() 等 -- 事件加工
subscribe() -- 事件消費
- 事件產生:默認運行在當前線程,可以由 subscribeOn() 自定義線程
- 事件加工:默認跟事件產生的線程保持一致, 可以由 observeOn() 自定義線程
- 事件消費:默認運行在當前線程,可以有observeOn() 自定義
總之:
- Rxjava默認運行在當前線程中。如果當前線程是子線程,則rxjava運行在子線程;同樣,當前線程是主線程,則rxjava運行在主線程
- 如果只規定了事件產生的線程,那麼事件消費線程將跟隨事件產生線程。
- 如果只規定了事件消費的線程,那麼事件產生的線程和 當前線程保持一致。
這裏就不舉例說明了,可以自行寫個demo體會。
原理分析
迴歸正題,我們看下創建Retrofit對象時,addCallAdapterFactory方法的參數類型是CallAdapter.Factory,它的作用:網絡執行適配器工廠類,會根據接口裏面定義的類型來找到合適的適配器。
接口類型:
- 當接口返回返回類型爲Observable(或者Observable的簡化版Single、Completable、Maybe),Call對象就是OkHttpCall對象,這個Call對象是用來進行網絡請求的,此時的回調是在子線程的, 通過rxjava發射到主線程,線程切換是使用Rxjava的線程切換調度器;比如主線線程調度器是AndroidSchedulers.mainThread(),也是使用Handler實現的。
- 當接口返回類型爲Call,直接調用enqueue方法來進行網絡請求,此時的回調是在主線程的,因爲Retrofit使用MainThreadExecutor(Handler)來回調到主線程
不論是接口返回返回類型爲Observable還是Call,最終都使用了okhttp3.Call執行網絡請求,而OkHttpCall(在retrofit2包下)封裝了okhttp3.Call的相關方法。特別說明這裏的Call就是OkHttpCall(實現Call接口),而OkHttpCall分爲同步請求和異步請求,分別對應的是execute方法和enqueue方法。
上面兩種接口是怎麼區分?
創建Retrofit對象時,通過調用Retrofit.Builder的addCallAdapterFactory方法添加了平臺適配器的工廠類CallAdapter.Factory,它的兩個子類是ExecutorCallAdapterFactory和RxJava2CallAdapterFactory。
默認是使用ExecutorCallAdapterFactory。看源碼:
Retrofit類下:
public Retrofit build() {
if (baseUrl == null) {
throw new IllegalStateException("Base URL required.");
}
okhttp3.Call.Factory callFactory = this.callFactory;
if (callFactory == null) {
callFactory = new OkHttpClient();
}
Executor callbackExecutor = this.callbackExecutor;
if (callbackExecutor == null) {
callbackExecutor = platform.defaultCallbackExecutor();
}
// Make a defensive copy of the adapters and add the default Call adapter.
List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories);
//添加默認的適配器
adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));
// Make a defensive copy of the converters.
List<Converter.Factory> converterFactories = new ArrayList<>(this.converterFactories);
return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories,
callbackExecutor, validateEagerly);
}
Platform類下:
static class Android extends Platform {
@Override public Executor defaultCallbackExecutor() {
return new MainThreadExecutor();
}
@Override CallAdapter.Factory defaultCallAdapterFactory(@Nullable Executor callbackExecutor) {
if (callbackExecutor == null) throw new AssertionError();
//根據主線程Executor實例化了ExecutorCallAdapterFactory
return new ExecutorCallAdapterFactory(callbackExecutor);
}
static class MainThreadExecutor implements Executor {
private final Handler handler = new Handler(Looper.getMainLooper());
@Override public void execute(Runnable r) {
handler.post(r);
}
}
}
上面的callbackExecutor主要作用是
將當前的數據調度到主線程去。
最終在接口調用時,通過根據接口返回類型returnType和annotations(這個參數實際沒用上)返回指定的CallAdapter.Factory,看源碼:
ServiceMethod類下:
private CallAdapter<T, R> createCallAdapter() {
//省略...
try {
//noinspection unchecked
return (CallAdapter<T, R>) retrofit.callAdapter(returnType, annotations);
} catch (RuntimeException e) { // Wide exception range because factories are user code.
throw methodError(e, "Unable to create call adapter for %s", returnType);
}
}
Retrofit類下:
/**
* Returns the {@link CallAdapter} for {@code returnType} from the available {@linkplain
* #callAdapterFactories() factories}.
*
* @throws IllegalArgumentException if no call adapter available for {@code type}.
*/
public CallAdapter<?, ?> callAdapter(Type returnType, Annotation[] annotations) {
return nextCallAdapter(null, returnType, annotations);
}
/**
* Returns the {@link CallAdapter} for {@code returnType} from the available {@linkplain
* #callAdapterFactories() factories} except {@code skipPast}.
*
* @throws IllegalArgumentException if no call adapter available for {@code type}.
*/
public CallAdapter<?, ?> nextCallAdapter(@Nullable CallAdapter.Factory skipPast, Type returnType,
Annotation[] annotations) {
//省略...
int start = adapterFactories.indexOf(skipPast) + 1;
for (int i = start, count = adapterFactories.size(); i < count; i++) {
//根據返回類型獲取合適的適配器
CallAdapter<?, ?> adapter = adapterFactories.get(i).get(returnType, annotations, this);
if (adapter != null) {
return adapter;
}
}
//省略...
}
所以,
當接口返回類型是Observable時,使用RxJava2CallAdapterFactory執行網絡請求,必須通過addCallAdapterFactory方法添加該適配器;
當接口返回類型是Call時,使用ExecutorCallAdapterFactory執行網絡請求;
addCallAdapterFactory及RxJava2CallAdapterFactory的使用理解
1.當addCallAdapterFactory的值是RxJava2CallAdapterFactory.createAsync(),由於isAsync是true,看源碼:
Observable<Response<R>> responseObservable = isAsync
? new CallEnqueueObservable<>(call)
: new CallExecuteObservable<>(call);
故創建的是CallEnqueueObservable,這時候我們可以不指定Observable的事件發生的線程,平時都是通過如下代碼指定:
observable.subscribeOn(Schedulers.io())
之所以可以不指定是因爲而CallEnqueueObservable是異步的(CallExecuteObservable是同步請求使用),底層使用了線程池執行進行異步執行,相當在子線程執行了網絡請求。
事件消費所在線程(觀察者所在線程)可以不指定,默認就是事件產生線程。如果需要進行ui更新必須指定main線程,
AndroidSchedulers.mainThread()。
實例代碼:
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(new GsonBuilder().create()))
.addCallAdapterFactory(RxJava2CallAdapterFactory.createAsync())//createAsync
.baseUrl("https://api.douban.com/v2/").client(httpClient.build())
.build();
BookService service = retrofit.create(BookService.class);
io.reactivex.Observable<AppType> observable = service.getAppType();//創建觀察者
observable.observeOn(AndroidSchedulers.mainThread())//請求完成後在主線程更顯UI
//省略...
2.當addCallAdapterFactory的值是RxJava2CallAdapterFactory.create(),由於isAsync是false
故創建的是CallExecuteObservable,這時候必須指定網絡請求Observable的事件發生的IO線程,即:
observable.subscribeOn(Schedulers.io())
如果不指定IO線程,會出現NetworkOnMainThreadException,這是因爲主線程執行了網絡請求。爲什麼會出現這種異常?容我先吸口煙,再慢慢道來。上面說了CallExecuteObservable是同步請求的,底層沒有開闢線程池進行異步調用,簡單說就是沒有在子線程執行網絡請求。
實例代碼:
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(new GsonBuilder().create()))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())//create
.baseUrl("https://api.douban.com/v2/").client(httpClient.build())
.build();
BookService service = retrofit.create(BookService.class);
io.reactivex.Observable<AppType> observable = service.getAppType();//創建觀察者
observable.subscribeOn(Schedulers.io())//請求數據的事件發生在io線程
observable.observeOn(AndroidSchedulers.mainThread())//請求完成後在主線程更顯UI
//省略...
平時開發中默認都是使用 RxJava2CallAdapterFactory.create()多,也就是說Retrofit結合Rxjava使用,實際使用的是Retrofit的同步請求,這時候是需要指定事件消費的線程就行了即io線程
總結
通過上述簡單描述,可以看出Retrofit的設計非常強大。本篇文章中貼出的框架源碼比較少,可能不能一下子看懂,可以自己實踐操作,翻源碼理解一下來加深印象。如果看一遍不懂,看兩遍,反覆循環,直到看懂爲止。以前覺得熟練使用框架就行,現在覺得非常有必要閱讀框架源碼,理解別人的設計思想,學以致用,提高自己。