Android 网络框架--Retrofit2 源码分析(下)

承接上篇我们讲了Retrofit获取一个对OkHttpCall的封装对象(Call对象)的过程,调用该对象的请求方法就可以执行同步或异步请求了。上篇为了让大家先从整体流程熟悉Retrofit,有些细节没有具体讲解,本篇对一下几点进行源码分析:

  • 怎么对OKHttp进行封装的,即OkHttpCall类。
  • 怎么创建OKHttp请求的Request对象的,即怎么解析注解,组织一个完整的Request。
  • Converter的创建、调用、实现。

OkHttpCall 源码分析

OkHttpCall是对OkHttp请求的一个封装,当调用OkHttpCall请求方法时,OkHttpCall主要做了三件事情:

  • 创建OkHttp的请求对象Call。
  • Call调用execute请求方法,完成请求。
  • 解析响应数据,返回结果。

我们以同步请求方法execute()来分析源码:

 @Override 
    public Response<T> execute() throws IOException {
        okhttp3.Call call;
        
            ......//异常处理
        
            call = rawCall;
            if (call == null) {
                try {
                    //创建一个OKHttp的Call
                    call = rawCall = createRawCall();
                } catch (IOException | RuntimeExceptione) {
                    creationFailure = e;
                    throw e;
                }
            }
        }
        if (canceled) {
            call.cancel();
        }
        //执行请求,解析Response,返回结果
        return parseResponse(call.execute());
    }

异步求方法enqueue过程大致相同,不再贴代码。 在这里提醒大家的一点:Retrofit中也定义了一个Call接口,也是用作请求封装的,跟OkHttp的Call接口同名,大家注意区分。至于OkHttp的知识我们后续专门文章跟大家一块学习。

进一步看下createRawCall()方法如何创建Call的。

private okhttp3.Call createRawCall() throws IOException {    
    // 创建request    
    Request request = serviceMethod.toRequest(args);    
    // 创建call,其实就是一个RealCall    
    okhttp3.Call call = serviceMethod.callFactory.newCall (request);    
    if (call == null) {
      throw new NullPointerException("Call.Factory returned null.");    
      }    
      return call; 
 }  

创建OKHttp的Call需要创建一个Request,然后传给callFactory创建一个Call对象,我们找到callFactory初始化的地方,看下callFactory是什么:

.....
okhttp3.Call.Factory callFactory = this.callFactory;
if (callFactory == null) {
  callFactory = new OkHttpClient();
}
.....

callFactory就是OKHttp中的OkHttpClient。Call创建完,剩下的工作就交给OKHttp了。
至此,我们分析了请求封装流程,并且知道调用创建Request对象的地方,接下来看Request对象怎么创建。

创建OKHttp请求的Request对象

Requst的创建主要涉及到类有:

  • RequestBuilder:Request构造器,用来构造Request。
  • ServiceMethod:解析请求接口方法的注解,包括方法注解信息、参数注解信息。
  • ParameterHandler:参数处理器,用来处理参数注解信息和实际传入参数数据,并将处理后的参数数据传递给RequestBuilder,用作构造Request对象。

从ServiceMethod的toRequest(args)方法看起:

Request toRequest(@Nullable Object... args) throws IOException {
        //创建RequestBuilder 对象
        RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers,contentType, hasBody, isFormEncoded, isMultipart);
        
         ......//异常处理
         
        //遍历参数处理器parameterHandlers来处理每个参数定义和具体参数args[p]
        for (int p = 0; p < argumentCount; p++) {
            handlers[p].apply(requestBuilder, args[p]);
        }
        return requestBuilder.build();
    }
  • 创建一个RequestBuilder。
  • 遍历参数处理器parameterHandlers来处理每个参数定义和具体参数args[p]。
  • 最后通过请求构造器RequestBuilder 构造一个Request。

RequestBuilder 初始化传入的一串参数和parameterHandlers都是在ServiceMethod初始化的时候,解析请求接口方法的注解得到的。分为方法注解解析和参数注解解析,下面具体看一下:

注解解析
方法注解解析:

解析方法注解的方法是parseMethodAnnotation(Annotation annotation) :

private void parseMethodAnnotation(Annotation annotation) {
            if (annotation instanceof DELETE) {
                parseHttpMethodAndPath("DELETE", ((DELETE) annotation).value(), false);
            } else if (annotation instanceof GET) {
                parseHttpMethodAndPath("GET", ((GET) annotation).value(), false);
            } else if (annotation instanceof HEAD) {
                parseHttpMethodAndPath("HEAD", ((HEAD) annotation).value(), false);
                if (!Void.class.equals(responseType)) {
                    throw methodError("HEAD method must use Void as response type.");
                }
    ......//各种方法注解的判断和解析,不都贴出
  }

对方法注解进行解析,继续看到parseHttpMethodAndPath方法:

private void parseHttpMethodAndPath(String httpMethod, String value, boolean hasBody) {
            if (this.httpMethod != null) {
                throw methodError("Only one HTTP method is allowed. Found: %s and %s.",
                        this.httpMethod, httpMethod);
            }
            //获得请求类型,是否有body数据
            this.httpMethod = httpMethod;
            this.hasBody = hasBody;

            if (value.isEmpty()) {
                return;
            }
       ......
            this.relativeUrl = value;
            this.relativeUrlParamNames = parsePathParameters(value);
        }

这样就得到httpMethod 、hasBody 、relativeUrl等 构造RequestBuilder 所需要的部分参数数据,其他方法注解解析同理,不再批量贴代码。

参数注解解析:

参数标解析parseParameter方法,同样是ServiceMethod初始化时调用,返回的是第p个参数的处理器parameterHandlers[p],代码如下

{
this.parameterHandlers[p] = parseParameter(p,parameterType,parameterAnnotations);
}

parseParameter方法又调用到parseParameterAnnotation实现参数注解解析,注解太多了,就以常用的Query注解,看下主要代码,其他注解同理:

private ParameterHandler<?> parseParameterAnnotation(
 int p, Type type, Annotation[] annotations, Annotation annotation) {
      .......
       if (annotation instanceof Query) {
           //解析注解数据value、encoded
           Query query = (Query) annotation;
           String name = query.value();
           boolean encoded = query.encoded();
           Class<?> rawParameterType = Utils.getRawType(type);
           gotQuery = true;
           //创建请求转换器
           Converter<?, String> converter =  retrofit.stringConverter(type, annotations);
           //创建参数处理器的具体实现类
           return new ParameterHandler.Query<>(name, converter, encoded);               
          }
   ........

Query解析:

  • 判断为Query注解,拿到Query的value。
  • 创建了对应该Query注解的一个Converter。
  • 最后创建并返回了ParameterHandler.Query。

Query是ParameterHandler的实现类,并且是对应Query注解的参数处理类。其他参数注解同理,都有一个ParameterHandler实现类与之对应。

----------------------------------注解解析完成--------------------------------
注解解析讲解完成,回头看toRequest方法中调用ParameterHandler的apply方法,还是ParameterHandler.Query为例:

Override void apply(RequestBuilder builder, @Nullable T value) throws IOException {
      if (value == null) return; 
      //将请求传入的参数值转换成String
      String queryValue = valueConverter.convert(value);
      if (queryValue == null) return; 
      //参数数据添加给RequestBuilder
      builder.addQueryParam(name, queryValue, encoded);
}

apply方法,将传入的参数值通过Converter转换成字符串类型,作为get请求url参数的value,name就是get请求url参数的key,然后添加给RequestBuilder 。

继续看到addQueryParam方法:

void addQueryParam(String name, @Nullable String value, boolean encoded) {

    ......//异常处理
    
    if (encoded) {
      urlBuilder.addEncodedQueryParameter(name, value);
    } else {
      urlBuilder.addQueryParameter(name, value);
    }
  }

最终参数信息添加到 urlBuilder对象中,其他参数注解同理都是通过ParameterHandler的apply方法处理后添加到RequestBuilder中保存,用于Requst构建。

RequestBuilder所有数据准备完毕以后调用build方法,最终构建Request:

Request build() {
    HttpUrl url;
    //构造完整的Url
    HttpUrl.Builder urlBuilder = this.urlBuilder;
    if (urlBuilder != null) {
      url = urlBuilder.build();
    } else {
      url = baseUrl.resolve(relativeUrl);
      if (url == null) {
        throw new IllegalArgumentException(
            "Malformed URL. Base: " + baseUrl + ", Relative: " + relativeUrl);
      }
    }
    //构造完整的body
    RequestBody body = this.body;
    if (body == null) {
      if (formBuilder != null) {
        body = formBuilder.build();
      } else if (multipartBuilder != null) {
        body = multipartBuilder.build();
      } else if (hasBody) {
        body = RequestBody.create(null, new byte[0]);
      }
    }
    MediaType contentType = this.contentType;
    if (contentType != null) {
      if (body != null) {
        body = new ContentTypeOverridingRequestBody(body, contentType);
      } else {
        requestBuilder.addHeader("Content-Type", contentType.toString());
      }
    }
    //构造完整的Request
    return requestBuilder
        .url(url)
        .method(method, body)
        .build();
  }

build方法中,先构造完整的url和body,然后构建并返回完整的Request。

Request构造涉及到大量注解解析,繁琐但没有逻辑难度。因为不同注解意义不同,处理是有区别的,但是解析流程是和我们上面讲解的流程一样的。

Converter的创建、调用、实现

Converter按用途分为两类:

  • Converter<ResponseBody, R> responseConverter将响应数据转换成响应实体对象。
  • Converter<T, String> 或者 Converter<T, RequestBody> requestConverter,用来将请求实体对象转换成对应的请求数据。
responseConverter的创建、调用、实现

responseConverter的创建:
上篇我们已经讲到,ServiceMethod初始化时候调用了Retrofit中的

public <T> Converter<ResponseBody, T>responseBodyConverter(Type type, Annotation[] annotations) {
    return nextResponseBodyConverter(null, type, annotations);
  }

具体创建上篇中已讲到,是通过converter.Factory创建,不在赘述。

responseConverter的调用:
是在OkHttpCall 中解析响应数据parseResponse方法中调用的

 T body = serviceMethod.toResponse(catchingBody);

继续看到toResponse方法,

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

responseConverter的实现:
以GsonResponseBodyConverter为例:

Override public T convert(ResponseBody value) throws IOException {
    JsonReader jsonReader =  gson.newJsonReader(value.charStream());
    try {
      return adapter.read(jsonReader);
    } finally {
      value.close();
    }
  }
requestConverter的创建、调用、实现

requestConverter的创建:
Query注解解析中,已经提到创建requestConverter。

      .......
  
      //创建请求转换器
      Converter<?, String> converter = retrofit.stringConverter(type, annotations);

     ........

调用到Retrofit的stringConverter方法,具体创建也是通过converter.Factory创建,不再赘述。

requestConverter的调用:
是在ParameterHandler中参数处理apply方法中调用的。参考上面的代码。

requestConverter的实现:
以GsonRequestBodyConverter为例:

@Override public RequestBody convert(T value) throws IOException {
    Buffer buffer = new Buffer();
    Writer writer = new OutputStreamWriter(buffer.outputStream(), UTF_8);
    JsonWriter jsonWriter = gson.newJsonWriter(writer);
    adapter.write(jsonWriter, value);
    jsonWriter.close();
    return RequestBody.create(MEDIA_TYPE, buffer.readByteString());
  }

将请求实体对象转换成RequestBody。

注意

  • 本文是对上篇源码分析的补充分析, 请先对上篇的Retrofit核心流程熟悉,再看本文。
  • 本文按照代码阅读的顺序来分析的源码,从OkHttpCall 讲到的request创建、注解解析、convert使用。实际调用顺序不是这样,比如注解解析是在ServiceMethod创建时完成的,是在request创建之前。
  • 本文为了突出主要源码,略掉异常处理的代码,这部分代码大部分是用作检查请求接口的规范性。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,527评论 5 470
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,314评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,535评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,006评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,961评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,220评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,664评论 3 392
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,351评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,481评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,397评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,443评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,123评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,713评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,801评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,010评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,494评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,075评论 2 341

推荐阅读更多精彩内容