我就簡單理解一下Retrofit及其CallAdapter.Factory

前言

       用了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() 自定義

總之:

  1. Rxjava默認運行在當前線程中。如果當前線程是子線程,則rxjava運行在子線程;同樣,當前線程是主線程,則rxjava運行在主線程
  2.  如果只規定了事件產生的線程,那麼事件消費線程將跟隨事件產生線程。
  3. 如果只規定了事件消費的線程,那麼事件產生的線程和 當前線程保持一致。

這裏就不舉例說明了,可以自行寫個demo體會。

原理分析

迴歸正題,我們看下創建Retrofit對象時,addCallAdapterFactory方法的參數類型是CallAdapter.Factory,它的作用:網絡執行適配器工廠類,會根據接口裏面定義的類型來找到合適的適配器。

接口類型:

  1. 當接口返回返回類型爲Observable(或者Observable的簡化版Single、Completable、Maybe),Call對象就是OkHttpCall對象,這個Call對象是用來進行網絡請求的,此時的回調是在子線程的,    通過rxjava發射到主線程,線程切換是使用Rxjava的線程切換調度器;比如主線線程調度器是AndroidSchedulers.mainThread(),也是使用Handler實現的。
  2.  當接口返回類型爲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的設計非常強大。本篇文章中貼出的框架源碼比較少,可能不能一下子看懂,可以自己實踐操作,翻源碼理解一下來加深印象。如果看一遍不懂,看兩遍,反覆循環,直到看懂爲止。以前覺得熟練使用框架就行,現在覺得非常有必要閱讀框架源碼,理解別人的設計思想,學以致用,提高自己。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章