我了解的Retrofit2

一.概述

我想作为一个Android开发如果没有听过Retrofit网络请求框架,那么他是与时代脱节的,你随便在gayhub上找一个开源的Android项目都能发现Retrofit的身影。在介绍Retrofit之前,大家还是需要知道okhttp的基本使用方式的。
okhttp使用很简单,主要分为同步请求和异步请求:

OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(url).build();
  • 同步请求,即等待请求结果返回,然后对结果进行解析,主要流程:
Response response = client.newCall(request).execute();

然后对返回的结果Request进行解析。

  • 异步请求,即回调式请求,结果返回后进行回调,不会阻塞当前线程的执行。主要流程:
Call call = client.newCall(request);
call.enqueue(new Callback() {
    @Override
    public void onResponse(Response response) throws IOException {
        //get result from response.
    }
    @Override
    public void onFailure(Request arg0, IOException arg1) {
    
    } 
});

可以看到,两种操作方式,都是对返回的Response进行包括io操作、结果转换等解析操作。你可以把okhttp就当成类似HttpURLConnection一样的Api,他们的功能是一样的都是负责网络请求。如果想对Okhttp进一步了解的可以参考:Android OkHttp完全解析 是时候来了解OkHttp了

二.Retrofit的使用

既然都会使用okhttp了,那么使用Retrofit就很容易了。网上教程一大堆,这边主要罗列基本的3个操作:

  • 1.创建接口类,其中每个方法都是一个对应的网络请求。
public interface API {
    @POST("/load")
    Call<Rep> load();
    
    @Post("/test")
    Call<Rep> down();
}
  • 2.创建Retrofit对象,方式和Okhttp一样是通过Builder模式进行配置生成对象,其中baseUrl表示表示完整的url前半段,和API中的注解合并成最终的url。
Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("http://com.seu.test/")
    .build();
  • 3.创建异步请求
Call<Rep> call = retrofit.create(API.class).load();
call.enquque(new Callback<Rep> {
    @Override
    public void onResponse(Call<Rep> call, Response<Rep> response) {
        //get result from response
    }
 
    @Override
    public void onFailure(Call<Rep> call, Throwable t) {
 
    }
});

是不是看去和Okhttp的异步请求一模一样。你猜对了,Retrofit就是对Okhttp的网络请求的封装。如果你现在还不了解Retrofit的用途,那我还能怎么办呢。Retrofit2.0使用详解,我也只能帮到这了。

三.Retrofit源码分析

注解Annotation

通过查看源码可以知道在retrofit2.http目录下面都是Java注解类,其中包括熟悉的GETPOST等。本文结合GET进行入门式介绍。
Java中包括4种元注解(用于自定义注解的注解):

  • Documented,如果定义了该注解,则通过javadoc后,该注解的信息可以在docs中显示(对于开发来说,没啥用,不行就在自定义注解中直接加上就行)。
  • Inherited 定义该注解是否可以在子类中被继承。一般该注解的使用也不会很多,至今没用过继承的注解。
  • Retention 定义了该注解的存活周期。这个是我见过用处比较大的注解了,但是在我解释了他的用处以后,你也会觉得so easy。Retention包括三部分:
Rentention 的三个enum属性
SOURCE: 说明该自定义注解,只存在与XX.java中,在编译后的XX.class都是找不到的。比如@Override,可以通过jd-gui来查看XX.class文件,确实找不到该注解,该注解是在虚拟机层面进行解析的,实际自定义注解中大概也不会用到该类别。
CLASS : 存在于XX.java和XX.class文件中,但是在运行时Java虚拟机会忽略该注释,只有在编译阶段会读取到该注解,在运行时通过反射是获取不到该注解的,可以查看Butter Knife,他就是编译时注解。
RUNTIME: 和class属性一样,都存在于源文件以及编译生成的class文件中,不同的是在运行时可以得到该注解,即可以运行时可以通过反射得到。
  • Target表示该注解可以用在哪个地方,比如:用于属性字段、用于方法声明、用于包声明、用于接口声明等等,详细的可以参考ElementType。

在了解了Annotation的元注解以后,我们来解释GET

//还不是小儿科,直接忽略
@Documented
//说明该注解是使用在方法上的,且只能用在方法上
@Target(METHOD)
//说明该注解是运行时注解,可以通过反射得到
@Retention(RUNTIME)
public @interface GET {
  //需要有一个属性
  String value() default "";
}

怎么样,一个自定义的注解就可以完成了,妈妈再也不用担心我看不懂注解咯。其实所有的自定义注解都是照猫画虎,没啥困难的。还想深入全名学习注解的可以参考:Java注解(Annotation),保证药到病除。到现在为止,retrofit2.http下面所有的类基本都可以知道它们的真实含义了吧。

动态代理

在将ServiceMethod之前还是需要稍微介绍下Java的动态代理技术:动态代理是jdk封装好的比较好用的类代理生成方式,Proxy.newProxyInstance有三个参数:

  • ClassLoader,这个比较通用,直接用类本身的Classloader就可以。
  • Class<?>[] interfaces,表示该生成代理类要实现的接口功能。
  • InvocationHandler h,接口功能的真正实现是h。

Proxy.newProxyInstance是通过反射的方式生成代理类,通过源码分析可以看到,Class对象是通过ProxyGenerator生成的字节码,不信的话你可以通过ProxyGenerator生成的byte[]保存到xx.class文件中,看看是不是把所有的功能都让h来完成的,可以参考:JDK动态代理的实现及原理,保证让你对动态代理有比较深刻的理解。
看到了没有,说话算话,真的很简单的介绍了动态代理。

开刀Retrofit.java

Retrofit是通过Builder模式生成的对象,所以直接对Retrofit.Builder类进行解析,这里只罗列来了属性和build(),属性设置方法略(没啥特别的,都是一堆类似set方法):

public static final class Builder {
    //Retrofit运行的平台:具体实现类比较简单,其中包括了Android平台、IOS平台、还有java8平台。这里你就直接理解为Android平台吧。
    private Platform platform;
    //该接口直接实现类为OkHttpClient,相信我。
    private okhttp3.Call.Factory callFactory;
    private HttpUrl baseUrl;
    private List<Converter.Factory> converterFactories = new ArrayList<>();
    private List<CallAdapter.Factory> adapterFactories = new ArrayList<>();
    //网络请求返回后是不在主线程的,那么是不是需要有一个主线程的executor来执行ui操作,猜对了就是他。
    private Executor callbackExecutor;
    //我暂时理解为懒加载的意思,如果为true则把所有的methond都进行反射,如果为false则用到哪个method再去反射。
    private boolean validateEagerly;
}
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();
    }

    // 这里会默认添加一个适配器,这才能让API中的load方法返回Call<Rep>
    List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories);
    //即加入ExecutorCallAdapterFactory
    adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));

    // 没有给默认解析器,所以要自己定义一个解析器
    List<Converter.Factory> converterFactories = new ArrayList<>(this.converterFactories);

    return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories,
          callbackExecutor, validateEagerly);
    }

adapterFactories:这个东西举个栗子吧,由于adapterFactories中会默认添加ExecutorCallAdapterFactory,所以我们看到API中的方法返回Call是默认可以解析的。如果接触过RxJava的同学应该知道,为了让API中的方法能够返回Observable,则需要添加一个自定义Retrofit Adapters,而RxJavaCallAdapterFactory就是能够让API返回Observable的具体实现。所以你想自定义API中函数的返回类型,就需要添加自定义Adapter,而常见的Adapter在GayHub上都给有给出。
converterFactories:还是很好理解的,一般的网络请求后返回的body都是String形式的,那么怎么把String形式转换成具体的entity对象呢?就是他来实现的。你可以用Gson、jackson来解析返回的数据,只要你配置了真确的converter就可以啦。retrofit-converters,而默认中converterFactories为空的没有具体的实现,所以在初始化Retrofit的时候需要添加例如:addConverterFactory(GsonConverterFactory.create())的converter。
这种可插拔式的框架,可以灵活的适配项目之前已经使用的一些框架。如果retrofit只支持fastjson解析,而之前项目都用Gson解析的,那你会不会把之前的Gson都用fastjson替换(不然有洁癖的我是无法容忍的)?这就是retrofit可插拔的优点。

现在创建API的实例,通过Retrofit.create():

public <T> T create(final Class<T> service) {
    //确保API是一个接口,且该接口没有继承其他接口
    Utils.validateServiceInterface(service);
    //看看是不是懒加载,如果true则会把API里面的所有method都进行一次性缓存。
    if (validateEagerly) {
      eagerlyValidateMethods(service);
    }
    //动态加载
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
        new InvocationHandler() {
          private final Platform platform = Platform.get();

          @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);
            }
            //android平台返回的是false,所以不管它
            if (platform.isDefaultMethod(method)) {
              return platform.invokeDefaultMethod(method, service, proxy, args);
            }
            //API中每个方法都会生成对应的一个ServiceMethod,解析该方法对应的Annotation信息。
            ServiceMethod serviceMethod = loadServiceMethod(method);
            //他就是okhttp3.Call的一个包装类,真正的网络请求在这里面进行,且是通过okhttp3.Call进行的。
            OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
            //返回该API中method的返回类型,例如Call<Rep>
            return serviceMethod.callAdapter.adapt(okHttpCall);
          }
        });
  }

adapt()默认返回的是ExecutorCallbackCall,他就是Call子类,所以你也可以直接把它理解为API函数的返回类型,例如load的返回值Call<Rep>,没有任何问题。
ServiceMethod咋一眼看去不知道什么鬼东西,其实他的存在就是:把API中的每个method方法解析成一个ServiceMethod对象,解析的内容就是该method对应的注解信息,把这些信息存储在对应的ServiceMethod对象中进行缓存等待网络请求使用。

ServiceMethod loadServiceMethod(Method method) {
    ServiceMethod result;
    synchronized (serviceMethodCache) {
      result = serviceMethodCache.get(method);
      if (result == null) {
        result = new ServiceMethod.Builder(this, method).build();
        //缓存,不多说,总不可能在多次调用API方法的时候,每次都使用反射进行解析吧。
        serviceMethodCache.put(method, result);
      }
    }
    return result;
  }

下面就要开始解析ServiceMethod.Builder()方法,按照惯例只写属性和builder方法。这样,一个method中所有信息都被保存在ServiceMethod当中,后续的网络请求只需要获取ServiceMethod当中的数据即可。

static final class Builder<T> {
    //需要介绍吗,就是上面loadServiceMethod传入的this
    final Retrofit retrofit;
    //动过动态代理得到的method,后续要详细的解析该method
    final Method method;
    //作用于该method的所有注解,比如API方法中的GET
    final Annotation[] methodAnnotations;
    //method方法中参数对应的注解,一个参数可以对应多个注解
    final Annotation[][] parameterAnnotationsArray;
    //method中参数对应的真实类型列表
    final Type[] parameterTypes;
    //method返回类型,就是你看到的Call<Rep>。
    Type responseType;
    //这一堆boolean自行脑补吧
    boolean gotField;
    boolean gotPart;
    boolean gotBody;
    boolean gotPath;
    boolean gotQuery;
    boolean gotUrl;
    String httpMethod;
    boolean hasBody;
    boolean isFormEncoded;
    boolean isMultipart;
    String relativeUrl;
    Headers headers;
    MediaType contentType;
    Set<String> relativeUrlParamNames;
    ParameterHandler<?>[] parameterHandlers;
    Converter<ResponseBody, T> responseConverter;
    CallAdapter<?> callAdapter;
    
    public ServiceMethod build() {
      //获取CallAdapter,可以通过该函数知道,最终会调用retrofit.callAdapter(returnType, annotations);
      callAdapter = createCallAdapter();
      responseType = callAdapter.responseType();
      //最终调用retrofit.responseBodyConverter(responseType, annotations);
      responseConverter = createResponseConverter();
      //猜都不用猜,肯定是解析retrofit2.http下面的注解,解析完之后上面的一堆boolean值差不多都赋值好咯。
      for (Annotation annotation : methodAnnotations) {
        parseMethodAnnotation(annotation);
      }

      int parameterCount = parameterAnnotationsArray.length;
      parameterHandlers = new ParameterHandler<?>[parameterCount];
      for (int p = 0; p < parameterCount; p++) {
        Type parameterType = parameterTypes[p];
        if (Utils.hasUnresolvableType(parameterType)) {
          throw parameterError(p, "Parameter type must not include a type variable or wildcard: %s",
              parameterType);
        }
        //解析对应参数和该参数的注解列表。
        Annotation[] parameterAnnotations = parameterAnnotationsArray[p];
        
        parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
      }
      return new ServiceMethod<>(this);
    }
}

通过对上述对Method的解析,得到了一一对应的ServiceMethod对象,最后通过serviceMethod.callAdapter.adapt(okHttpCall)返回一个API中函数的返回类型。由于系统默认了使用ExecutorCallAdapterFactory,可以对比DefaultCallAdapterFactory,他们的作用是一样的,只是网络请求返回的一个回调在主线程,一个回调在子线程:

final class DefaultCallAdapterFactory extends CallAdapter.Factory {
  static final CallAdapter.Factory INSTANCE = new DefaultCallAdapterFactory();

  @Override
  public CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
    if (getRawType(returnType) != Call.class) {
      return null;
    }
    //这里的returnType是API中函数的返回类型,比如Call<Rep>
    //responseType则是Rep,就是returnType对应的泛型类型。
    final Type responseType = Utils.getCallResponseType(returnType);
    return new CallAdapter<Call<?>>() {
      @Override public Type responseType() {
        return responseType;
      }

      @Override public <R> Call<R> adapt(Call<R> call) {
        //看看,直接把call返回了,这就是为什么可以处理API中函数是Call返回类型的原因。
        return call;
      }
    };
  }
}

好了,到这里Call<Rep> call = retrofit.create(API.class).load()我们就得到了Call,接下去就是enqueue了,这个过程有没有很像Okhttp的enqueue,知识点有没有!前面我们已经知道了这里对应的Call就是OkhttpCall(不清楚的直接在动态代理里面找),所以要enqueue就去找OkhttpCall:

@Override public void enqueue(final Callback<T> callback) {
    //这是啥东西,哈哈,就是要去真真网络请求的Call,不是retrofit的call。
    //如果还是混淆了这两个call,那么只能再多看看Okhttp是怎么网络请求的。
    okhttp3.Call call;
    Throwable failure;

    //禁止同一个call入列两次
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already executed.");
      executed = true;

      call = rawCall;
      failure = creationFailure;
      if (call == null && failure == null) {
        try {
          //从该函数的命名就可以看到他的意思,创建真实的Call,即Okhttp的Call
          call = rawCall = createRawCall();
        } catch (Throwable t) {
          failure = creationFailure = t;
        }
      }
    }

    if (failure != null) {
      callback.onFailure(this, failure);
      return;
    }

    if (canceled) {
      call.cancel();
    }
    //真真的网络请求来了,这一块网络请求就是Okhttp真真的网络请求,看到这你要还不懂的话,怪我咯。
    call.enqueue(new okhttp3.Callback() {
      @Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse)
          throws IOException {
        Response<T> response;
        try {
        //通过真实返回的rawResponse,解析得到retrofit的response
          response = parseResponse(rawResponse);
        } catch (Throwable e) {
          callFailure(e);
          return;
        }
        callSuccess(response);
      }

      @Override public void onFailure(okhttp3.Call call, IOException e) {
        try {
          callback.onFailure(OkHttpCall.this, e);
        } catch (Throwable t) {
          t.printStackTrace();
        }
      }

      private void callFailure(Throwable e) {
        try {
          callback.onFailure(OkHttpCall.this, e);
        } catch (Throwable t) {
          t.printStackTrace();
        }
      }
      //把成功的结果传递给回调,其中response中包好了解析好的对象。    
      private void callSuccess(Response<T> response) {
        try {
          callback.onResponse(OkHttpCall.this, response);
        } catch (Throwable t) {
          t.printStackTrace();
        }
      }
    });
  }
  //把okhttp的返回rawResponse转换成retrofit的Response,并把解析内容放入到Response当中。
  Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
    ResponseBody rawBody = rawResponse.body();

    //该rawResponse只解析返回内容的头部,可以了解NoContentResponseBody读取body抛异常了
    rawResponse = rawResponse.newBuilder()
        .body(new NoContentResponseBody(rawBody.contentType(), rawBody.contentLength()))
        .build();

    int code = rawResponse.code();
    if (code < 200 || code >= 300) {
      try {
        // Buffer the entire body to avoid future I/O.
        ResponseBody bufferedBody = Utils.buffer(rawBody);
        return Response.error(bufferedBody, rawResponse);
      } finally {
        rawBody.close();
      }
    }

    if (code == 204 || code == 205) {
      return Response.success(null, rawResponse);
    }
    //异常捕获response?不了解具体的用处。。。
    ExceptionCatchingRequestBody catchingBody = new ExceptionCatchingRequestBody(rawBody);
    try {
      //这个地方厉害了,就是通过convert来转换成具体的对象,比如用GsonConverterFactory.create()来把String转换成具体对象。自行看代码,其实就一句话。
      T body = serviceMethod.toResponse(catchingBody);
      return Response.success(body, rawResponse);
    } catch (RuntimeException e) {
      catchingBody.throwIfCaught();
      throw e;
    }
  }

到这里,我们就把解析好的T body返回给回调接口Callback里面去了,现在我们就可以在onResponse里面通过Response.body()获取到解析以后的真正对象咯。
好了基本上能说的都说了,应该能够差不多了解了Retrofit的使用流程了。

Utils

Retrofit中有一个非常重要的工具类Utils,里面包含了所有的关于Type的反射方法,比如通过Call<Rep>得到Rep的Type等等。
Type是和泛型相关的接口,具体分为4种类型:

  • ParameterizedType:具体的泛型类型,比如ArrayList<String>中,具体泛型就是String。
  • TypeVariable:泛型变量,在泛型类里面使用该泛型变量,这时候该变量就是TypeVariable。
  • GenericArrayType:泛型数组,String[],懂?
  • WildcardType:通配符泛型,比如ArrayList<? extends Number>。

不想讲了,自己参考:Java中的Type详解

四.总结

本文通过Okhttp的网络请求为主线,然后把Retrofit是如何包装Okhttp的过程进行了分析,到这里你应该有能力去修改Retrofit去适应你自己app里面的业务功能了吧。
最后再把参考的几篇文章进行罗列下,欢迎提出问题,我说鸡蛋你说要:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容