Retrofit 笔记

一开始的目的是为了学习动态代理的使用,后来干脆把整个流程梳理了下

这不是完整的Retrofit,是纯Java环境下不涉及Android,Http的流程代码,Retrofit的精简代码,把内部一些设计和逻辑做了精简,主要是为了学习整个代码的大体设计和流程

Retrofit内关于OkHttp、Rx、Android相关的部分都去除了,其中okhttp3.call由一个RawCall来代替,内部没有做相关网络请求,只是做了下字符串拼接后println(),尽量还保持着Retrofit原有的类,有些地方,把注释也给粘贴过来了

Demo完整代码——>GitHub RetrofitDemo


1. Demo

使用也基本Retrofit的形式,Demo本身并无任何意义

1.1 Service

/** 获取币种Coin Service 接口 */
public interface IBtcService {
    /** 获取 BTC 信息 */
    Call<String> getBtcCoinInfo(@Content String content);
}

@Content是自己定义的一个注解,表明方法内是Content,作用类比@Path等,支持多个不同类型的Content,例如

getBtcCoinInfo(@Content String content1, @Content int content2)

返回类型必须是Call<>


1.2 使用

public class RetrofitDemo {
    public static void main(String[] args) {
        btcInfo();
    }

    /** BTC 信息 */
    private static void btcInfo() {
        Retrofit retrofit = new Retrofit.Builder().build();
        IBtcService iBtcService = retrofit.create(IBtcService.class);
        Call<String> call = iBtcService.getBtcCoinInfo("BTC $3512.6 ");
        try {
            Response<String> response = call.execute();
            System.out.println(response.body());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

结果就是Content前拼接当前时间

2019-1-27 12:01:27
BTC $3512.6 

2. 流程

基于Retrofit2.5.0版本,整个流程的代码都在core文件夹下

整个流程涉及有4个重要部分Retrofit,CallAdapter,Converter,OkHttpCall

Retrofit作为门面,关键点之一是crete()方法,使用动态代理通过CallAdatperRetrofit.Call转换为一个Service

Converter转换器,Request BodyResponse Body都要用

OkHttpCall,在实际的Retrofit中,持有着一个OkHttp3.Call,最终所有的有关网络请求的操作,都会在这里执行。由于我这里将网络请求去掉做精简,只是调用了RawCall.execute()来模拟网络请求

注意:

还有一个重要的东西CallbackExecutor,给精简去掉了。在Android内,由于UI线程的原因,网络请求时OkHttp会通过线程池运行在单独线程中,但本Demo为了简化流程,就网络请求模拟时,没有新创建线程, 对整个流程学习影响不大

实际在Retrofit中,Android环境默认情况下,会用一个Handler进行线程间通信将Response回调到UI线程


2.1 build()构建

Retrofit构造方法并不是private的,但一般都是通过内部的Builder来构建

 @Nullable
 private Executor callbackExecutor;

 Builder(Platform platform) {
     this.platform = platform;
 }

 public Builder() {
     this(Platform.get());
 }

 public Retrofit build() {

     Executor callbackExecutor = this.callbackExecutor;
     if (callbackExecutor == null) {
         callbackExecutor = platform.defaultCallbackExecutor();
     }

     // Make a defensive copy of the adapters and add the default Call adapter.
     List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
     callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor));

     // Make a defensive copy of the converters.
     List<Converter.Factory> converterFactories = new ArrayList<>(
                    1 + this.converterFactories.size() + platform.defaultConverterFactoriesSize());

     // Add the built-in converter factory first. This prevents overriding its behavior but also
     // ensures correct behavior when using converters that consume all types.
     converterFactories.add(new BuiltInConverters());
     converterFactories.addAll(this.converterFactories);
     converterFactories.addAll(platform.defaultConverterFactories());
     return new Retrofit(converterFactories, callAdapterFactories);
 }

Executor可以先不关注

CallAdapter作用就是将Call转换为Service,默认情况下使用DefaultCallAdapterFactory

Converter转换器,默认情况下,使用BuiltInConverters来构建内置的转换器

在调用build()后,Retrofit就有了关联的CallAdapterFactory、ConveterFactory,但此时具体要使用的CallAdpater、Converter还不知道


2.2 动态代理

Retrofit作为门面,关键点之一是crete()方法,使用动态代理通过CallAdatperRetrofit.Call转换为一个Service

/**
 * 利用 JDK 动态代理,create 出一个 T(Service)
 * <p>
 * 需要将 Call 通过 CallAdapter 转换成 T
 * <p>
 * Single-interface proxy creation guarded by parameter safety
 *
 * @param service 代理接口 Class
 * @param <T>     代理
 * @return T, 代理 Service
 */
@SuppressWarnings("unchecked")
public <T> T create(final Class<T> service) {
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[]{service},
            new InvocationHandler() {
                private final Object[] emptyArgs = new Object[0];
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
                }
            });
}

loadServiceMethod()从已创建的mServiceCache中获取当前方法对应的ServiceMethod,没有就创建一个新的put进去

private ServiceMethod<?> loadServiceMethod(Method method) {
    ServiceMethod<?> result = mServiceCache.get(method);
    if (result != null) {
        return result;
    }
    synchronized (mServiceCache) {
        result = mServiceCache.get(method);
        if (result == null) {
            result = ServiceMethod.parseAnnotations(this, method);
            mServiceCache.put(method, result);
        }
    }
    return result;
}

2.2.1 ServiceMethod

ServiceMethod是一个抽象类,有一个HttpSeeviceMethod子类,负责将好的Request,CallAdapter,Converter组合起来

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.");
        }

        return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
    }

    abstract T invoke(Object[] args);
}

parseAnnotations()内,首先会通过RequestFactory.parseAnnotations()进行解析注解,然后构建出一个RequestFactory

再判断returnType返回类型,若为void,就会抛出异常,也就是创建的Service接口返回值不能为void

返回值为子类HttpServiceMethodparseAnnotations()方法返回值


HttpServiceMethod

内部主要有两个方法:

 /** 构建具体的 CallAdapter,Response Converter */
 static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
         Retrofit retrofit, Method method, RequestFactory requestFactory) {
     // 创建 CallAdapter
     CallAdapter<ResponseT, ReturnT> callAdapter = createCallAdapter(retrofit, method);
     Type responseType = callAdapter.responseType();
     // Response Converter
     Converter<ResponseBody, ResponseT> responseConverter =
             createResponseConverter(retrofit, method, responseType);
     return new HttpServiceMethod<>(requestFactory, callAdapter, responseConverter);
 }
 
 /** ServiceMehod 抽象方法,将OkHttpCall进行适配*/
 @Override
 ReturnT invoke(Object[] args) {
     return mCallAdapter.adapt(new OkHttpCall<>(mRequestFactory, args, mResponseConverter));
 }

到了此时,具体要使用的CallApdater、Converter都已经创建好,就等着调用Service内的方法进行网络请求

mCallAdapter.adapt(),就是适配器模式,将Service适配成Call


2.3 Converter 转换器

Converter<F,T>是一个接口,将一个F -> T

两个地方要用,Request Body以及Response Body

Request Body需要解析注解,拿到注解的信息拼装用于网络请求的Request

Response Body就是将Json转换成为Bean,但Demo里这里简化了,只是直接将字符串进行返回


2.3.1 解析 Request Body 注解

ServiceMethod.parseAnnotations()内,RequestFactory.parseAnnotations()时会进行解析注解,组装Request

static RequestFactory parseAnnotations(Retrofit retrofit, Method method) {
    return new Builder(retrofit, method).build();
}

进行 build 构建RequestFactory

RequestFactory build() {
    // 创建的 Service 接口方法中的参数注解
    int parameterCount = parameterAnnotationsArray.length;
    parameterHandlers = new ParameterHandler<?>[parameterCount];
    for (int p = 0; p < parameterCount; p++) {
        parameterHandlers[p] = parseParameter(p, parameterTypes[p], parameterAnnotationsArray[p]);
    }
    return new RequestFactory(this);
}

parseParameter()就是根据Service接口内方法参数,使用parseParameterAnnotation()进行遍历对每个注解进行解析

/** 解析具体的参数注解 */
@Nullable
private ParameterHandler<?> parseParameterAnnotation(
        int p, Type type, Annotation[] annotations, Annotation annotation) {
    if (annotation instanceof Content) {
        validateResolvableType(p, type);

        Converter<?, String> converter =
                mRetrofit.stringConverter(type, annotations);
        return new ParameterHandler.Content<>(converter);
    }
    return null;
}

@Content是一个自己定义的注解

/** 内容注解 */
@Documented
@Target(PARAMETER)
@Retention(RUNTIME)
public @interface Content {
}

ParameterHandler一个抽象类,根据注解,实现了一个Content子类,负责对注解进行解析拼接。注解是RUNTIME型的,为了加快读取,使用ParameterHandler

static final class Content<T> extends ParameterHandler<T> {

    private final Converter<T, String> mValueConverter;

    Content(Converter<T, String> valueConverter) {
        this.mValueConverter = valueConverter;
    }

    @Override
    void apply(StringBuilder builder, T value) throws IOException {
        builder.append(mValueConverter.convert(value));
    }
}

apply()将注解标记的参数拼接到StirngBuilder,在OkHttpCall内发起网路请求时,会通过这个StirngBuilder拿到Request Body


2.3.2 Response Body 转换

Retrofit.Builderbuild()方法内,添加了一个BuiltInConverters内置的转换器

converterFactories.add(new BuiltInConverters());

BuiltInConverters 内置转换器

/** 内置的转换器 */
final class BuiltInConverters extends Converter.Factory {
    @Nullable
    @Override
    public Converter<ResponseBody, ?> responseBodyConverter(
            Type type, Annotation[] annotations, Retrofit retrofit) {
        // 由于简化过程没有进行 http 请求,最终都转换成了 String
        if (type == String.class) {
            return ResponseBodyToStringConverter.INSTANCE;
        }

        // 返回 Void
        if (type == Void.class) {
            return VoidResponseBodyConverter.INSTANCE;
        }
        return null;
    }

    /** Response Body 转换成 String */
    static final class ResponseBodyToStringConverter implements Converter<ResponseBody, String> {
        static final ResponseBodyToStringConverter INSTANCE = new ResponseBodyToStringConverter();

        @Override
        public String convert(ResponseBody value) throws IOException {
            return value.body();
        }
    }
}

ResponseBodyToStringConverter内直接返回了ResponseBody.body(),也就是Stirng本身


2.4 OkHttpCall

call.execute()时,最终会调用OkHttpCall内部逻辑,发起网络请求

@Override
public Response<T> execute() throws IOException {
    RawCall call;
    synchronized (this) {
        if (mExecuted) {
            throw new IllegalStateException("Already executed.");
        }
        mExecuted = true;
        call = mRawCall;
        if (call == null) {
            try {
                call = mRawCall = createRawCall();
            } catch (IOException | RuntimeException | Error e) {
                throw e;
            }
        }
    }
    return parseResponse(call.execute());
}

在方法内,会先创建RawCall

private RawCall createRawCall() throws IOException {
        return new RawCall(mRequestFactory.create(mArgs));
}

mRequestFactory.create()内,会将ParameterHandler存的解析的注解拼装的Request Body转成String

parseResponse()解析结果

private Response<T> parseResponse(String s) throws IOException {
    T body = mResponseConverter.convert(new ResponseBody(s));
    return Response.success(body);
}

根据创建的Response Body Converter来解析,之后构建一个Response类包装下,返回


3. 最后

整个精简后的Demo流程就这些,可以看看具体的代码

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

推荐阅读更多精彩内容

  • 目录介绍 1.首先回顾Retrofit简单使用方法 2.Retrofit的创建流程源码分析2.1 Retrofit...
    杨充211阅读 1,045评论 0 16
  • Retrofit这个开源库出来也有一定年头了,记得之前还是在V1.0的版本的时候,之前在三月份也写过一个Retro...
    lovejjfg阅读 1,427评论 0 5
  • Retrofit 源码解析 简单用法 Retrofit最简单的用法就是定义一个接口,创建Retrofit对象,调用...
    Kingty阅读 779评论 3 14
  • # 结构 HTTP请求包括三部分:请求行(Request Line),头部(Headers)和数据体(Body)。...
    okmnhyxx阅读 231评论 0 0
  • 现如今 一切安稳 所有遗憾都按部就班地填满 时间真是奇妙 分秒必争地带走一切 又从容不迫地等一切发生 漂泊的游子在...
    丹三酱阅读 235评论 0 0