retrofit原理

前言:Retrofit是基于OkHttp封装的一个网络请求框架,既然是OkHttp的上层框架,当然是解决了OkHttp在开发中的一些痛点。具体的来说:

  1. 使用过程中接口配置繁琐,OkHttp中每发起一个请求都要新建一个Request,当要配置复杂请求(body,请求头,参数)
    时尤其复杂。
  2. 需要用户拿到responseBody后自己手动解析,解析工作应该是可以封装的。
  3. 无法适配自动进行线程切换。
  4. 嵌套网络请求会陷入“回调陷阱”

Retrofit基本使用

依赖:

implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'//数据解析(可选)
implementation 'com.squareup.retrofit2:adapter-rxjava:2.9.0'//RxJava适配器(可选)

构建接口Service:

public interface  ApiService {
    @GET("users/{users}")
    Call<UserBean> getUserData(@Path("users") String users);
}

使用:

static Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("https://gitee.com/api/v5/")
        .addConverterFactory(GsonConverterFactory.create())
        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
        .build();

final ApiService service = retrofit.create(ApiService.class);

final Call<UserBean> result = service.getUserData("caoyangim");

result.enqueue(new Callback<UserBean>() {
    public void onResponse(Call<UserBean> call, Response<UserBean> response) {
        if (response.isSuccessful()){
            System.out.println("success:"+response.body());
        }
    }
    public void onFailure(Call<UserBean> call, Throwable throwable) {
        System.out.println("ERROR:"+throwable.toString());
    }
});

注解

  • get
  • post
  • put
  • delete
  • patch
  • head
  • options
  • http 通用注解,可以替换以上所有注解,其拥有method,path,hasbody三个属性

源码解析

构建Retrofit网络实例对象

在创建retrofit网络实例对象的时候用到了build建造者模式【一般 配置参数大于5个 & 参数可选 就要用build模式了】,代码如下:

public Retrofit build() {
    if (baseUrl == null) {
      throw new IllegalStateException("Base URL required.");
    }

    okhttp3.Call.Factory callFactory = this.callFactory;
    //默认只支持OKHttpClient,不支持httpUrlConnection和httpClient
    //不过你也可以自己构建一个HTTPClient,传给callFactory
    if (callFactory == null) {
      callFactory = new OkHttpClient();
    }
    

    //为线程切换准备了一个execute,可以点进去看,这个Executor在Android平台下其实就是Handler
    Executor callbackExecutor = this.callbackExecutor;
    if (callbackExecutor == null) {
      callbackExecutor = platform.defaultCallbackExecutor();
      /**
       * 返回的excutor就是这个:
       
        static final class MainThreadExecutor implements Executor {
            private final Handler handler = new Handler(Looper.getMainLooper());
            @Override
            public void execute(Runnable r) {handler.post(r);}
        }
        
      **/
        
    }

    //网络请求适配工厂的集合,构造时传入的RxJavaCallAdapter会传到这里面来
    List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
    callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor));

    //数据转换工厂的集合,传入的GsonAdapter到这里来
    List<Converter.Factory> converterFactories =
        new ArrayList<>(
            1 + this.converterFactories.size() + platform.defaultConverterFactoriesSize());
    
    ...

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

TIP:Retrofit在这里提供了一个统一的入口,所有需要的东西都通过build模式往里面加就行了,用的时候也是用retrofit.create()用,所以这里也用到了“外观(门面)模式”

retrofit.create()

public <T> T create(final Class<T> service) {
  validateServiceInterface(service);
  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 @Nullable 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);
              }
              args = args != null ? args : emptyArgs;
              return platform.isDefaultMethod(method)
                  ? platform.invokeDefaultMethod(method, service, proxy, args)
                  : loadServiceMethod(method).invoke(args);
            }
          });
}

这里面用到了动态代理的技术,你传入一个带有获取数据方法的接口,这个方法帮你动态的生成一个代理类继承该接口,这样一来所有的获取数据的方法都走这里的invoke函数,就可以拦截调用函数的执行,从而将网络接口的参数配置归一化。

即你每次发起网络请求看似是走的自己定义的接口,实际上它是会走到这个方法中来的。

然后往下一般会走loadServiceMethod(method)方法,此方法的作用是解析传入的这个方法,主要是通过解析它的注解获取请求路径、传递参数等属性,返回的是一个ServiceMethod对象。

loadServiceMethod

ServiceMethod<?> loadServiceMethod(Method method) {
  ServiceMethod<?> result = serviceMethodCache.get(method);
  if (result != null) return result;

  synchronized (serviceMethodCache) {
    result = serviceMethodCache.get(method);
    if (result == null) {
      result = ServiceMethod.parseAnnotations(this, method);
      serviceMethodCache.put(method, result);
    }
  }
  return result;
}

从这个方法可以看出有个缓存机制,解决了每次都使用反射带来的效率问题。

ServiceMethod/HttpServiceMethod

image-20210221151029435.png

里面还有几个属性:

abstract class HttpServiceMethod<ResponseT, ReturnT> extends ServiceMethod<ReturnT> {
    ...
  private final RequestFactory requestFactory;
  private final okhttp3.Call.Factory callFactory;
  private final Converter<ResponseBody, ResponseT> responseConverter;
}
image-20210221151159987.png

retrofit构建了所有的网络请求共同的一些属性,而serviceMethod对应着某个单独的网络请求,其内部属性更加具体化。

请求数据-getUserData

因为用到了动态代理模式,所有不管你接口中写了什么自定义的请求方法,他都会被转到动态代理的类中来:即上面说到的loadServiceMethod():

return platform.isDefaultMethod(method)
                    ? platform.invokeDefaultMethod(method, service, proxy, args)
                    : loadServiceMethod(method).invoke(args);

loadServiceMethod中会走到parseAnnotations中,返回了一个ServiceMethod对象:

@Override
final @Nullable ReturnT invoke(Object[] args) {
  Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
  return adapt(call, args);
}
@Override
protected ReturnT adapt(Call<ResponseT> call, Object[] args) {
    return callAdapter.adapt(call);
}

返回值的类型根据callAdapter来定,一般可以用defalultCallAdapter,RxJavaCallAdapter 或者也可以自定义callAdapter,只需重写adapt方法即可:收到的是call,根据自己的需求返回即可。

enqueue

enqueue最终是调用到了OkHttpCall.enqueue方法中来,在这里面实际上是用到了okhttp的相关方法,比如createRawCall():

private okhttp3.Call createRawCall() throws IOException {
  okhttp3.Call call = callFactory.newCall(requestFactory.create(args));
  if (call == null) {
    throw new NullPointerException("Call.Factory returned null.");
  }
  return call;
}

返回的是okHttp3中的call,然后用这个call:

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) {
          throwIfFatal(t);
          t.printStackTrace(); // TODO this is not great
        }
      }

      @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) {
          throwIfFatal(t);
          t.printStackTrace(); // TODO this is not great
        }
      }
    });

这些都是okhttp3的用法,被封装到了retrofit中。

再在返回结果的时候用到callbackExecutor.execute来切换了线程,executor实际上是:

static final class MainThreadExecutor implements Executor {
  private final Handler handler = new Handler(Looper.getMainLooper());

  @Override
  public void execute(Runnable r) {
    handler.post(r);
  }
}

设计模式

retrofit中用到了门面(外观)模式,建造者模式,代理模式(动态代理)等设计模式的思想。

retrofit中你可以通过很多组件式的方式使用,比如callAdapter的选择上,可以选择java8的,可以选择RxJava/RxJava2的,scala的方式来编码;获取数据后数据转换方面(convert),可以使用gson ,jackson,moshi甚至xml的方式,都可以自己根据需求选择。这些可以自定义的来组合使用,即外观模式。

建造者模式和代理模式分别在业务代码和源码中可以体现出来。

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

推荐阅读更多精彩内容

  • 一、Retrofit简介 Retrofit是现下Android端开发非常流行的一款网络请求框架,它通过动态代理的方...
    爱恨流星阅读 577评论 0 0
  • Retrofit原理浅析 做Android也有几年了,各种Android http类库也用过不少,自己的做过的项目...
    Alexclin阅读 29,084评论 4 52
  • Retrofit 依赖 Retrofit 的简单使用 Retrofit 官方示例文档 本篇博客直接使用官方的示例,...
    拂晓是个小人物阅读 1,023评论 0 3
  • 记得很久以前,写过一篇文章,主要说的是Retrofit应用的:Retrofit2+RxJava2踩坑 今天我们来简...
    俗人浮生阅读 383评论 0 0
  • Retrofit是目前比较流行的网络框架,它封装了okhttp作为底层的网络通讯框架, 使用动态代理方式来解析注...
    e小e阅读 334评论 0 0