• Retrofit2之源码解析2


    4.从架构角度看Retrofit的作用、原理和启示
    3.Retrofit 框架源码学习
    2.Retrofit分析-经典设计模式案例
    1.Retrofit分析-漂亮的解耦套路

    =================

    4.从架构角度看Retrofit的作用、原理和启示

    Retrofit是squareup公司的开源力作,和同属squareup公司开源的OkHttp,一个负责网络调度,一个负责网络执行,为Android开发者提供了即方便又高效的网络访问框架。

    不过,对于Retrofit这样设计精妙、代码简洁、使用方便的优秀开源项目,不能仅知道如何扩展和使用,或者仅研究它采用的技术或模式,

    “技”当然重要,但不能忽视了背后的“道”。

    对于Retrofit,我们还应该看到的,是她在优化App架构方面的努力,以及她在提升开发效率方面的借鉴和启示。

    本文试图通过一个具体场景,先总结Retrofit在架构中起到的作用,再分析其实现原理,最后探讨Retrofit给我们带来的启示。

    我们先通过一个简单的应用场景来回顾Retrofit的使用过程。

    基本场景

    通常来说,使用Retrofit要经过这样几个步骤

    1. 引用
        compile 'com.squareup.retrofit2:retrofit:2.3.0'
        compile 'com.squareup.retrofit2:retrofit-converters:2.3.0'
        compile 'com.squareup.retrofit2:retrofit-adapters:2.3.0'
    

    如果需要使用更多扩展功能,比如gson转换,rxjava适配等,可以视自己需要继续添加引用

        compile 'com.squareup.retrofit2:converter-gson:2.3.0'
        compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
    

    如果现有的扩展包不能满足需要,还可以自己扩展converter,adapter等。

    1. 定义接口
      Retrofit要求定义一个网络请求的接口,接口函数里要定义url路径、请求参数、返回类型。
    public interface INetApiService {
        @GET("/demobiz/api.php")
        Call<BizEntity> getBizInfo(@Query("id") String id);
    }
    

    在这个接口定义中,用注解@GET("/demobiz/api.php")声明了url路径,用注解@Query("id") 声明了请求参数。
    最重要的是,用Call<BizEntity>声明了返回值是一个Retrofit的Call对象,并且声明了这个对象处理的数据类型为BizEntity,BizEntity是我们自定义的数据模型。

    1. 依次获得Retrofit对象、接口实例对象、网络工作对象
      首先,需要新建一个retrofit对象。
      然后,根据上一步的接口,实现一个retrofit加工过的接口对象。
      最后,调用接口函数,得到一个可以执行网络访问的网络工作对象。
    //新建一个Retrofit对象
    Retrofit retrofit=new Retrofit.Builder()
    .baseUrl(Config.DOMAIN)//要访问的网络地址域名,如http://www.zhihu.com
    .addConverterFactory(GsonConverterFactory.create())
    .build();
    ...
    
    //用retrofit加工出对应的接口实例对象
    INetApiService netApiService= retrofit.create(INetApiService.class);
    //可以继续加工出其他接口实例对象
    IOtherService otherService= retrofit.create(IOtherService.class);
    ···
    
    //调用接口函数,获得网络工作对象
    Call<BizEntity> callWorker= netApiService.getBizInfo("id001");
    

    这个复杂的过程下来,最终得到的callWorker对象,才可以执行网络访问。

    1. 访问网络数据
      用上一步获取的worker对象,执行网络请求
    callWorker.enqueue(new Callback<BizEntity>() {
                @Override
                public void onResponse(Call<BizEntity> call, Response<BizEntity> response) {...}
                @Override
                public void onFailure(Call<BizEntity> call, Throwable t) {...}
            });
    

    在回调函数里,取得我们需要的BizEntity数据对象。
    网络访问结束。

    角色与作用

    我们从上面的应用场景可以看出,Retrofit并不做网络请求,只是生成一个能做网络请求的对象。
    Retrofit的作用是按照接口去定制Call网络工作对象。

    什么意思?就是说:
    Retrofit不直接做网络请求
    Retrofit不直接做网络请求
    Retrofit不直接做网络请求

    重要的事情说三遍。

    网络请求的目标虽然是数据,但是我们需要为这个数据写大量的配套代码,发起请求的对象Call,接收数据的对象CallBack,做数据转换的对象Converter,以及检查和处理异常的对象等。
    这对于一个项目的开发、扩展和维护来说,都是成本和风险。

    而Retrofit做的事情,就是为开发者节省这部分的工作量,Retrofit一方面从底层统一用OkHttp去做网络处理;

    另一方面在外层灵活提供能直接融入业务逻辑的Call网络访问对象。

    具体来说,Retrofit只负责生产对象,生产能做网络请求的工作对象,他有点像一个工厂,只提供产品,工厂本身不处理网络请求,产品才能处理网络请求。
    Retrofit在网络请求中的作用大概可以这样理解:


     
    Retrofit的作用

    我们看到,从一开始,Retrofit要提供的就是个Call工作对象。
    换句话说,对于给Retrofit提供的那个接口

    public interface INetApiService {
        @GET("/demobiz/api.php")
        Call<BizEntity> getBizInfo(@Query("id") String id);
    }
    

    这个接口并不是传统意义上的网络请求接口,这个接口不是用来获取数据的接口,而是用来生产对象的接口,这个接口相当于一个工厂,接口中每个函数的返回值不是网络数据,而是一个能进行网络请求的工作对象,我们要先调用函数获得工作对象,再用这个工作对象去请求网络数据。

    所以Retrofit的实用价值意义在于,他能根据你的接口定义,灵活地生成对应的网络工作对象,然后你再择机去调用这个对象访问网络。
    理解了这一点,我们才能去扩展Retrofit,并理解Retrofit的设计思想。

    功能扩展

    我们先来看Retrofit能扩展哪些功能,然后再去理解Retrofit的工作原理。
    Retrofit主要可以扩展三个地方:

    1. OkHttpClient
      Retrofit使用OkHttpClient来实现网络请求,这个OkHttpClient虽然不能替换为其他的网络执行框架比如Volley,但是Retrofit允许我们使用自己扩展OkHttpClient,一般最常扩展的就是Interceptor拦截器了
    OkHttpClient mClient = new OkHttpClient.Builder()
                    .addInterceptor(new Interceptor() {
                        @Override
                        public Response intercept(Chain chain) throws IOException {
                            try {
                                Request.Builder builder = chain.request().newBuilder();
                                builder.addHeader("Accept-Charset", "UTF-8");
                                builder.addHeader("Accept", " application/json");
                                builder.addHeader("Content-type", "application/json");
                                Request request = builder.build();
                                return chain.proceed(request);
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                            return null;
                        }
                    }).build();
    
    Retrofit retrofit = new Retrofit.Builder()
                    .baseUrl(Config.DOMAIN)
                    .addConverterFactory(GsonConverterFactory.create())
                    .client(mClient)
                    .build();
    
    1. addConverterFactory

    扩展的是对返回的数据类型的自动转换,把一种数据对象转换为另一种数据对象。
    在上述场景中,GsonConverterFactory可以把Http访问得到的json字符串转换为Java数据对象BizEntity,这个BizEntity是在INetApiService接口中要求的的。
    这种转换我们自己也经常做,很好理解。
    如果现有的扩展包不能满足需要,可以继承Retrofit的接口。retrofit2.Converter<F,T>,自己实现Converter和ConverterFactory。
    在创建Retrofit对象时,可以插入我们自定义的ConverterFactory。

    //retrofit对象
    Retrofit retrofit=new Retrofit.Builder()
    .baseUrl(Config.DOMAIN)
    .addConverterFactory(GsonConverterFactory.create())
    .addConverterFactory(YourConverterFactory.create())//添加自定义Converter
    .build();
    
    1. addCallAdapterFactory

            扩展的是对网络工作对象callWorker的自动转换,把Retrofit中执行网络请求的Call对象,转换为接口中定义的Call对象。
    这个转换不太好理解,我们可以对照下图来理解:


     
    callAdapter转换Call对象

    Retrofit本身用一个OkHttpCall的类负责处理网络请求,而我们在接口中定义需要定义很多种Call,例如Call<BizEntity>,或者Flowable<BizEntity>等,接口里的Call和Retrofit里的OkHttpCall并不一致,所以我们需要用一个CallAdapter去做一个适配转换。
    (Retrofit底层虽然使用了OkHttpClient去处理网络请求,但她并没有使用okhttp3.call这个Call接口,而是自己又建了一个retrofit2.Call接口,OkHttpCall继承的是retrofit2.Call,与okhttp3.call只是引用关系。
    这样的设计符合依赖倒置原则,可以尽可能的与OkHttpClient解耦。)

    这其实是Retrofit非常核心,也非常好用的一个设计,如果我们在接口中要求的函数返回值是个RxJava的Flowable对象

    public interface INetApiService {
        @GET("/demobiz/api.php")
        Flowable<BizEntity> getBizInfo(@Query("id") String id);
    }
    

    那么我们只需要为Retrofit添加对应的扩展

    //retrofit对象
    Retrofit retrofit=new Retrofit.Builder()
    .baseUrl(Config.DOMAIN)
    .addConverterFactory(GsonConverterFactory.create())
    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
    .build();
    

    就能得到Flowable类型的callWorker对象

    //用retrofit加工出对应的接口实例对象
    INetApiService netApiService= retrofit.create(INetApiService.class);
    ···
    //调用接口函数,获得网络工作对象
    Flowable<BizEntity> callWorker= netApiService.getBizInfo("id001");
    

    在这里,callAdapter做的事情就是把retrofit2.Call对象适配转换为Flowable<T>对象。
    同样,如果现有的扩展包不能满足需要,可以继承Retrofit的接口retrofit2.CallAdapter<R,T>,自己实现CallAdapter和CallAdapterFactory。

    Retrofit实现原理

    Retrofit固然设计精妙,代码简洁,使用方便,但相应的,我们要理解Retrofit的实现原理也不太容易,这么精妙的设计是极佳的研究素材,我们不能仅仅停留在知道怎么使用,怎么扩展的阶段,那实在是对这个优秀开源项目的浪费。
    其实,Retrofit使用的,就是动态代理,方法注解、建造者和适配器等成熟的技术或模式,但是由于她的设计紧凑,而且动态代理屏蔽了很多过程上的细节,所以比较难以理解。

    Retrofit实现原理——从动态代理开始

    从前面的使用场景可知,retrofit会生成一个接口实例。

    //用retrofit加工出对应的接口实例对象
    INetApiService netApiService= retrofit.create(INetApiService.class);
    

    到Retrofit源码里看create函数,是一个动态代理。

     public <T> T create(final Class<T> service) {
        ...
        return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
            new InvocationHandler() {
                ...
                ServiceMethod<Object, Object> serviceMethod =
                    (ServiceMethod<Object, Object>) loadServiceMethod(method);
                OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
                return serviceMethod.callAdapter.adapt(okHttpCall);
              }
            });
      }
    

    要理解动态代理,最好要看到动态生成的代理类。

    由于动态代理是在运行时动态生成的代理类,用常规的反编译方法无法查看,一般要使用Java提供的sun.misc.ProxyGenerator.generateProxyClass(String proxyName,class[] interfaces)函数生成代理类,函数会返回byte[]字节码,然后对字节码反编译得到Java代码。
    有一个小问题是,AndroidStudio并不提供sun.misc这个包,我们需要用IntelliJ或者Eclipse建立一个Java工程,在Java环境里调用这个函数。

    拿到的代理类,大概是这样的:

    public final class INetApiService extends Proxy implements INetApiService {
      ...//一些Object自带方法
      private static Method m3;//接口定义的方法
      static {
        try {
          //Object自带方法的初始化
          m0,m1,m2 = ...
          //接口中定义的方法
          m3 = Class.forName("com.demo.net$INetApiService")//反射接口类
              .getMethod("getBizInfo",//反射函数
                  new Class[] { Class.forName("java.lang.String") });//反射参数
          //接口中定义的其他方法
          ...
        } 
        ...
      }
    //返回接口实例对象
    public INetApiService (InvocationHandler invocationHandler){
      super(invocationHandler);
    }
    //
    public final Call getBizInfo(String str){
      ...
      try{//用Handler去调用
        return (Call)this.h.invoke(this, m3, new Object[]{str});
      }
    }
    
    }
    

    我们可以看到,代理类生成的是一个INetApiService接口的实例对象,该对象的getBizInfo函数返回的是接口中定义的Call网络工作对象,

    这也体现了Retrofit的核心价值,生成接口定义的Call网络工作对象。

    那么,这个Call网络工作对象是如何生成的呢,上面动态代理生成的代码是这样的:

     return (Call)this.h.invoke(this, m3, new Object[]{str});
    

    也就是说,这个Call网络工作对象是在InvocationHandler中实现的,也就是在Retrofit.create函数中,由InvocationHandler实现的。

    这样我们就明白了,Retrofit使用动态代理,其实是为了开发者在写代码时方便调用,而真正负责生产Call网络工作对象的,还是Retrofit.create函数中定义的这个InvocationHandler,这个InvocationHandler的代码我们再贴一遍:

            new InvocationHandler() {
                ...
                ServiceMethod<Object, Object> serviceMethod =
                    (ServiceMethod<Object, Object>) loadServiceMethod(method);
                OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
                return serviceMethod.callAdapter.adapt(okHttpCall);
              }
    

    ServiceMethod能让我们准确解析到INetApiService中定义的函数,为最后的适配转换提供转换目标,详细分析我们后面再说,先看适配转换的过程。

    我们看到,Retrofit内部默认使用OkHttpCall对象去处理网络请求,但是返回的网络工作对象是经过适配器转换的,转换成接口定义的那种Call网络工作对象。

    这个适配转换,就是Retrofit能按照接口去定制Call网络工作对象的秘密。

    Retrofit实现原理——适配转换Call对象

    我们在初始化Retrofit对象时,好像不添加CallAdapterFactory也能实现适配转换。

    //retrofit对象
    Retrofit retrofit=new Retrofit.Builder()
    .baseUrl(Config.DOMAIN)
    .addConverterFactory(GsonConverterFactory.create())
    //可以不添加CallAdapterFactory
    .build();
    

    这是怎么回事呢,我们知道Retrofit使用了建造者模式,建造者模式的特定就是实现了建造和使用的分离,所以建造者模式的建造函数里,

    一般会有很复杂的对象创建和初始化过程,所以我们要看一下Retrofit的build函数。

    public Retrofit build() {
          ...
          okhttp3.Call.Factory callFactory = this.callFactory;
          if (callFactory == null) {
            callFactory = new OkHttpClient();//使用OkHttpClient处理网络请求
          }
          ...
          //根据当前运行平台,设置默认的callAdapterFactory
          adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));
          ...
          return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories,
              callbackExecutor, validateEagerly);
        }
    

    这段代码里,我们看到Retrofit使用OkHttpClient处理网络请求,并且会添加默认的callAdapterFactory,这个platform是一个简单工厂,能根据当前系统平台去生成对应的callAdapterFactory

      private static Platform findPlatform() {
        try {
          Class.forName("android.os.Build");
          if (Build.VERSION.SDK_INT != 0) {
            return new Android();//根据当前系统平台返回相应的对象
          }
        ...
      }
      ...
      static class Android extends Platform {
        ...
        @Override CallAdapter.Factory defaultCallAdapterFactory(@Nullable Executor callbackExecutor) {
          if (callbackExecutor == null) throw new AssertionError();
          return new ExecutorCallAdapterFactory(callbackExecutor);
        }
        ...
      }
    

    这个Platform是Retrofit在Builder的构造函数里初始化的。

    所以,在Retrofit.build()函数中,我们为Retrofit默认添加的callAdapterFactory,是在Platform中为Android系统设定的ExecutorCallAdapterFactory。
    我们看ExecutorCallAdapterFactory的代码,这是一个工厂类,可以返回CallAdapter对象:

      @Override
      public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
        ...
        return new CallAdapter<Object, Call<?>>() {
          ...
          //               转换后              转换前,也就是OkHttpCall
          @Override public Call<Object> adapt(Call<Object> call) {
            return new ExecutorCallbackCall<>(callbackExecutor, call);
          }
        };
      }
    

    在adapt函数中,适配器会把Retrofit中用来访问网络的OkHttpCall,转换为一个ExecutorCallbackCall(继承了INetApiService接口里要求返回的网络工作对象retrofit2.Call),
    这个例子里面,由于OkHttpCall和ExecutorCallbackCall都实现了retrofit2.Call接口,结果出现了从Call<Object>转换为Call<Object>的情况,这可能不容易理解,我们换个RxJava2CallAdapterFactory来看看

      //RxJava2CallAdapterFactory中
      @Override
      public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
        ...
        return new RxJava2CallAdapter(responseType, scheduler, isAsync, isResult, isBody, isFlowable,
            isSingle, isMaybe, false);
    }
      //RxJava2CallAdapter中
      //               转换后        转换前,也就是OkHttpCall
      @Override public Object adapt(Call<R> call) {
       ...
       Observable<?> observable;
       ...
       return observable;
      }
    

    这个CallAdapter的转换就比较明显了,把retrofit2.Call对象通过适配器转换为了一个实为Observable<?>的Object对象。

    至此,我们可以理解Retrofit根据接口定义动态生产Call网络请求工作对象的原理了,其实就是通过适配器把retrofit2.Call对象转换为目标对象。

    至于适配器转换过程中,如何实现的对象转换,就可以根据需求来自由实现了,比如利用静态代理等,如有必要,我们可以自行开发扩展,Retrofit框架并不限制我们对于适配器的实现方式。

    Retrofit实现原理——函数解析、网络请求和数据转换

    在前面分析中,我们知道了Retrofit的整体工作流程,就是Retrofit用动态代理生成Call网络请求对象,在这个过程中,用适配器把Retrofit底层的retrofit2.Call对象转换为INetApiService中定义的Call网络请求对象(如Flowable)。

    问题是,Retrofit具体是如何知道了INetApiService中定义的Call网络请求对象,如何实现网络请求,以及如何执行的数据转换呢?

    具体过程如下;
    首先,根据INetApiService中定义的函数,解析函数,得到函数的具体定义,并生成对应的ServiceMethod。
    然后,根据这个ServiceMethod,实现一个OkHttpCall的Call对象,负责在Retrofit底层实现网络访问。
    其中,在网络访问返回了网络数据时,根据ServiceMethod实现数据转换。
    最后,利用上一小节中匹配的适配器,把OkHttpCall对象转换为INetApiService要求的Call网络请求对象。

    所以,我们要了解的就是函数解析、网络请求和数据转换这三个动作,至于最后的适配转换,在上一节中已经分析过了。

    1. 函数解析
    在接口函数里,用注解描述了输入参数,用Java对象定义了返回值类型,所以对输入参数和返回值,ServiceMethod采取了不同的方式去处理。
    输入参数
    输入参数是用来描述url的,它的处理相对简单,ServiceMethod会根据反射得到的Method,取得Annotation注解信息,这些注解是Retrofit自己预定义好的(retrofit2.http.*),ServiceMethod根据预先的定义,直接判断注解所属的逻辑分支,在有网络请求时分情况进行处理,就能得到目标url,http请求头等数据。
    返回值
    返回值是需要用CallAdapter去适配的,所以核心在于生成对应的CallAdapter。
    在Retrofit生成Call网络工作对象时,她通过动态代理获取到了接口函数的Method定义,从这个Method中可以获取函数定义的返回对象类型,由于这个转换是需要CallAdapterFactory生产CallAdapter对象去实现,而Retrofit事先并不知道要使用哪个Factory,所以她是遍历所有的CallAdapterFactory,根据目标函数的返回值类型,让每个Factory都去尝试生产一个CallAdapter,哪个成功就用哪个。

    2. 网络请求
    OkHttpCall继承的retrofit2.Call接口是为了依赖倒置解耦的,真正的网络请求是由OkHttpCall内部引用的okhttp3.call处理的,这个okhttp3.call是
    借道ServiceMethod获取的Retrofit中的callFactory,也就是Retrofit中的OkHttpClient。

    整个引用链条是这样的:
    OkHttpCall--okhttp3.call
    -->
    ServiceMethod--callFactory
    -->
    Retrofit.build()--callFactory//(如未扩展赋值)new OkHttpClient();
    -->
    Retrofit.Builder().client(mClient)//(可能有扩展赋值)扩展过的OkHttpClient

    最终的网络请求是由OkHttpCall调用OkHttpClient发出的,调用和回调等过程,也就是在OkHttpCall中处理的。

    网络请求的生成过程中,为了使用接口函数中定义的参数,OkHttpCall会调用ServiceMethod来生成Request请求对象,再交给OkHttpCall去处理。

    3. 数据转换
    因为回调是在OkHttpCall中处理的,所以对回调数据的转换也在OkHttpCall中触发,为了符合接口函数中定义的返回数据类型,OkHttpCall会调用ServiceMethod来转换Response返回数据对象。

    OkHttpCall对返回的网络数据,会调用一个serviceMethod.toResponse(ResponseBody body)函数,函数中执行的是:

      R toResponse(ResponseBody body) throws IOException {
        return responseConverter.convert(body);
      }
    

    这个函数可以把原始的okhttp3. ResponseBody数据转换为INetApiService接口中要求的数据类型(如BizEntity类型)。
    从代码可以看出,实现数据转换的核心对象其实是responseConverter,这个Converter实际上要依次经过Retrofit的建造和ServiceMethod的建造后,才能确定下来的。

    Retrofit建造时添加数据转换工厂
    Retrofit里有converterFactries列表,这是在我们初始化Retrofit实例时添加的

    //retrofit对象
    Retrofit retrofit=new Retrofit.Builder()
    .baseUrl(Config.DOMAIN)
    .addConverterFactory(GsonConverterFactory.create())
    .addConverterFactory(YourConverterFactory.create())//添加自定义Converter
    .build();
    

    ServiceMethod建造时设定数据转换器
    ServiceMethod在建造时,就已经确定了对应的是INetApiService中的哪个函数,所以需要明确设定自己的Converter<R,T>转换对象

      public ServiceMethod build() {
          ...
          responseConverter = createResponseConverter();
          ...
      }
    

    这需要调用Retrofit

        private Converter<ResponseBody, T> createResponseConverter() {
          ...
          retrofit.responseBodyConverter(responseType, annotations);
        }
    

    Retrofit会在自己的转换器工厂列表中遍历每个ConverterFactory,尝试根据ServiceMethod所对应的目标数据类型,找到Converter数据转换类

        for (int i = start, count = converterFactories.size(); i < count; i++) {
          Converter<ResponseBody, ?> converter =
              converterFactories.get(i).responseBodyConverter(type, annotations, this);
          if (converter != null) {
            //noinspection unchecked
            return (Converter<ResponseBody, T>) converter;
          }
        }
    

    以Gson转换为例,GsonConverterFactory会通过getAdapter来尝试匹配目标数据类型:

    public <T> TypeAdapter<T> getAdapter(TypeToken<T> type) {...}
    

    如果可以匹配,那么前面调用serviceMethod.toResponse(ResponseBody body)函数时,会调用

      R toResponse(ResponseBody body) throws IOException {
        return responseConverter.convert(body);
      }
    

    在调用这段代码时,其实就是调用了Gson中最终执行数据转换的代码:

      @Override public T convert(ResponseBody value) throws IOException {
        JsonReader jsonReader = gson.newJsonReader(value.charStream());
        try {
          return adapter.read(jsonReader);
        } finally {
          value.close();
        }
      }
    

    总结来说,Retrofit在类的单一职责方面分隔的很好,OkHttpCall类只负责网络交互,凡是需要知道函数定义的,都交给ServiceMethod类去处理,而ServiceMethod类对使用者不公开,因为Retrofit是个外观模式,而所有需要扩展的都在Retrofit的建造者中实现,他们的分工大概是这样的:


     
    三个类的分工

    这三个类分工合作,共同实现了函数解析、网络访问和数据转换,并保留了良好的可扩展性。

    Retrofit实现原理——整体结构与分工实现

    至此,Retrofit的实现细节就已经基本清楚了,他用动态代理去定制接口定义的Call网络工作对象,用适配器去把底层的Call对象转换为目标Call对象,用函数解析/OkHttpClient/数据转换等实现对Call对象的适配转换,并能处理真正的网络请求。
    这里面涉及的整体结构和角色分工,大概可以这样表示:


     
    整体结构与角色分工

    其中,扩展适配器、扩展数据转换和扩展OkHttpClient,虽然都是通过Retrofit实现扩展,但真正的使用者是Retrofit内部的ServiceMethod、OkHttpCall和okhttp3.call等类或对象。

    反推Retrofit的设计过程

    如果我们不直接正面分析Retrofit的结构设计和技术细节,而是先从Retrofit的功能和作用入手,倒过来推测Retrofit的目标,进而分析其架构和搭建细节,Retrofit为什么会设计成这样就很好理解了。

    Retrofit的功能是按照接口定义,自动定制Call网络工作对象,所以Retrofit的目标应该就是避免为网络访问开发大量的配套代码。

    为了实现这一目标,Retrofit需要分析哪些是易变的,哪些是不变的,然后分别处理。

    由于Retrofit提供网络访问的工作对象,又是服务于具体业务,所以可以分网络访问和具体业务两部分来分析。

    网络访问的不变性
    对于网络访问来说,不变的是一定有一个实现网络访问的对象,Retrofit选用了自家的OkHttpClient,不过为了把Retrofit和OkHttp两个项目解耦合,Retrofit根据依赖倒置原则,定义了Retrofit自己的Call即retrofit2.call,并定义了操作网络请求的OkHttpCall

    网络访问的易变性
    对于网络访问来说,易变的是网络访问的url、请求方式(get/post等)、Http请求的Header设置与安全设置等,以及返回的数据类型。

    针对易变的url和请求方式,Retrofit使用了方法注解的方式,可读性良好,扩展性优异,但这需要实现对接口函数中注解的解析,这样就有了ServiceMethod
    针对Http请求的各种设置,其实Retrofit没做什么,因为Retrofit使用的OkHttp有拦截器机制,可以应付这种变化。
    针对返回的数据类型,由于目标数据类型与业务有关,是不确定的,Retrofit无法提供一个万能的转换类,所以Retrofit提供了扩展接口,允许开发者自己定义ConverterFactory和Converter,去实现潜在的数据类型转换。

    具体业务的不变性
    对于具体业务来说,不变的是一定要有一个Call网络工作对象,所以Retrofit可以有一个生产对象的机制(像工厂一样)

    具体业务的易变性
    对于具体业务来说,易变的就是这个Call网络工作对象的类型,不仅有CallBacl回调、可能还有Flowable工作流、或者其他潜在的对象类型。

    针对这种Call对象的易变性,Retrofit也是无法提供一个万能的实现类,所以也是提供了扩展解耦,允许开发者自己定义CallAdapterFactory和CallAdapter,去实现潜在的Call类型转换。

    因为这种Call对象的生产需要有大量的配套代码,为了简化代码,Retrofit使用动态代理来生产这个对象。

    最后,因为需要处理的方法和对象太多太复杂,需要使用建造者模式来把建造过程和使用过程分离开。

    这样倒着走一遍之后,我们再看Retrofit的设计和实现原理,就会觉得水到渠成,对于Retrofit精妙的设计更会有一种切身体会。

    借鉴与启示

    在上文的反推过程中,我们可窥见(瞎猜)Jake大神的一些思路:

    1. 万物皆对象
      网络访问后,回调数据是个对象;网络访问本身也是个对象。
    2. 依赖倒置
      哪怕是使用自家的OkHttp,哪怕底层调用的始终是OkHttpClient,也需要依赖一个抽象的retrofit2.Call接口,依赖于抽象,而不是依赖于具体。
    3. 单一职责
      类的职责需要维持单一,流程需要但是超出自己职责的功能,去调用相关的类实现,比如OkHttpClient和ServiceMethod的各自职责与调用关系。
    4. 迪米特法则
      内部实现再复杂,对于外部调用者也只展示他需要的那些功能,例如Retrofit。
    5. 自动>人工
      动态代理的使用,可以用自动生成的模板代码,减轻人工编写配套代码的工作量,成本更低,风险更低。
    6. 利用工厂类开放扩展
      对于流程确定,但方法不能确定的,利用工厂类,对调用者开放扩展能力。
    7. 利用多个工厂类组成扩展列表
      如果1个工厂类不能实现兼得,何不设置一个工厂类列表,在多个工厂类中,看哪个工厂类能解决问题。
    8. 利用建造者模式把建造和使用分离
      这样使用者不需要关系复杂的建造过程,例如Retrofit和ServiceMethod。
    9. 利用外观模式减少对复杂子系统的操作
      虽然有复杂的子系统协同工作,调用者只需要调用最外层的Retrofit即可。
    10. 其他
      开放封闭、接口隔离、里式替换、静态代理等设计原则或设计模式都有体现也都很熟悉了,就不再啰嗦。

    最后感叹一下。

    对于网络访问的抽象与优化,实际上是个非常难的课题,在Retrofit之前,大家努力的方向基本上都是Volley/OkHttp这种围绕底层网络访问的工作。
    因为越底层的东西越容易抽象,越上升到接近业务层,就越容易在纷扰的业务层中迷失。
    Retrofit能精准地抓到Call网络工作对象这个关键点,并能通过一系列精巧的设计实现对这种类型“飘忽不定”的对象的自动化定制生产,着实令人赞叹。

    参考

    Retrofit
    你真的会用Retrofit2吗?Retrofit2完全教程
    Retrofit2 源码解析
    Retrofit 框架源码学习
    拆轮子系列:拆 Retrofit
    Android 动态代理以及利用动态代理实现 ServiceHook

     --------------------

    3.Retrofit 框架源码学习

    Retrofit,OkHttp,Okio Square 安卓平台网络层三板斧源码学习
    基于 retrofit 2.4.0-SNAPSHOT 版本 retrofit github 地址

    Retrofit 是 Square 安卓平台网络层三板斧最后一个项目,Retrofit 依赖 OkHttp 。Retrofit 让 http 网络请求更加清晰。

    使用方式

    1. 声明一个接口,并用接口描述 request

       public interface GitHubService {
           @GET("users/{user}/repos")
           Call<List<Repo>> listRepos(@Path("user") String user);
       }
      

      方法上面的注释表示 request 的接口名 ,方法的返回类型就是 http 请求的返回值,方法的参数就是 http 的请求参数。

    2. 创建一个 Retrofit 客户端

       Retrofit retrofit = new Retrofit.Builder()
       .baseUrl("https://api.github.com/")
       .build();
      
       GitHubService service = retrofit.create(GitHubService.class);
      

      Retrofit 创建的时候指定了 request 的接口地址,然后调用 retrofit.create 方法创建一个 GitHubService 实例。

    3. 发起网络请求

       Call<List<Repo>> repos = service.listRepos("octocat");
       repos.execute().body()
      

    Retrofit 创建 Service 实例

    上面的例子可以看到,retrofit.create() 方法会创建一个 GitHubService 实例,但是 GitHubService 本身是一个接口。

    为了了解 retrofit.create() 方法,我们先看下 Retrofit 的创建过程。

    创建 Retrofit 对象。

    Retrofit 和 OkHttp 一样都是使用构建者模式创建对象。先看下 Retrofit.Builder 的 build() 方法。

        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);
        }
    

    创建 Retrofit 的时候需要传递一下几个参数

    1. callFactory 用来创建一个实现了 okhttp3.Call.Factory 的对象,如果没有设置,默认为 OkHttpClient。
    2. baseUrl 网络接口的地址。
    3. converterFactories 用来把服务器返回的数据转换为对象。
    4. adapterFactories 用来发起网络请求。
    5. callbackExecutor 是一个调度器,用来接收返回的数据,在 Android 上默认是封装了 handler 的 MainThreadExecutor
    6. validateEagerly  是一个开关,如果为 true 会缓存创建的 ServiceMethod 。
    

    retrofit.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();
    
                    

    这里用到了一个公共技术点之 Java 动态代理,create 方法传入一个 Class ,这个 Class 对象就是上文的 GitHubService 的 Class 。

    GitHubService 的方法是由 InvocationHandler 代理实现的,重点看三行代码

    ……
    ServiceMethod<Object, Object> serviceMethod =(ServiceMethod<Object, Object>) loadServiceMethod(method);
    OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
    return serviceMethod.callAdapter.adapt(okHttpCall);
    
    第一行 loadServiceMethod(method)
    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 对象。

    第二行 new OkHttpCall<>(serviceMethod, args)
    OkHttpCall(ServiceMethod<T, ?> serviceMethod, @Nullable Object[] args) {
        this.serviceMethod = serviceMethod;
        this.args = args;
    }
    

    创建了一个 OkHttpCall ,serviceMethod 和 args 是 OkHttpCall 的成员函数。

    所以,

    第三行 serviceMethod.callAdapter.adapt(okHttpCall)

    这里需要明白 serviceMethod.callAdapter 是怎么来的

    1. 在 ServiceMethod.Builder.build() 中调用 createCallAdapter()
    2. 在 createCallAdapter() 中会找到 
       (CallAdapter<T, R>) retrofit.callAdapter(returnType, annotations)
    3. 在 callAdapter() 中调用 nextCallAdapter
    4. nextCallAdapter 会遍历 adapterFactories 返回一个 CallAdapter。
    

    这里再回头看下 adapterFactories Retrofit.Builder.build() 方法中

    List<CallAdapter.Factory> adapterFactories = 
        new ArrayList<>(this.adapterFactories);
    adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));
    

    而在 Retrofit.nextCallAdapter() 中

    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;
        }
    }
    

    如果没有设置 AdapterFactory 将会使用一个默认的 AdapterFactory

    CallAdapter.Factory defaultCallAdapterFactory(@Nullable Executor callbackExecutor) {
        if (callbackExecutor != null) {
            return new ExecutorCallAdapterFactory(callbackExecutor);
        }
        return DefaultCallAdapterFactory.INSTANCE;
    }
    

    所以如果我们设置了一个 RxJavaCallAdapterFactory,就会返回 RxJavaCallAdapterFactory。

     
    retrofit_01.png

    发起网络请求

    通过 retrofit.create() 我们可以知道,retrofit.create() 返回的是一个代理对象InvocationHandler ,那么在执行

    Call<List<Repo>> repos = service.listRepos("octocat");
    

    方法时,调用的实际上是 callAdapter.adapt(okHttpCall),以 DefaultCallAdapterFactory 为例

    结合 retrofit.create() 方法可以得知这里返回的是一个 OkHttpCall 对象。

    接下来使用 OkHttpCall.execute()  或者 异步执行 enqueue(Callback<T> callback)

    这两种方式都会调用 createRawCall() 创建一个 okhttp3.Call

    private okhttp3.Call createRawCall() throws IOException {
        Request request = serviceMethod.toRequest(args);
        okhttp3.Call call = serviceMethod.callFactory.newCall(request);
        if (call == null) {
            throw new NullPointerException("Call.Factory returned null.");
        }
        return call;
    }
    

    此处的  serviceMethod.callFactory 就是 retrofit.create() 中创建的 OkHttpClient()
    后面的内容都是由 Okhttp 模块接管,进行网络请求,参考okHttp 框架源码学习

    然后调用 parseResponse(call.execute())

    Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
        ResponseBody rawBody = rawResponse.body();
        ……
        省略一些 http 返回值处理逻辑
        ……
        try {
            T body = serviceMethod.toResponse(catchingBody);
            return Response.success(body, rawResponse);
        } ……
    }
    

    okHttp 请求网络的返回数据,会交给 serviceMethod.toResponse

    R toResponse(ResponseBody body) throws IOException {
        return responseConverter.convert(body);
    }
    

    在 ServiceMethod.Builder.build() 方法中可以找到 responseConverter 是通过 createResponseConverter() 方法的返回对象。

    createResponseConverter() 只是报包裹了 retrofit.responseBodyConverter(responseType, annotations) 方法。

    retrofit.responseBodyConverter() 继续跟踪下去会得知,返回的是 converterFactories 数组的第 0 个对象,也就是内置的 BuiltInConverters.responseBodyConverter() 方法返回的 BufferingResponseBodyConverter

    static final class BufferingResponseBodyConverter
            implements Converter<ResponseBody, ResponseBody> {
        static final BufferingResponseBodyConverter INSTANCE = new BufferingResponseBodyConverter();
    
        @Override
        public ResponseBody convert(ResponseBody value) throws IOException {
            try {
                // Buffer the entire body to avoid future I/O.
                return Utils.buffer(value);
            } finally {
                value.close();
            }
        }
    }
    

    再看一下 Utils.buffer(value)

    static ResponseBody buffer(final ResponseBody body) throws IOException {
        Buffer buffer = new Buffer();
        body.source().readAll(buffer);
        return ResponseBody.create(body.contentType(), body.contentLength(), buffer);
    }
    

    最终会返回一个重新封装的 Okhttp 框架的 ResponseBody 对象。

     
     

    2.Retrofit分析-经典设计模式案例

      retrofit就是对okhttp再做了一层封装。 

    本篇是retrofit番外篇。只讲retrofit中的设计模式以及我个人的理解与延伸。如果你还没看过retrofit源码,不妨先看看这篇Retrofit分析-漂亮的解耦套路

     
    retrofit00.png

    以前用的volley, async-http-lib, xUtils。这些libs基本都是上图这个workflow。向服务器请求API总共分三步。

    1. build request(API参数配置)
    2. executor(这里可以有很多变体,比如有无队列,进出顺序,线程管理)
    3. parse callback(解析数据,返回T给上层)

    如今的retrofit也是换汤不换药的。也是这三步。

    1. 通过注解配置API参数
    2. CallAdapter(你可以把它理解成executor)
    3. Converter(解析数据并转换成T)

    本来还应该有个CallFactory来切换具体的http client的。就像volley那样>=9使用HttpUrlConnection, <9使用HttpClient

    不过我想都是square出品,彼此互推也是理所当然的啊。我相信以后okhttp肯定是唯一的请求client。

    上面说CallAdapter可以理解成executor,具体是什么,我们下面讲具体设计模式时再详细讨论。

    外观模式(门面模式)

    Retrofit给我们暴露的方法和类不多。核心类就是Retrofit,我们只管配置Retrofit,然后做请求。剩下的事情就跟上层无关了,只需要等待回调。这样大大降低了系统的耦合度。对于这种写法,我们叫外观模式(门面模式)。

    几乎所有优秀的开源library都有一个门面。比如Glide.with() ImageLoader.load() Alamofire.request()。有个门面方便记忆,学习成本低,利于推广品牌。 Retrofit的门面就是retrofit.create()

    当我们自己写的代码的时候尽量也要这样来做。

    比如我们有一个独立并公用的模块,需要供其他模块来调用。比如downloadlocationsocialshare
    最好我们写一个module,将所有相关的代码都放在这个module中。这是第一步。
    第二步,为你的module提供一个漂亮的门面。比如下载的DownloadManager, 经纬度的LocationTracker, 社交分享的SocialManager。它们做为功能模块的入口,要尽量的简洁,方法命名好记易理解,类上要有完整的示例注释
    第三步,闭门造车。不管你在里面干什么,外面都是不知道的,就像薛定谔的那只猫,外层不调用它,永远不知道它是否好用。
    不过为了以后好维护,不给他人留坑,还是尽量写的工整一些。

    装饰模式

    装饰模式跟静态代理很像。

    每次一说装饰模式,就想成decorator,实际上叫wrapper更直观些。既然是wrapper,那就得有源的句柄,在构造wrapper时得把source作为参数传进来。wrapper了source,同样还wrapper其他功能。

    代理模式,Proxy Delegate,实际上Delegate也不知道自己被代理了,Proxy伪装成Delegate来执行,既然是proxy,那proxy不应该提供delegate没有的public方法,以免被认出来。

    抛开理论的描述,我们直接来看下面的代码。

     
    retrofit02.png

    你可以将ExecutorCallbackCall当作是Wrapper,而真正去执行请求的源Source是OkHttpCall。之所以要有个Wrapper类,是希望在源Source操作时去做一些额外操作。这里的操作就是线程转换,将子线程切换到主线程上去。

    简单的解释下,enqueue()方法是异步的,也就是说,当你调用OkHttpCall的enqueue方法,回调的callback是在子线程中的,如果你希望在主线程接受回调,那需要通过Handler转换到主线程上去。ExecutorCallbackCall就是用来干这个事。当然以上是原生retrofit使用的切换线程方式。如果你用rxjava,那就不会用到这个ExecutorCallbackCall而是RxJava的Call了。这里不展开。

    动态代理

    再来说动态代理。以往的动态代理和静态代理使用的场景是类似的。都想在delegate调用方法前后做一些操作。如果我的代理类有很多方法,那我得额外写很多代码,所以这时候就引入了动态代理。通过动态设置delegate,可以处理不同代理的不同方法。看不懂没关系,直接上代码:

     
    retrofit03.png

    简而言之,动态代理就是拦截调用的那个方法,在方法前后来做一些操作。Retrofit里的动态代理比较巧妙。实际上它根本就没有delegate。因为这个方法没有真正的实现。使用动态代理,只是单纯的为了拿到这个method上所有的注解。

    所有的工作都是由proxy做了。比起我们总说代理就是打log要高明多了。

    我以前自己写数据库框架时,也碰到这样的场景。一个类里有很多一对一,一对多关系。如果从db里fetch出来都去做初始化,那会非常影响性能。但如果不初始化,到使用时再去手动初始化就更麻烦了。怎么办呢?

        class A{
            private B b;
            private ArrayList<C> cs;
            
            public B getB(){
                return b;
            }
            
            public ArrayList<C> getCs(){
                return cs;
            }
        }
    

    当类A里的get方法被invoke时,我就判断,这个类有没有被初始化,如果有,那就不做任何操作。

    如果没有,那得等会,我把数据从数据库中fetch出来给你赋值后,再去invoke。这个场景可以叫懒加载,可以套用AOP面向切面编程。

    动态代理能实现这个需求吗?可以,但是支持的很糟糕。因为动态代理依赖接口实现,总不能将所有的pojo中的方法都申明到接口里吧?那真是要命了。

    所以我用了种替代方案,既然是AOP,有个面向切面的框架AspectJ。你可以通过它来切入这些get方法,先判断有没初始化,然后再返回。

     
    retrofit04.png

    差不多就是这样,没有Proxy的概念,只是在编译时,把这些切面织入进去。对于pojo而言完全是透明的。是不是很6。

    不过这里也有很多其他的性能瓶颈,比如说我在第一次调用时,要先去数据库fetch,这也是耗时操作。这个先跳过,有机会再跟大家八一八,我那数据库框架是怎么撸出来的。

    适配器模式

    适配器模式就是,已经存在的OkHttpCall,要被不同的标准,平台来调用。 设计了一个接口CallAdapter,让其他平台都是做不同的实现来转换,这样不花很大的代价就能再兼容一个平台。666。

    如果你已经看过retrofit源码,很可能被CallAdapter玩坏。这个CallAdapter不是那么好理解。先抛开代码,我们来看看适配器模式。

    Adapter简单来说,就是将一个已存在的东西转换成适合我们使用的东西。就比方说电源Adapter。出国旅游都要带转接头。比方说,RecyclerView里的Adapter是这么定义的。Adapters provide a binding from an app-specific data set to views。

    再回来看看Retrofit,为什么我们需要转接头呢。那个被转换的是谁?

    我们看看CallAdapter的定义。Adapts a {@link Call} into the type of {@code T}. 这个Call是OkHttpCall,它不能被我们直接使用吗?被转换后要去实现什么特殊的功能吗?

    我们假设下。一开始,retrofit只打算在android上使用,那就通过静态代理ExecutorCallbackCall来切换线程。但是后来发现rxjava挺好用啊,这样就不需要Handler来切换线程了嘛。想要实现,那得转换一下。将OkHttpCall转换成rxjava(Scheduler)的写法。再后来又支持了java8(CompletableFuture)甚至居然还有iOS支持。大概就是这样一个套路。当然我相信square的大神肯定一开始就考虑了这种情况,从而设计了CallAdapter

     
     
     

    策略模式?

    在retrofit里,这个适配器模式不是那么明显。而且和其他模式交错在一起,所以看起来很麻烦。比如这个CallAdapter又夹杂着策略模式(仅是个人看法)。

    你可以看看Rxjava里如何去创建adapter的,它是根据api方法声明的returnType来创建具体的CallAdapter实例的。上代码你就明白了。

     
    retrofit05.png

     
    retrofit07.png

    是不是很像根据不同的策略使用不同的算法?不同的returnType声明就是set不同的Strategy。

    总结

    来张提纲挈领的流程图,没保存的赶紧存起来。以后就能照着它自己开车了。

     
    retrofit01.png

    好,大概就将这么多啦。这些就是retrofit的解耦套路了。通过一系列的设计模式,封装思想来解耦,

    看到现在,其实retrofit就是一个负责调度的controller。先给retrofit配置好,让它能够正常工作。你给它一个方法调用,它就在内部开始运转。这个方法以前我消化过吗,没消化那就用一个ServiceMethod来解析它。解析后要用来配置一个request请求。但它自己搞不定这事啊,所以需要给它一个转接头,通过转接头来使用okhttpcall。请求是做好了,但是response它又不认识,
    所以又请来convertor来帮忙,转换完毕之后才吐出一个我们最终要的那个对象。

    如果看文章不够过瘾,可以看Stay精心录制的视频Retrofit分析-漂亮的解耦套路,看完你再也不怕看不懂retrofit了。

    而且你还可以用Stay这种分析套路来轻松看懂其他源码。

     ====================
     

    1.Retrofit分析-漂亮的解耦套路

    万万没想到Retrofit会这么火,在没看源码之前,我简单的认为是因为它跟OkHttp同出一源(Square),所以才会炒的那么热。又或者是因为它能支持RxJava,所以火上浇油,一发不可收拾。

    后来看过Retrofit源码之后,我才理解为什么它倍受关注,是因为它集诸优点于一身,并且炒鸡解耦。你能预见的特殊需求,都能非常容易的扩展。

    没有HTTP框架的日子

    我们先来看一下没有HTTP框架以前,我们是如何做请求的。

     
    retrofit00.png
    1. 首先build request参数
    2. 因为不能在主线程请求HTTP,所以你得有个Executer或者线程
    3. enqueue后,通过线程去run你的请求
    4. 得到服务器数据后,callback回调给你的上层。

    大概是以上4大步骤,在没有框架的年代,想要做一次请求,是万分痛苦的,你需要自己管理线程切换,需要自己解析读取数据,解析数据成对象,切换回主线程,回调给上层。

    这段空白的时间持续了很久。从我10年工作起到12年,因为写烦了重复的代码,所以就得想办法,把那些变化的地方封装起来,也只是简单的封装。

    好在官方出了AsyncTask,虽然坑很多,但如果再自己维护一个队列,基本不会出现问题。

    更好的地方是数据格式从xml变成json了。gson解放了双手,再也不用解析dom了。

    早些时期的HTTP框架

    后来慢慢出了不少真正的HTTP框架。Stay也借鉴了很多文章,封装了一套适用于自身业务需求的框架。

    这个时期的框架有个特点,就是拼了命去支持所有类型。比方说Volley支持直接返回Bitmap。xUtils不仅大而全,而且连多线程下载也要支持。

    在资源匮乏的时代,它们的存在有它们的道理。但如果说现在还用Volley做图片请求,还在用xUtils或Afinal里的各个模块。那就说不过去了。

    术业有专攻,百家争鸣的时期,难道不该选择最好的那一个吗?(Stay没真的用过xUtils和Afinal这种组合框架,潜意识告诉我,它们有毒,一旦某个环节出问题或者需要扩展,那代价就太大了)

    Retrofit

    tips:本文以retrofit最新版本2.0.1为例,大家也可以去github下源码,找tag为'parent-2.0.1'就可以。目前代码变动比较大。2.0.1已经使用okhttp3了,而我项目中2.0.0-beta2还是okhttp2.5。

    retrofit的最大特点就是解耦,要解耦就需要大量的设计模式,

    先来看一张Stay画的精简流程图(如有错误,请斧正),类图就不画了。

     
    retrofit01.png

    Stay在一些设计模式很明确的地方做了标记。

    外观模式,动态代理,策略模式,观察者模式。当然还有Builder模式,工厂等这些简单的我就没标。

    先简述下流程吧:

    1. 通过门面Retrofit来build一个Service Interface的proxy

       
      retrofit03.png
    2.  当你调用这个Service Interface中的某个请求方法,会被proxy拦截。

       
      retrofit02.png
    3. 通过ServiceMethod来解析invoke的那个方法 ,通过解析注解,传参,将它们封装成我们所熟悉的request。然后通过具体的返回值类型,让之前配置的工厂生成具体的CallAdapterResponseConverter,这俩我们稍后再解释。

    4. new一个OkHttpCall,这个OkHttpCall算是OkHttp的包装类,用它跟OkHttp对接,所有OkHttp需要的参数都可以看这个类。当然也还是可以扩展一个新的Call的,比如HttpUrlConnectionCall。但是有点耦合。看下图标注:


       
      retrofit031.png

      红框中显式的指明了OkHttpCall,而不是通过工厂来生成Call。所以如果你不想改源码,重新编译,那你就只能使用OkHttp了。不过这不碍事。(可能也是因为还在持续更新中,所以这块可能后面会改进的)

    5. 生成的CallAdapter有四个工厂,分别对应不同的平台,RxJava, Java8, Guava还有一个Retrofit默认的。这个CallAdapter不太好用中文解释。                      简单来说就是用来将Call转成T的一个策略。因为这里具体请求是耗时操作,所以你需要CallAdapter去管理线程。怎么管理,继续往下看。

    6. 比如RxJava会根据调用方法的返回值,如Response<'T> |Result<'T>|Observable<'T> ,生成不同的CallAdapter。实际上就是对RxJava的回调方式做封装。比如将response再拆解为success和error等。(这块还是需要在了解RxJava的基础上去理解,以后有时间可以再详细做分析)

    7. 在步骤5中,我们说CallAdapter还管理线程。比方说RxJava,我们知道,它最大的优点可以指定方法在什么线程下执行。如图

       
      retrofit04.png

      我们在子线程订阅(subscribeOn),在主线程观察(observeOn)。具体它是如何做的呢。我们看下源码。
       
      retrofit05.png

      在adapt Call时,subscribeOn了,所以就切换到子线程中了。
    8. 在adapt Call中,具体的调用了Call execute(),execute()是同步的,enqueue()是异步的。因为RxJava已经切换了线程,所以这里用同步方法execute()。

       
      retrofit06.png
    9. 接下来的具体请求,就是OkHttp的事情了,retrofit要做成的就是等待返回值。在步骤4中,我们说OkHttpCall是OkHttp的包装类,所以将OkHttp的response转换成我们要的T,也是在OkHttpCall中执行的。

    10. 当然具体的解析转换操作也不是OkHttpCall来做的,因为它也不知道数据格式是什么样的。所以它只是将response包装成retrofit标准下的response。

    11. Converter->ResponseConverter,很明显,它是数据转换器。它将response转换成我们具体想要的T。Retrofit提供了很多converter factory。比如Gson,Jackson,xml,protobuff等等。你需要什么,就配置什么工厂。在Service方法上声明泛型具体类型就可以了。

    12. 最后,通过声明的observeOn线程回调给上层。这样上层就拿到了最终结果。至于结果再如何处理,那就是上层的事了。

    再来回顾下Stay画的流程图:

     
    retrofit01.png

    这真是漫长的旅行,Stay也是debug一个个单步调试才梳理出来的流程。当然其中还有很多巧妙的解耦方式,我这里就不赘述了。大家可以看看源码分析下,当真是设计模式的经典示例。

    我想现在大家应该对retrofit有所了解了。当你再给别人介绍retrofit的时候,就别只说它的注解方式多新颖,多炫技了。注解式框架有很多的,像j2ee中一大把。所以注解算不得多精湛的技艺。   最牛逼的还是它的解耦方式,这个套路没有多年的实际架构经验是设计不出来的。

  • 相关阅读:
    uva 11404 Palindromic Subsequence(LCS回文串,最小字典序)
    paip.输入法编程---输入法ATIaN历史记录 v8b
    uva 10859 Placing Lampposts (树形dp)
    【设计模式】学习笔记8:命令模式
    Android自定义ProgressDialog
    (二十四)解释器模式详解
    Hadoop--两个简单的MapReduce程序
    BMP图像的灰度化---C++实现
    android 数组数据绑定到listview
    Win64 驱动内核编程-10.突破WIN7的PatchGuard
  • 原文地址:https://www.cnblogs.com/awkflf11/p/12541476.html
Copyright © 2020-2023  润新知