我就简单理解一下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的设计非常强大。本篇文章中贴出的框架源码比较少,可能不能一下子看懂,可以自己实践操作,翻源码理解一下来加深印象。如果看一遍不懂,看两遍,反复循环,直到看懂为止。以前觉得熟练使用框架就行,现在觉得非常有必要阅读框架源码,理解别人的设计思想,学以致用,提高自己。

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