Retrofit源码解析

这篇文章主要是对Retrofit的请求过程从源码的角度上做个分析,来了解为什么只要定义一个接口,然后就能完成网络请求,看看Retrofit帮我们做了什么事情。

创建请求接口

public interface NewsService{
    @GET("news/latest")
    Observable<News> getLatest();
}

这里顶一个接口,不需要再定义一个实现类。待会直接使用Retrofit的create创建一个实例即可。

创建Retrofit实例

HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);
OkHttpClient okHttpClient = new OkHttpClient.Builder()
        .addInterceptor(interceptor)
        .build();
retrofit = new Retrofit.Builder()
        .baseUrl(HOST_NAME)
        .client(okHttpClient)
        .addConverterFactory(LoganSquareConverterFactory.create())
        .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
        .build();

首先介绍一下这里使用了Builder模式,设置baseUrl,client等等。一般请求下我们整个app中保持一个Retrofit实例即可,我们可以自己弄一个单例。

创建服务实例

newsService = RetrofitClient.getInstance().create(NewsService.class);

通过这么一句话就可以创建一个实例,但是我们并没有创建NewsService的实现类,OK,我们看下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();

      @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);
        }
        if (platform.isDefaultMethod(method)) {
          return platform.invokeDefaultMethod(method, service, proxy, args);
        }
        ServiceMethod serviceMethod = loadServiceMethod(method);
        OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
        return serviceMethod.callAdapter.adapt(okHttpCall);
      }
    });
}

其实这里使用动态代理技术,使用Proxy生成一个实例,在执行实例的方法时会调用InvocationHandler的inovke方法。

首先invoke方法会调用loadServiceMethod()返回一个ServiceMethod,从方法名字我们大概可以猜出来ServiceMethod是一个方法的描述实例。我们直接来看下ServiceMethod.Builder的build方法。

public ServiceMethod build() {
  callAdapter = createCallAdapter();
  responseType = callAdapter.responseType();
  if (responseType == Response.class || responseType == okhttp3.Response.class) {
    throw methodError("'"
        + Utils.getRawType(responseType).getName()
        + "' is not a valid response body type. Did you mean ResponseBody?");
  }
  responseConverter = createResponseConverter();

  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];
    if (parameterAnnotations == null) {
      throw parameterError(p, "No Retrofit annotation found.");
    }

    parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
  }
  //省略一些代码
  ......
  return new ServiceMethod<>(this);
}

1、首先调用createCallAdapter根据当前方法的返回类型返回一个CallAdapter,不同的返回类型当然有不同的callAdapter
2、调用createResponseConverter方法返回一个Converter,其实这个也是根据返回类型创建的,我们创建的时候设置的ConverterFactory为LoganSquareConverterFactory所以,会返回LoganSquareResponseBodyConverter
3、调用parseMethodAnnotation处理方法的注解GET、POST等。
4、处理方法参数根据参数的注解生成ParameterHandler,当然不同的参数注解生成的实例类型也不同,具体可参看ParameterHandler源码。
其实这里使用了一部分的反射,这样势必会降低程序运行的效率,尽管使用了缓存策略,只有第一个才会这样。可以考虑假如apt,在编译时期,处理这些事情
有一篇博客对这块写的还是蛮不错的,在这了记录一下
图解 Retrofit - ServiceMethod,作者的图画的太棒了。

获取ServiceMethod实例之后,创建了一个OkHttpCall实例,返回serviceMethod.callAdapter.adapt(okHttpCall)。前面分析了这里的callAdapter其实就是SimpleCallAdapter,我们可以直接看这个类的adapt方法。

@Override public <R> Observable<R> adapt(Call<R> call) {
  Observable<R> observable = Observable.create(new CallOnSubscribe<>(call)) //
      .flatMap(new Func1<Response<R>, Observable<R>>() {
        @Override public Observable<R> call(Response<R> response) {
          if (response.isSuccessful()) {
            return Observable.just(response.body());
          }
          return Observable.error(new HttpException(response));
        }
      });
  if (scheduler != null) {
    return observable.subscribeOn(scheduler);
  }
  return observable;
}

这里使用CallOnSubscribe创建了一个Observable实例。
在分发的时候,会调用CallOnSubsrcibe的call方法,call方法内部又会调用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 {
          throw (RuntimeException) creationFailure;
        }
      }
    
      call = rawCall;
      if (call == null) {
        try {
          call = rawCall = createRawCall();
        } catch (IOException | RuntimeException e) {
          creationFailure = e;
          throw e;
        }
      }
    }
    
    if (canceled) {
      call.cancel();
    }
    
    return parseResponse(call.execute());
}

首先会调用createRawCall方法创建一个Call,这个方法很重要,他会对请求进行组装,之后会调用call.execute()发起请求。

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的toRequest返回一个request,参数就是调用方法的参数,这里基本上就可以确定请求的地址等具体信息了,然后调用callFactory的newCall方法返回一个call,这个CallFactory就是OkHttpClient

/** Builds an HTTP request from method arguments. */
Request toRequest(Object... args) throws IOException {
    RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers,
        contentType, hasBody, isFormEncoded, isMultipart);
    
    @SuppressWarnings("unchecked") // It is an error to invoke a method with the wrong arg types.
    ParameterHandler<Object>[] handlers = (ParameterHandler<Object>[]) parameterHandlers;
    //  省略部分代码
    .....
    for (int p = 0; p < argumentCount; p++) {
      handlers[p].apply(requestBuilder, args[p]);
    }
    return requestBuilder.build();
}

这里重点是handlers的apply方法,前面我们分析ServiceMethod中知道其实这里是根据方法的参数得到,主要是除去Query、Path这里参数注解的我们简单看下

static final class Query<T> extends ParameterHandler<T> {
    private final String name;
    private final Converter<T, String> valueConverter;
    private final boolean encoded;

    Query(String name, Converter<T, String> valueConverter, boolean encoded) {
      this.name = checkNotNull(name, "name == null");
      this.valueConverter = valueConverter;
      this.encoded = encoded;
    }

    @Override void apply(RequestBuilder builder, T value) throws IOException {
      if (value == null) return; // Skip null values.
      builder.addQueryParam(name, valueConverter.convert(value), encoded);
    }
}

就简单看下Query类,都是继承自ParameterHandler,主要是apply方法,这里又调用了RequestBuilder的addQueryParam方法

/** Encodes the query parameter using UTF-8 and adds it to this URL's query string. */
public Builder addQueryParameter(String name, String value) {
  if (name == null) throw new IllegalArgumentException("name == null");
  if (encodedQueryNamesAndValues == null) encodedQueryNamesAndValues = new ArrayList<>();
  encodedQueryNamesAndValues.add(
      canonicalize(name, QUERY_COMPONENT_ENCODE_SET, false, false, true, true));
  encodedQueryNamesAndValues.add(value != null
      ? canonicalize(value, QUERY_COMPONENT_ENCODE_SET, false, false, true, true)
      : null);
  return this;
}

OK,到最后把参数名参数值放在一个集合里面,成对连续放置。
到此基本上Retrofit的请求前的处理就这么多,下面就是OkHttp发起请求的过程了,我们下篇再分析。

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

推荐阅读更多精彩内容