明白Retrofit原理,才能更好的应用

4,383 阅读12分钟

文章目录:

前言

写在前面

Flyabbit是一个使用Retrofit+Rxjava2+dagger2+Mvp+Material Design 快速搭建的一个app,ui是其一,更重要的是我将通过一系列的文章从项目零开始 技术选型->到封装->组件化设计->本地Maven(nexus)简单使用->自动打包(Jenkins持续集成)->单元测试->线上Bug快速修复(热修复),为大家参考部分参考,不足之处还望指出

项目 Github: github.com/chengzichen…

初衷:

之所以写[从零开始]这系列,是技术更新的太快,导致以前的一些编码和设计不在适用目前公司情况,从零开始搭建起一个更适用目前公司的框架,但是更重要的一点是在一个团队里快速上手和熟悉一个新的各类框架,以及简单原理.常言之授人予鱼不如授人与渔,在这里也是为了记录这过程,也希望能给到大家一点帮助

这篇是怎么出来的?我也不知道怎么就想着写了,当时想着写了那就当<楼外篇>吧

简介

相信大家也知道Retrofit代码解耦和设计模式的应用简直是代码范例,Retrofit+Rxjava+Okhttp这三剑客也被大家使用这么久了

Retrofit 2.0

  • A type-safe HTTP client for Android and Java

Retrofit解析

Retrofit源码的总体概要

  1. 相信大家也知道Retrofit代码解耦和设计模式的应用简直是代码范例,下面的源码分析都基于Retrofit-v2.3.0,首先用一波数据让大家简单了解Retrofit源码

    1. Retrofit的jar包只有89k(89k能干嘛)
    2. Retrofit源码的类的个数合计41个
    3. Retrofit源码的方法的个数合计513个()
    4. Retrofit代码的涉及的常见模式不完全统计有7种
  2. 这里给列出所有的类进行分类了(41个),后面都做了一一的解释分析

    • 其中有24个注解类

      • 请求方式注解
        • GET
        • POST
        • DELETE
        • PUT
        • PATCH
        • HEAD
        • OPTIONS
      • 使用方法上部注解
        • HTTP
        • FormUrlEncoded
        • Headers
        • Multipart
        • Streaming
      • 方法参数类使用的注解
        • Url
        • Body
        • Path :
        • Field
        • FieldMap :
        • Header
        • HeaderMap
        • Part
        • PartMap
        • Query
        • QueryMap
        • QueryName
    • 还剩下17个类

      • 其中4个接口(还有一个package-info忽略)
        • Call
        • CallAdapter
        • Callback
        • Converter
      • CallAdapter.Factory实现类有两个
        • DefaultCallAdapterFactory
        • ExecutorCallAdapterFactory
      • 其中Call实现类一个
        • OkHttpCall
      • Converter.Factory实现类一个
        • BuiltInConverters
      • 工具类一个
        -Utils
      • 异常类一个
        • HttpException
      • 最后剩下的6个类就是比较关键的几个类了
        • Retrofit
        • Platform
        • ParameterHandler
        • RequestBuilder
        • ServiceMethod
        • Response
  3. 涉及的设计模式有

    • 动态代理
    • 策略模式
    • 观察者模式
    • 适配器模式
    • Builder模式
    • 工厂模式
    • 装饰模式
    • 外观模式

    …(我目前了解的有这些,如有错误,请斧正)

    设计模式链接:http://www.runoob.com/design-pattern/facade-pattern.html

  4. 最后应该是源码的流程分析

    1. 构造者模式创建Retrifit对象初始化okHttpClient,ConverterFactory,CallAdapterFactory
    2. 调用retrofit.create()方法,使用代理模式拦截service调用的每个方法
    3. 获取service中Method上所有相关的注解参数保存在ServiceMethod对象中
    4. 再通过ServiceMethod对象创建OkhttpCall对象,内部调用Okhttp.call实现请求.
    5. 最后通过适配器模式,用初始化时保存到ServiceMethod中的CallAdapter对象中的adapt(OkhttpCall)方法,返回我们声明的接口的service中方法返回值.

每个类的简要分析

以下几类都是请求方式:

  • GET(SELECT):从服务器取出资源(一项或多项)
  • POST(CREATE):在服务器新建一个资源。
  • DELETE(DELETE):从服务器删除资源。
  • PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
  • PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。
  • HEAD :获取资源的元数据。
  • OPTIONS: 获取信息,关于资源的哪些属性是客户端可以改变的。

  • HTTP: 官方给出的定义:为请求使用定制的HTTP请求的注解
    示例:

 HTTP(method = "CUSTOM", path = "custom/endpoint/")
   Call<ResponseBody> customEndpoint();

//或者

 HTTP(method = "DELETE", path = "remove/", hasBody = true)
   Call<ResponseBody> deleteObject(@Body RequestBody object);
  • Url:URL将替换BaseUrl

  @GET
  Call<ResponseBody> list(@Url String url);
  
  • Body :当您想要直接控制一个post/put请求的请求体时(而不是作为请求参数或表单样式的请求体发送),在服务方法param上使用该注释。该对象将使用Retrofit改进改造实例Converter转换器,转换器进行序列化,结果将被直接设置为请求主体。

白话: 也就是说需要创建一个Converter转换器,把需要提交的数据序列化以下,再放入body中去

一般情况下GsonConverter支持了序列化为json的就够了,特殊情况只需要重写Converter就可以,当然也支持:

Gson: com.squareup.retrofit2:converter-gson 
Jackson: com.squareup.retrofit2:converter-jackson 
Moshi: com.squareup.retrofit2:converter-moshi 
Protobuf: com.squareup.retrofit2:converter-protobuf 
Wire: com.squareup.retrofit2:converter-wire 
Simple XML: com.squareup.retrofit2:converter-simplexml

示例 :

    //这里是默认使用的GsonConverter,所以@Body 传TeamInviteRequestBean对象就可以转成Json
   @Headers({"Content-Type: application/json;charset=UTF-8","Accept: application/json"})//需要添加头
@POST("group-rest/groupInvitation/push")
Flowable<ApiResponse<String>> teamInvite(@Body TeamInviteRequestBean teamInviteRequestBean);
  • Path : 在URL路径段中指定的替换。值被转换为字符串和URL编码,(与Converter类中stringConverter方法有关,如果没有找到合适的转化器,将会使用Object的toString方法)

示例:

    GET("/image/{id}")
    Call<User> example(@Path("id") int id);
  • Field :用于Post方式传递参数,需要在请求接口方法上添加@FormUrlEncoded,即以表单的方式传递参数

示例:

@FormUrlEncoded
@POST("user/edit")
Call<User> example(@Field("name") String name);
  • FieldMap : 用于Post方式传递参数,需要在请求接口方法上添加@FormUrlEncoded,即=以表单编码的请求命名的键/值对
@FormUrlEncoded
@POST("user/edit")
Call<User> things(@FieldMap Map<String, String> fields);
  • FormUrlEncoded :请求主体将使用表单URL编码提交。用这个注释做的请求将有 application/x-www-form-urlencoded 的MIME
    类型

示例同上.
- Header : 用于替换http头部

示例 :
 <pre><code>
@GET("user")
Call<User> foo(@Header("Authorization") String authorization)

@GET("user")
Call<User> foo(@Header("Accept-Language") String lang)
 </code></pre>
  • HeaderMap: 跟@Header作用一样,@Header是作为请求方法的参数传入,@HeaderMap只是使用Map的方式添加

示例:

    GET("/search")
    Call<User> foo()(@HeaderMap Map<String, String> headers);

-Headers@Header作用一样,只是使用方式不一样,@Header是作为请求方法的参数传入,@Headers是以固定方式直接添加到请求方法上

示例:

    @Headers("Cache-Control: max-age=640000")
    @GET("user")
    Call<User> foo();
    或者
    @Headers({
      "X-Foo: Bar",
      "X-Ping: Pong"
     })
    @GET("user")
    Call<User> foo();
  • Multipart :请求体是多部分的组成的。应该将部件声明为参数
    带有@Part,并允许多个,一般用于多图文一起提交的情况
    上一篇文章中也有讲过.
    示例 :
public interface IUserBiz
{
    @Multipart
    @POST("register")
    Call<User> registerUser(@Part MultipartBody.Part photo, @Part("username") RequestBody username, @Part("password") RequestBody password);
    //MultipartBody多部分组成 ,RequestBody单个
}
  • Part : 表示多部分请求的单个部分 .

示例:

 
@Multipart
@POST("/")
Call<ResponseBody> example(
@Part("description") String description,
@Part(value = "image", encoding = "8-bit") RequestBody image);

- PartMap : 表示多部件请求的名称和值部分。同@Part是样的,只是以Map键值对形式提交,作为键或值是不允许的null的,

示例:

 
@Multipart
@POST("/upload")
Call<ResponseBody> upload(
@Part("file") RequestBody file,
@PartMap Map<String, RequestBody> params);

示例 :


@GET("group/users")
Call example(@Query("user") int userId);
  • QueryMap : 与 @Query 相同都是用于Http Get请求传递参数,只是多个参数放在Map提交

示例


 @GET("/group/{id}/users")
  List groupList(@Path("id") int groupId, @QueryMap Map

流程梳理

以上就是每个类的大概说明,涉及的设计模式有:动态代理,策略模式,观察者模式,适配器模式,Builder模式,工厂模式,装饰模式…(我目前了解的有这些,如有错误,请斧正),可拓展的适配器类型有 RxjavacalllAdapterjava8callAdapter GuavacallAdapter DefultCallAdapter ExecutorCallAdapter等,接下来我们从设计模式结合流程来分析Retrofit,其实上面每个类也介绍的差不多了,这里再把流程梳理一遍,对Retrofit的认识算是更进一步了.想要收获更多只能直接看代码了.

照常理,先看看Retrofit简单用法

Retrofit retrofit = new Retrofit.Builder()
.baseUrl(“www.baidu,com”)
.client(okHttpClient)//这里如果不添加,okHttpClient,Retrofit会创建一个默认的okHttpClient
//增加返回值为String的的Converter转换器
.addConverterFactory(ScalarsConverterFactory.create())
//增加返回值为Gson的Converter转换器
.addConverterFactory(GsonConverterFactory.create())
//增加返回值为Oservable的Call适配器
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();

UserApi userApi = retrofit.create(UserApi.class);//这里采用的是Java的动态代理模式
Call call = userApi.getUser(“zhangshan”);//传入我们请求的值

  1. 首先从上面来看, 这里使用了Builder模式构建Retrofit对象时并我们传入了

    • okHttpClient(这里如果不添加,okHttpClient,Retrofit会创建一个默认的okHttpClient,代码链接@retrofit#build())

    • ConverterFactory(ScalarsConverterFactory,GsonConverterFactory,这里如果没有传的话默认的是BuiltInConverters,没错就是它因为Retrofit中只有他这么一个实现了Converter的独苗子,代码链接@Retrofit#build)

    • CallAdapterFactory(没有添加话默认为ExecutorCallAdapterFactory或者DefaultCallAdapterFactory,代码链接@Platform#defaultCallAdapterFactory)
  2. 当我们创建完毕retrofit对象后所有的事情都交给了retrofit对象,这种设计模式我们叫外观模式(外观模式 Facade Pattern:隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口),后面调用了retrofit.create()方法(主要的实现基本也就在这里了)
    public <T> T create(final Class<T> service) {
        Utils.validateServiceInterface(service);//验证是否是Interface
        if (validateEagerly) {
          eagerlyValidateMethods(service);//验证方法
        }

        //这里使用了动态代理,第一个参数是指定一个类加载器,第二个给出定义的接口的类类型,传入一个InvocationHandler回调
        return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
            new InvocationHandler() {
              private final Platform platform = Platform.get();//这里是获取环境对应的平台,Handler handler = new Handler(Looper.getMainLooper())并初始化了一个主线程的handle
                //这个invoke方法会在代理对象的方法中调用,第一个参数就是代理对象
              @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);
                }//这里是判断是否Object的方法(过滤掉)
                if (platform.isDefaultMethod(method)) {
                  return platform.invokeDefaultMethod(method, service, proxy, args);
                }/这里是判断是否Object的默方法(过滤掉)
                ServiceMethod<Object, Object> serviceMethod =
                    (ServiceMethod<Object, Object>) loadServiceMethod(method);
                OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
                return serviceMethod.callAdapter.adapt(okHttpCall);//这里使用了适配器模式,这里的callAdapter
              }
            });
      }

首先进来就验证参数service是一个Interface和验证里面的方法,方法返回的是动态代理的对象,也就是说调用service的某个请求方法时在这里都会被proxy拦截(动态代理模式)在proxy只要关心

  ServiceMethod<Object, Object> serviceMethod =
                    (ServiceMethod<Object, Object>) loadServiceMethod(method);
                OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
                return serviceMethod.callAdapter.adapt(okHttpCall);//这里使用了适配器模式,这里的callAdapter

ServiceMethod在这个类里完成请求参数的解析,组装,拼接,和保存了将一个method转化为Call的所有的信息,也就是说loadServiceMethod(method)将一个method转化为Call的所有的信息保存到ServiceMethod中(包括okhttp3.Call.Factory,Converter

            ServiceMethod loadServiceMethod(Method method) {
        ServiceMethod result;
        synchronized (serviceMethodCache) {
          result = serviceMethodCache.get(method);
          if (result == null) {//判断是否内存中有存在
            result = new ServiceMethod.Builder(this, method).build();
            serviceMethodCache.put(method, result);
          }
        }
        return result;
      }

先判断内存中时是否存在,没有的话通过 new ServiceMethod.Builder()构建,如果想看是怎么构建的一个ServiceMethod对象请看ServiceMethod#build()

接下来通过new OkHttpCall<>(serviceMethod, args),通过ServiceMethod对象创建OkHttpCall对象,而OkHttpCall是封装了okhttp的实现类,然后通过okhttp3.Callexecute或者enqueue做实际请求,这个过程也是比较简单的

     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.callAdapter.adapt(okHttpCall);//这里使用了适配器模式,这里的CallAdapter是在构建Retrofit的时候传入CallAdapterFactory(工厂模式)生产出来的,然后调用adapt(okHttpCall)会将OkHttpCall返回的结果转换成我们声明的接口的返回值.

但是如果没有传入CallAdapterFactory就默认使用的是ExecutorCallAdapterFactory(工厂模式)生产的CallAdapter对象CallAdapter中包括了一个ExecutorCallbackCall.而ExecutorCallbackCall是在不改变其结构OkhttpCall的情况下用ExecutorCallbackCall扩展OkhttpCall切换到主线程的功能(使用handle).最终返回Response对象或者对应的结果回调给Callback,也就是示例中的Call<String> call = userApi.getUser("zhangshan");

现在目前用的比较多的RxJava2CallAdapterFactory(我这里是用的是Rxjava2),也可以看看是怎么实现的,有没有什么可以值得学习的地方

RxJava2CallAdapterFactory也是实现了CallAdapter的实现类,直接上代码
 @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;
  }

一定有疑问怎么返回这么多的类型的ObservableFlowable,这里和Rxjava1.0,RxJavaCallAdapterFactory是不一样的,首先判断是否是异步的返回CallEnqueueObservable(最终调用的是Okhttp.callenqueue的方法,然后回调Observable,onNext()或者onError())同步的CallExecuteObservable(最终调用的是Okhttp.callexecute的方法,然后回调Observable,onNext()或者onError()),再根据不同情况返回ResultObservable,BodyObservable(这两个类是对responseObservable的拓展了一些功能),当然也适配了Flowable支持了背压.(当然这也是策略模式)
RxJavaCallAdapterFactor,用的地方不同.然后我们拿到我们声明的接口的(Observab或者Flowable)返回值,类似Flowable<String> call = userApi.getUser("zhangshan");.

最后看看下面的图,结合下上文在回味一下,感受下Retrofit代码解耦和设计模式的应用,简直是代码范例,心里真是美滋滋..

image

图片来自:Retrofit分析-漂亮的解耦套路 by stay4it

关于我

参考

文件下载

Retrofit原理解析