专栏首页终身开发者源码分析Retrofit请求流程

源码分析Retrofit请求流程

Retrofitsquare 公司的另一款广泛流行的网络请求框架。前面的一篇文章《源码分析OKHttp执行过程》已经对 OkHttp 网络请求框架有一个大概的了解。今天同样地对 Retrofit 的源码进行走读,对其底层的实现逻辑做到心中有数。

0x00 基本用法

Retrofit 的项目地址为:https://github.com/square/retrofit

打开项目目录下的 samples 文件夹,从这里可以浏览 Retrofit 项目的使用范例。

在本文中打开 SimpleService.java 这个类作为源码走读的入口。这个类很简单,展示了 Retrofit 的基本用法

public final class SimpleService {
  //定义接口请求地址
  public static final String API_URL = "https://api.github.com";
  //定义接口返回数据的实体类
  public static class Contributor {
    public final String login;
    public final int contributions;

    public Contributor(String login, int contributions) {
      this.login = login;
      this.contributions = contributions;
    }
  }
  //定义网络请求接口
  public interface GitHub {
    //这个是请求github项目代码贡献者列表的接口
    //使用@GET注解指定GET请求,并指定接口请求路径,使用大括号{}定义的参数,是形参,retrofit会把方法中的
    //@Path 传入到请求路径中
    @GET("/repos/{owner}/{repo}/contributors")
    Call<List<Contributor>> contributors(
        @Path("owner") String owner,
        @Path("repo") String repo);
  }

  public static void main(String... args) throws IOException {
    // 创建一个retrofit,并且指定了接口的baseUrl
    // 然后设置了一个gson转换器,用于将接口请求下来的json字符串转换为Contributor实体类。
    Retrofit retrofit = new Retrofit.Builder()
        .baseUrl(API_URL)
        .addConverterFactory(GsonConverterFactory.create())
        .build();

    // 这里是魔法所在,retrofit将程序猿定义的接口变成“实现类”
    GitHub github = retrofit.create(GitHub.class);

    //通过retrofit这个“实现类”执行contributors方法
    Call<List<Contributor>> call = github.contributors("square", "retrofit");

    // 执行Call类中的execute方法,这是一个同步方法
    // 当然跟okhttp一样,异步方法是enqueue,这个下文会提到
    List<Contributor> contributors = call.execute().body();
    for (Contributor contributor : contributors) {
      System.out.println(contributor.login + " (" + contributor.contributions + ")");
    }
  }
}

通过上面代码的阅读,知道 retrofit 使用流程

  1. 定义 API
  2. 构造接口数据实体类
  3. 构造 retrofit 对象,指定 baseUrl和数据转换器(即接口数据解析器,如对 jsonxmlprotobuf等数据类型的解析)
  4. 通过 retrofit 将程序猿定义的 API 接口变成"实现类"
  5. 执行“实现类”的方法
  6. 执行网络请求,获取接口请求数据

这个流程关键点是4、5、6,下文将详细对这几个步骤的源码进行阅读。

在继续下文之前,我们先看看这个 SimpleService的执行结果,它打印了 retrofit 这个项目的代码贡献者

JakeWharton (928)
swankjesse (240)
pforhan (48)
eburke (36)
dnkoutso (26)
NightlyNexus (26)
edenman (24)
loganj (17)
Noel-96 (16)
rcdickerson (14)
rjrjr (13)
kryali (9)
adriancole (9)
holmes (7)
swanson (7)
JayNewstrom (6)
crazybob (6)
Jawnnypoo (6)
danrice-square (5)
vanniktech (5)
Turbo87 (5)
naturalwarren (5)
guptasourabh04 (4)
artem-zinnatullin (3)
codebutler (3)
icastell (3)
jjNford (3)
f2prateek (3)
PromanSEW (3)
koalahamlet (3)

0x01 构造过程

从上文的源码阅读中,可以看出程序猿只是定义了一个接口,但是现在实现接口的工作是由 retrofit 来实现的

GitHub github = retrofit.create(GitHub.class);

Call<List<Contributor>> call = github.contributors("square", "retrofit");
create

打开 retrofit.create方法

public <T> T create(final Class<T> service) {
    //对接口进行校验
    Utils.validateServiceInterface(service);
    if (validateEagerly) {
      eagerlyValidateMethods(service);
    }
    //通过Proxy创建了一个代理
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
        new InvocationHandler() {
          private final Platform platform = Platform.get();
          private final Object[] emptyArgs = new Object[0];

          @Override public Object invoke(Object proxy, Method method, @Nullable 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);
            }
            //判断是否为默认方法,Java8中接口也可以有默认方法,所以这里有这个判断
            if (platform.isDefaultMethod(method)) {
              return platform.invokeDefaultMethod(method, service, proxy, args);
            }
            //关键点
            return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
          }
        });
  }

这个方法很短,关键是通过 Proxy 创建了一个 Github 接口的代理类并返回该代理。

newProxyInstance 方法需要3个参数: ClassLoaderClass<?>数组、 InvocationHandler 回调。

这个 InvocationHandler 非常关键,当执行接口 Githubcontributors方法时,会委托给 InvocationHandlerinvoke 方法来执行。即 Github将接口代理给了 Proxy来执行了。

InvocationHandler

接着看 InvocationHandler 接口的实现。

invoke 方法中有三个参数,其中 proxy 就是代理对象,而 method 就是程序猿定义的那个网络请求接口,顾名思义 args 就是方法的参数。

此方法最终是调用了

loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
loadServiceMethod

打开 loadServiceMethod方法

ServiceMethod<?> loadServiceMethod(Method method) {
  // 判断是否有缓存
  ServiceMethod<?> result = serviceMethodCache.get(method);
  if (result != null) return result;
  //同步处理
  synchronized (serviceMethodCache) {
    result = serviceMethodCache.get(method);
    if (result == null) {
      //没有获取到缓存则使用`ServiceMethod`方法来创建
      result = ServiceMethod.parseAnnotations(this, method);
      //最后缓存起来
      serviceMethodCache.put(method, result);
    }
  }
  return result;
}

这个方法就是通过 method 来获取一个 ServiceMethod 对象。

ServiceMethod

打开 ServiceMethod 发现它是一个抽象类,有一个静态方法 parseAnnotations 和一个抽象方法 invoke

abstract class ServiceMethod<T> {
  static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
    //对注解进行解析
    RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);
    //获取方法的返回类型
    Type returnType = method.getGenericReturnType();
    //对返回类型进行校验
    if (Utils.hasUnresolvableType(returnType)) {
      throw methodError(method,
          "Method return type must not include a type variable or wildcard: %s", returnType);
    }
    if (returnType == void.class) {
      throw methodError(method, "Service methods cannot return void.");
    }
    //最终使用到HttpServiceMethod类
    return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
  }

  abstract T invoke(Object[] args);
}

parseAnnotations 方法就是对程序猿定义的接口中使用的注解进行解析。

最后是使用了 HttpServiceMethod.parseAnnotations方法

HttpServiceMethod
/** Adapts an invocation of an interface method into an HTTP call. */
final class HttpServiceMethod<ResponseT, ReturnT> extends ServiceMethod<ReturnT> {
  static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
      Retrofit retrofit, Method method, RequestFactory requestFactory) {
    CallAdapter<ResponseT, ReturnT> callAdapter = createCallAdapter(retrofit, method);
    //...省略部分代码
    Converter<ResponseBody, ResponseT> responseConverter =
        createResponseConverter(retrofit, method, responseType);

    okhttp3.Call.Factory callFactory = retrofit.callFactory;
    return new HttpServiceMethod<>(requestFactory, callFactory, callAdapter, responseConverter);
  }

  //...省略部分代码

  @Override ReturnT invoke(Object[] args) {
    return callAdapter.adapt(
        new OkHttpCall<>(requestFactory, args, callFactory, responseConverter));
  }
}

HttpServiceMethodServiceMethod 的子类。而在 parseAnnotations 方法中构造了 HttpServiceMethod实例并返回。

因此, loadServiceMethod方法返回的是 HttpServiceMehod对象

这样下面代码的执行实际上是执行了 HttpServiceMehodinvoke 方法。

loadServiceMethod(method).invoke(args != null ? args : emptyArgs);

再次翻看上文中 HttpServiceMethod

@Override ReturnT invoke(Object[] args) {
    return callAdapter.adapt(
        new OkHttpCall<>(requestFactory, args, callFactory, responseConverter));
  }

invoke 方法里有执行了 callAdapter.adapt方法,参数为 OkHttpCall,这个类实际上就是对 okhttp网络请求的封装,这里也可以看出 retrofit内部是使用了 okhttp来执行网络请求的

CallAdapter
public interface CallAdapter<R, T> {
  //..省略部分代码
  T adapt(Call<R> call);
  //CallAdapter抽象工厂类
  abstract class Factory {
    //返回CallAdapter实例
    public abstract @Nullable CallAdapter<?, ?> get(Type returnType, Annotation[] annotations,
        Retrofit retrofit);

    //..省略部分代码
  }
}

这是一个接口,内部有一个 Factory抽象工厂类,用于获取 CallAdapter对象。

CallAdapter 有很多子类,那 callAdapter.adapt 方法执行的是哪个具体类的方法呢?实际上,从调试代码中可以发现是调用 DefaultCallFactory中的内部实现类

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

  @Override public @Nullable CallAdapter<?, ?> get(
      Type returnType, Annotation[] annotations, Retrofit retrofit) {
    if (getRawType(returnType) != Call.class) {
      return null;
    }

    final Type responseType = Utils.getCallResponseType(returnType);
    //返回一个CallAapter实例
    return new CallAdapter<Object, Call<?>>() {
      @Override public Type responseType() {
        return responseType;
      }

      @Override public Call<Object> adapt(Call<Object> call) {
        //将参数返回,而这个参数就是OKHttpCall的实例
        return call;
      }
    };
  }
}

可以发现,在 adapt方法中就是将参数 call返回。

所以下面代码返回的是 OkHttpCall对象。

loadServiceMethod(method).invoke(args != null ? args : emptyArgs);

综上

//创建了Github接口的代理类
GitHub github = retrofit.create(GitHub.class);
//执行接口的方法,其实就是调用了代理类的方法,并最终返回了一个OKhttpCall对象
//而这个对象就是对Okhttp的封装
Call<List<Contributor>> call = github.contributors("square", "retrofit");

0x02 执行结果

上文中获取到 OKhttpCall对象,它只是把接口请求过程进行了封装,并没有真正的获取到接口数据。要获取到接口数据还需要调用 OkHttpCall.execute方法

List<Contributor> contributors = call.execute().body();
Call.execute 或 Call.enqueue

这里的请求过程与前文中《源码分析OKHttp执行过程》介绍的是类似的。接一下

打开 OkHttpCall.execute方法

@Override public Response<T> execute() throws IOException {
    okhttp3.Call call;

    synchronized (this) {
      if (executed) throw new IllegalStateException("Already executed.");
      executed = true;

      if (creationFailure != null) {
        if (creationFailure instanceof IOException) {
          throw (IOException) creationFailure;
        } else if (creationFailure instanceof RuntimeException) {
          throw (RuntimeException) creationFailure;
        } else {
          throw (Error) creationFailure;
        }
      }

      call = rawCall;
      if (call == null) {
        try {
          call = rawCall = createRawCall();
        } catch (IOException | RuntimeException | Error e) {
          throwIfFatal(e); //  Do not assign a fatal error to creationFailure.
          creationFailure = e;
          throw e;
        }
      }
    }

    if (canceled) {
      call.cancel();
    }

    return parseResponse(call.execute());
  }

这里的执行逻辑也很简单

  • 使用 synchronized进行同步操作
  • 进行异常处理
  • 调用 createRawCall 创建 okhttp3.Call 对象
  • 执行 okhttpCall.execute方法,并解析 response后返回请求结果

同样地,异步请求操作也是类似的

打开 OkHttpCall.enqueue方法

@Override public void enqueue(final Callback<T> callback) {
    checkNotNull(callback, "callback == null");

    okhttp3.Call call;
    Throwable failure;

    synchronized (this) {
      if (executed) throw new IllegalStateException("Already executed.");
      executed = true;

      call = rawCall;
      failure = creationFailure;
      if (call == null && failure == null) {
        try {
          //创建okhttp网络请求
          call = rawCall = createRawCall();
        } catch (Throwable t) {
          throwIfFatal(t);
          failure = creationFailure = t;
        }
      }
    }

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

    if (canceled) {
      call.cancel();
    }
    //最终是执行了OkHttp中的call.enqueue方法
    //并回调相应的接口
    call.enqueue(new okhttp3.Callback() {
      @Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
        Response<T> response;
        try {
          response = parseResponse(rawResponse);
        } catch (Throwable e) {
          throwIfFatal(e);
          callFailure(e);
          return;
        }

        try {
          callback.onResponse(OkHttpCall.this, response);
        } catch (Throwable t) {
          t.printStackTrace();
        }
      }

      @Override public void onFailure(okhttp3.Call call, IOException e) {
        callFailure(e);
      }

      private void callFailure(Throwable e) {
        try {
          callback.onFailure(OkHttpCall.this, e);
        } catch (Throwable t) {
          t.printStackTrace();
        }
      }
    });
  }

这个方法其实最终都是执行了 okhttp的相应方法。

0x03 总结

Retrofit 其实一种更加高级的网络应用框架,通过代理模式简化了接口的定义,无需提供接口的具体实现就可以完成网络接口请求的执行。它的底层实际上是封装了 okhttp 的执行过程,也把对网络的操作进行了封装,而对于程序猿来说只需要关注业务逻辑,对网络请求的具体实现不必关心。

例如在本文开头的实例中我们只需要定义接口,定义实体类,其他工作都交给了 Retrofit ,接下来就是 Magic

本文分享自微信公众号 - 终身开发者(AngryCode),作者:hylinux1024

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-11-04

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • App 组件化/模块化之路——使用SDK的思路进行模块化设计接口

    在不久之前分享一篇《App 组件化/模块化之路——如何封装网络请求框架》文章介绍了我在项目中封装网络请求框架的思路。开发一个 App 会涉及到很多网络请求 AP...

    阳仔
  • 源码分析——从AIDL的使用开始理解Binder进程间通信的流程

    Binder通信是 Android 系统架构的基础。本文尝试从 AIDL 的使用开始理解系统的 Binder通信。

    阳仔
  • 从数据结构的角度上看区块链到底是什么

    自从最近央视提出要发展自主区块链技术的号召以来,区块链领域又骚动了起来。程序猿是学习能力很强的群体,了解新技术是日常工作生活的一部分。作为一个从事区块链相关产品...

    阳仔
  • Rxjava + retrofit + dagger2 + mvp搭建Android框架

    最近出去面试,总会被问到我们项目现在采用的什么开发框架,不过据我的经验网络框架(volley)+图片缓存(uIl)+数据库(orm)+mvp,不过现在这套框架比...

    xiangzhihong
  • PHP反射类export方法详细解析

    CrazyCodes
  • 包装模式就是这么简单啦

    Java3y
  • 一个 android 的框架

    最近在 github 上看到一个 android 的框架,结合了 Rxjava + retrofit + dagger2 + mvp,结合了当下比较流行的框架,...

    用户1263308
  • Sweet Snippet 之 Timeslice Update

    游戏开发中,我们一般都要进行大量的对象更新(Update)操作,拿 Unity 中的 MonoBehavior 举例,其便支持定义专门的 Update 方法来处...

    用户2615200
  • Glide的图片下载进度

    好久没有写简书了,都荒废了自己,今天整理了一下以前的代码和目前现有的项目代码,看了关于gradle图片下载进度的代码,这边整理了Glide3.7.0和Glide...

    包子388321
  • BP神经网络识别性别

    1. 数据读入 function [ data,label ] = getdata( xlsfile ) % [data,label]=getdata('se...

    anytao

扫码关注云+社区

领取腾讯云代金券