惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

P
Privacy International News Feed
H
Help Net Security
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Blog — PlanetScale
Blog — PlanetScale
T
The Blog of Author Tim Ferriss
Y
Y Combinator Blog
Microsoft Security Blog
Microsoft Security Blog
Stack Overflow Blog
Stack Overflow Blog
Jina AI
Jina AI
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
D
Docker
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
I
InfoQ
Engineering at Meta
Engineering at Meta
H
Heimdal Security Blog
月光博客
月光博客
有赞技术团队
有赞技术团队
S
SegmentFault 最新的问题
V
Vulnerabilities – Threatpost
K
Kaspersky official blog
C
Cisco Blogs
Microsoft Azure Blog
Microsoft Azure Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
MongoDB | Blog
MongoDB | Blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
L
LINUX DO - 热门话题
H
Hackread – Cybersecurity News, Data Breaches, AI and More
T
Tor Project blog
M
MIT News - Artificial intelligence
L
LINUX DO - 最新话题
P
Privacy & Cybersecurity Law Blog
Google DeepMind News
Google DeepMind News
宝玉的分享
宝玉的分享
Security Archives - TechRepublic
Security Archives - TechRepublic
IT之家
IT之家
博客园_首页
F
Full Disclosure
T
Threatpost
www.infosecurity-magazine.com
www.infosecurity-magazine.com
阮一峰的网络日志
阮一峰的网络日志
Security Latest
Security Latest
S
Security @ Cisco Blogs
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
AI
AI
Hacker News - Newest:
Hacker News - Newest: "LLM"
H
Hacker News: Front Page
A
Arctic Wolf
C
Check Point Blog
S
Securelist
A
About on SuperTechFans

音视频开发进阶

音视频教程-第三节 音视频教程-第二节 真的,AI 可能就是新时代的信息差 充值 Cursor 之后,工作有了哪些变化?🤔 个人'蒸馏'大模型能做哪些有意思的事情 DeepSeek 大模型在 Mac 上的部署和运行 音视频教程-第一节 【WebRTC 专栏】-- Android 开发集成 WebRTC 库的几种方式 【WebRTC 专栏】-- 在 Mac M1 等系列芯片编译和开发 WebRTC-Android 库 Meta Llama3 大模型在 Mac 上的部署和运行 iOS VideoToolBox 解码 HEVC Open-GOP 视频的问题排查 Flutter 状态管理之 InheritedWidget 使用和分析 用 ChatGPT 回答技术问题怎么样 ? 音视频开发系统入门大致路线 UE 4.27 添加自定义 ShadingModel 用 UE4 虚幻引擎做个捏脸小功能~~ UE4 材质练习 之 凹凸贴图偏移的使用 UE4 材质练习系列基础 OpenGL上下文创建以及共享机制 007 | 播放器系列专栏-解析 MP4 文件读取信息 006 | 播放器系列专栏-在 Mac 上查看 MP4 格式信息 干货 | 快速抽取缩略图是怎么练成的? 关于直播、WebRTC、FFmpeg 的那些事 005 | 播放器系列专栏-在 Windows 上查看 MP4 格式信息 将音视频中的花屏、绿屏、黑屏问题一网打尽 关于音视频里面的解码帧率和渲染帧率 004 | 播放器系列专栏-认识MP4视频(下) 003 | 播放器系列专栏-认识MP4视频(上) 入门或者转行音视频,应该要怎么做? H264视频文件如何缩放分辨率 002 | 播放器系列专栏-FFmpeg依赖库的配置 001 | 播放器系列专栏-关于播放器项目的一个小实践 Seek策略以及在有B帧情况下的处理 目前流媒体开发工程师工作内容主要是什么? 一个音视频领域专业问答的小圈子 干货收藏 || Vulkan Game Engine 视频教程 音视频春节假期内卷指南(实操) Vulkan 在 FFmpeg 中的支持 Windows 下 FFmpeg 和 LibX264 的编译和配置 Metal 开发 | 使用 C++ 进行接口调用 音视频开发工作经验分享 || 视频版 FFmpeg 调用 MediaCodec 硬解码到 Surface 上 代码吸猫 | 用 OpenGL 图像渲染的养猫计划 百倍变速--解码到底能不能丢 非参考帧 ?FFmpeg 有话说!!! 老生常谈-FFmpeg 的编译问题轻松搞定 FFmpeg 调用 Android MediaCodec 进行硬解码(附源码) 【WebRTC 专栏】--创建相机预览 Unity Shader 光照基础之 Half Lambert 光照模型 Unity Shader 光照基础之Lambert光照模型 Unity Shader 光照基础内容 Unity Shader 显示一张图片纹理 UnityShader 的基本概念 Unity 物体的基本操作 C++ 模板系列小结07-尾置返回类型 C++ 模板系列小结06-可变参数模板特性 C++ 中的多线程的使用和线程池建设 C++ 模板系列小结05-模板类型作为模板参数 C++ 模板系列小结04-类模板中的成员模板 C++ 模板系列小结03-在模板中指定变量类型 C++ 模板系列小结02-非类型模板参数 C++ 模板系列小结01-函数模板和类模板 从零打造渲染引擎系列01-什么是渲染引擎 iOS开发 - 在 Swift 中去调用 C/C++ 代码 2021 技术新番 - 从零打造渲染引擎系列 iOS 音视频开发的一些基础准备工作 音视频交流群又来啦~~~ 【WebRTC 专栏】WebRTC & Android 开发学习环境搭建~ 【喜大普奔】域名终于备案通过啦 Shader 优化 | OpenGL 绘制网格效果 【音视频连载-011】第二季 FFmpeg 一层一层获取文件信息 KodeLife | Shader 实时编辑预览的强大工具使用实践 推荐几个堪称教科书级别的 Android 音视频入门项目 【音视频连载-010】第二季 FFmpeg 日志打印 【音视频连载-008】基础学习篇-SDL 播放 PCM 音频文件(下) 【音视频连载-007】基础学习篇-SDL 播放 PCM 音频文件(上) 【音视频连载-006】基础学习篇-SDL 播放 YUV 视频文件 【音视频连载-005】基础学习篇-SDL 加载 YUV 文件并显示 【音视频连载-004】基础学习篇-SDL 加载图片并显示 【音视频连载-003】基础学习篇-SDL 消息循环和事件响应 【音视频连载-002】基础学习篇-SDL 创建窗口并显示颜色 【音视频连载-001】基础学习篇- SDL 介绍以及工程配置 LearnOpenGL 源码在 MAC 上的编译与调试 2019 年终总结与回顾 Android NDK 开发的免费技术视频来啦~~ OpenGL 实现视频编辑中的转场效果 OpenGL 实践之贝塞尔曲线绘制 图像库 libjpeg-turbo 编译与实践 图像库 libpng 编译与实践 rust 开发编译 Android 动态库实践 Android NDK 开发 —— 从 Assets 文件夹加载图片并上传纹理 简单易用的图像解码库介绍 —— stb_image 博客图床迁移记 进击的 Vulkan 移动开发之 SwapChain 进击的 Vulkan 移动开发之 Command Buffer 进击的 Vulkan 移动开发之 Instance & Device & Queue 进击的 Vulkan 移动开发(一)之今生前世 Java 显式锁 Lock 与条件队列 C++ 标准容器库小结 一文读懂 YUV 的采样与格式 《OpenGL ES 3.x 游戏开发》碰撞检测之 AABB 包围盒
开源库之 Retrofit 源码分析
音视频开发进阶 · 2018-09-09 · via 音视频开发进阶

一个专注音视频领域的小圈子

分析一波 Retrofit 的源码实现。

简单使用

官方给出了使用例子,具体详情参看 官网

如果加入 RxJava 和 Gson ,就需要添加对应的适配和转换工厂类。

val retrofit = Retrofit.Builder()
    .baseUrl("https://api.github.com/")
    .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
    .addConverterFactory(GsonConverterFactory.create())
    .build()

围绕 Retrofit 的调用流程分析它的内部实现。

首先是 Retrofit 类的构建,通过建造者模式来实现。

Retrofit.Builder 类的调用方法中指定一些参数,就比如上面的 RxJava 和 Gson 对应的工厂类。

build 方法里面可以看到全部的所需要的内容:

    public Retrofit build() {
      // HTTP 请求的 host 路径
      if (baseUrl == null) {
        throw new IllegalStateException("Base URL required.");
      }
      // 用来执行请求的工厂,若没有设置就是使用 OKHttpClient
      okhttp3.Call.Factory callFactory = this.callFactory;
      if (callFactory == null) {
        callFactory = new OkHttpClient();
      }
      // 异步请求时回调线程切换的执行器
      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.add(platform.defaultCallAdapterFactory(callbackExecutor));
      // 数据转换器的工厂
      // Make a defensive copy of the converters.
      List<Converter.Factory> converterFactories =
          new ArrayList<>(1 + this.converterFactories.size());
      // 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);
      // 创建 Retrofit 实例
      return new Retrofit(callFactory, baseUrl, unmodifiableList(converterFactories),
          unmodifiableList(callAdapterFactories), callbackExecutor, validateEagerly);
    }

在 Build 创建 Retrofit 时有如下要注意的东西:

  • callFactory : 网络请求工厂
  • adapterFactories : 网络请求适配器工厂的集合
  • converterFactories : 数据转换器工厂的集合
  • callbackExecutor : 回调方法执行器

这四个东西可以说是 Retrofit 中很重要的内容了,它们在 Retrofit 类中都是接口或抽象类类型,将定义与实现进行分离,可以做到面向接口编程。

Retrofit 是对网络请求的封装库,一个网络请求的过程大致可以抽象如下:

创建网络请求 --> 发出网络请求 --> 收到请求内容 --> 对内容进行处理

而 Retrofit 可以说是对上面的流程都进行了抽象,抽象出一些类和接口,这些抽象类或者接口的调用就是实现上面的请求流程。

adapterFactories 就是对创建的网络请求适配的集合,比如默认请求是 Call,但是可以通过 RxJavaCallAdapterFactory 将网络请求适配到 RxJava 的 Observable

callFactory 就是用来执行网络请求的,将请求发出去,如果没有指定该变量,则默认使用 OkHttpClient 来完成该功能。

converterFactories 就是将请求后的内容转换成指定的格式的集合,如果服务器返回的是 GSON 格式,那么就可以指定 GsonConverterFactory 将 HTTP 的 Response 内容转换 GSON 对象。

callbackExecutor 就是在网络请求结束后,从子线程切换到对数据操作的线程,主要就是切换到主线程。

依照网络请求流程,上面四个重点变量在请求中出现的先后顺序也是如下:

adapterFactories --> callFactory --> converterFactories --> callbackExecutor

另外,Retrofit 还是支持多平台的,除了 Android 平台之外,还有 Java 等平台,在 Retrofit.Builder 的构造方法中就对当前平台做了一个判断,这里就只看 Android Platform 就好了。

弄懂了大致的脉络,接着再来看实际代码。

如果没有指定请求后的线程切换操作,那么就会使用 defaultCallbackExecutor 。 Android 下默认的就是 MainThreadExecutor ,通过主线程的 Handler 来实现线程切换。

    static class MainThreadExecutor implements Executor {
      private final Handler handler = new Handler(Looper.getMainLooper());
      // 向主线程 post 一个 runnable 实现切换
      @Override public void execute(Runnable r) {
        handler.post(r);
      }
    }

如果没有添加网络请求适配器对象,那么就会使用默认的 defaultCallAdapterFactory 。Android 下默认的就是 ExecutorCallAdapterFactory

它的主要代码如下:

  @Override
  public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
    if (getRawType(returnType) != Call.class) {
      return null;
    }
    final Type responseType = Utils.getCallResponseType(returnType);
    // 返回 CallAdapter 对象进行适配
    return new CallAdapter<Object, Call<?>>() {
      @Override public Type responseType() {
        return responseType;
      }
      // 具体适配函数
      @Override public Call<Object> adapt(Call<Object> call) {
        // 返回 ExecutorCallbackCall 函数,因为就是对 call 的一个委托
        return new ExecutorCallbackCall<>(callbackExecutor, call);
      }
    };
  }

重点在于返回 CallAdapter 对象,它将网络请求进行适配,如果没有使用 RxJava 的话,那么返回的就是 ExecutorCallbackCall

如果没有指定请求结果转换对象,那么就会使用默认的 BuiltInConverters 类进行转换,BuiltInConverters 与请求适配和线程切换不同,它是和平台无关的。

adapterFactoriesconverterFactories 中还用到了工厂模式,通过工厂模式对具体执行操作的 CallAdapterConverter 进行封装。

以上就完成了 Retrofit 对象的创建。

动态代理创建网络请求对象

接下来就是使用 Retrofit 类通过动态代理创建一个代理对象实例。

// 定义请求接口
public interface GitHubService {
  @GET("users/{user}/repos")
  Call<List<Repo>> listRepos(@Path("user") String user);
}
// 动态代理创建实现了请求接口的类
GitHubService service = retrofit.create(GitHubService.class);

动态代理的作用与实现,可以参考 这篇文章

请求接口定义的所有方法,都会通过 InvocationHandler 中的 invoke 方法去分发处理。

也就是说,请求接口定义的返回类型是 Call ,那么 invoke 方法返回的也是 Call 类型。

主要代码如下:

        new InvocationHandler() {
          private final Platform platform = Platform.get();

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

其中,method 就是对应接口中声明的方法,对每一个方法都会通过 loadServiceMethod 方法将它转换成 ServiceMethod 对象。

serviceMethod 会先从缓存 ConcurrentHashMap 中查找,如果没有在通过 ServiceMethod.Builder 创建一个。

创建时首先是如下两个方法:

    // 通过 method 的返回类型和 注解 找到对应的 callAdapter
    callAdapter = createCallAdapter();
    // 通过 method 的 请求返回类型 和 注解 找到对应的 responseConverter
    responseConverter = createResponseConverter();

这两个方法有点类似,在前面提到的 adapterFactories 适配器集合和 converterFactories 转换器集合都是列表。

要通过方法的返回类型、请求结果的返回类型、方法注解,为当前方法找到合适的适配器和转换器。

在这里可以看到,Retrofit 其实支持在请求接口中定义不同的返回类型,可以 CallObservable 同时使用,也可以 GSONResponse 同时使用。

确定了请求适配和结果转换类之后,就是处理接口方法中定义的那些注解,分别是为该方法定义的注解和为该方法参数定义的注解。

      // 处理方法对应的注解
      for (Annotation annotation : methodAnnotations) {
        parseMethodAnnotation(annotation);
      }
      // 处理方法参数对应的注解
      int parameterCount = parameterAnnotationsArray.length;
      parameterHandlers = new ParameterHandler<?>[parameterCount];
      for (int p = 0; p < parameterCount; p++) {
          parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
      }

关于注解处理这块比较冗长,文章篇幅有限,就不细写了,其实也主要是对那些 GETPOSTHEAD 等进行处理。

对这些注解的处理在最后创建 Call 用来发起网络请求时会用到。

当注解也处理完了之后,就创建了 serviceMethod 对象,接着创建 OkHttpCall 对象,这个对象就不是缓存的了,因为它就是用来执行请求的,每一次都要新建一个。

OkHttpCall 实现了 Call 接口,可以看做是对它的封装。Call 接口在 OkHttp 中就是用来发起网络请求的,它主要方法就是 executeenqueue

然后 serviceMethod 对象通过 adapt 方法对 OkHttpCall 进行适配转换,并且返回了转换结果,如果在创建 Retrofit 时添加了 RxJava 的适配,那么返回的就是 Observable 类型。

这个 adapt 方法的适配其实就是由 serviceMethod 的 callAdapter 来实现的。

上面提到,默认是使用 ExecutorCallAdapterFactory 工厂来实现的。

最后,invoke 函数返回的就是把 OkHttpCall 经过 adapt 适配后的对象,默认情况下返回 ExecutorCallbackCall 对象。

执行网络请求

有了 ExecutorCallbackCall 对象之后,就可以像 OkHttp 那样去执行请求了。

        // 异步请求
        repos.enqueue(object : Callback<MutableList<Repo>> {
            override fun onFailure(call: Call<MutableList<Repo>>?, t: Throwable?) {
            }

            override fun onResponse(call: Call<MutableList<Repo>>?, response: Response<MutableList<Repo>>?) {
            }
        })
        // 同步请求
        repos.execute()

ExecutorCallbackCall 对象其实是对 OkHttpCall 的委托,具体的 enqueueexecute 方法都是由 OkHttpCall 来实现的。

先看一下同步调用

   private @Nullable okhttp3.Call rawCall;
   @Override public Response<T> execute() throws IOException {
    okhttp3.Call call;
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already executed.");
      executed = true;
      call = rawCall;
      if (call == null) {
        try {
        // createRawCall 方法创建 call 
          call = rawCall = createRawCall();
        } catch (IOException | RuntimeException | Error e) {
        }
      }
    }
    // 取消请求
    if (canceled) {
      call.cancel();
    }
    // 解析请求内容
    return parseResponse(call.execute());
  }

实际是通过 createRawCall 方法来创建 Call 的,在 createRawCall 里面又主要是通过 serviceMethod.toCall 方法来完成的。

  okhttp3.Call toCall(@Nullable 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;

    int argumentCount = args != null ? args.length : 0;
    if (argumentCount != handlers.length) {
      throw new IllegalArgumentException("Argument count (" + argumentCount
          + ") doesn't match expected count (" + handlers.length + ")");
    }
    // 把注解和参数对应上
    for (int p = 0; p < argumentCount; p++) {
      handlers[p].apply(requestBuilder, args[p]);
    }
    // 最后还是通过 callFactory 来创建 Call 
    return callFactory.newCall(requestBuilder.build());
  }

toCall 方法里就用到了之前解析的注解内容,依然通过建造者模式创建请求,并且把请求的参数和对应的注解内容对应上,也就是 ParameterHandlerapply 方法。

最后是通过 callFactory 网络请求工厂来创建请求,默认就是使用 OkHttp 来创建。

parseResponse 方法会处理 Call 执行的结果,最终也是通过 serviceMethod.toResponse 方法来处理。

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

在该方法里,用到之前提到的数据转换器 responseConverter 来实现数据的转换,最终就得到了请求的结果 Response

异步请求和同步请求大致相同,不同点在于异步请求要切换到主线程,这也是用之前提到的 callbackExecutor 来实现线程切换。

      delegate.enqueue(new Callback<T>() {
        @Override public void onResponse(Call<T> call, final Response<T> response) {
          // 切换线程
          callbackExecutor.execute(new Runnable() {
            @Override public void run() {
              if (delegate.isCanceled()) {
                // Emulate OkHttp's behavior of throwing/delivering an IOException on cancellation.
                callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled"));
              } else {
                callback.onResponse(ExecutorCallbackCall.this, response);
              }
            }
          });
        }
        //...
      }

enqueue 请求的回调方法通过 callbackExecutor 实现了线程切换。

以上,就分析了 Retrofit 从创建到发起网络请求的过程。

小结

Retrofit 的设计思想非常值得学习,事实上直接使用 OkHttp 同样可以实现功能,但是 Retrofit 对 OkHttp 的封装却更加方便了。

首先,是把网络请求的类型和参数,通过注解来处理了,用注解标注,可以简化不少代码,在 Retrofit 内部去实现注解的解析,并且通过动态代理去创建实现了注解方法接口的对象实例。

其次,大量设计模式的运用让 Retrofit 更容易去拓展,添加不同的适配和转换,把网络请求的每一个环节都进行拆分,面向接口的编程,网络请求适配和数据转换,每一个部分都可以独立开。

接着,基于对 OkHttp 的封装,能够利用 OkHttp 去实现最重要的网络请求功能。

最后,学习 Retrofit 的设计思想,并把它们用到我们的代码中去。

原创文章,转载请注明来源:    开源库之 Retrofit 源码分析