【开源库学习】从OkHttp到Retrofit(其二 Retrofit)

news2025/8/6 16:11:06

从OkHttp到Retrofit

  • 简单使用
  • 实现原理
    • loadServiceMethod
    • Converter

简单使用

class RetrofitActivity : AppCompatActivity() {
    companion object {
        const val SERVER = "https://www.xxx.com/"
    }
    var disposable:Disposable? = null;
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_retrofit)
        val retrofit = Retrofit.Builder()
        .baseUrl(SERVER)
        .addConverterFactory(GsonConverterFactory.create())
        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
        .build()
        disposable = retrofit.create(TestApi::class.java).getIcons(1).subscribe(
            {
                Log.d("api", it.isSuccessful.toString())
            }, {
                Log.d("api", it.toString())
            })
    }

    override fun onStop() {
        super.onStop()
        disposable.dispose()
    }
}

//RESTFUL 风格接口
interface TestApi {
    @GET("/test/icon")
    fun getIcons(
        @Query("icon_id") icon_id: Long
    ): Observable<Response<IconBean>>
}

class IconBean{}

实现原理

retrofit
Retrofit的代码逻辑比较复杂,但是实际上Retrofit的主要功能就是给OkHttp做一层封装,这里我把主要流程进行了总结,简单来说当调用Retrofit.create(TestApi.classjava)方法之后,Retrofit首先会判断TestApi这个类是否符合条件,如果开启了严格检查的话,还会提前加载HttpServiceMethod。

//Retrofit.java
public <T> T create(final Class<T> service) {
    //验证
    validateServiceInterface(service);
    return (T)
        //动态代理
        Proxy.newProxyInstance(
        service.getClassLoader(), 
        new Class<?>[] {service}, 
        new InvocationHandler() {
            //判断android和jvm平台及其版本
            private final Platform platform = Platform.get();

            @Override
            public Object invoke(Object proxy, Method method, Object[] args){
                //如果是静态方法则直接执行
                if (method.getDeclaringClass() == Object.class) {
                    return method.invoke(this, args);
                }
                //isDefaultMethod:是否是默认方法
                return platform.isDefaultMethod(method)
                    ? platform.invokeDefaultMethod(method, service, proxy, args)
                    : loadServiceMethod(method).invoke(args); 
            }
        });
}
private void validateServiceInterface(Class<?> service) {
	//判断是不是接口,不是就抛出异常
    if (!service.isInterface()) {
      throw new IllegalArgumentException("API declarations must be interfaces.");
    }
	//判断存不存在泛型,存在则抛出异常
    Deque<Class<?>> check = new ArrayDeque<>(1);
    check.add(service);
    while (!check.isEmpty()) {
      Class<?> candidate = check.removeFirst();
      if (candidate.getTypeParameters().length != 0) {
        StringBuilder message =
            new StringBuilder("Type parameters are unsupported on ").append(candidate.getName());
        if (candidate != service) {
          message.append(" which is an interface of ").append(service.getName());
        }
        throw new IllegalArgumentException(message.toString());
      }
      Collections.addAll(check, candidate.getInterfaces());
    }
	//提前加载
    if (validateEagerly) {
      Platform platform = Platform.get();
      for (Method method : service.getDeclaredMethods()) {
        if (!platform.isDefaultMethod(method) && !Modifier.isStatic(method.getModifiers())) {
          loadServiceMethod(method);
        }
      }
    }
  }

loadServiceMethod

当检查完成之后,Retrofit就会创建出一个代理类,因此调用retrofit.create(TestApi::class.java).getIcons(1)事实上是调用了代理类的invoke方法,这个invoke方法会首先根据注解进行解析,添加参数,生成一个okHttp的Call请求,然后通过适配器模式将Call的返回结果进行适配。例如我希望使用RxJava的Observable作为返回类型,那么这里可以使用RxJavaCallAdapterFactory将OkHttp返回的Call<T>类型转换为Observable<T>
基于这个流程,来阅读一下代码。

ServiceMethod<?> loadServiceMethod(Method method) {
	//判断是否有缓存
    ServiceMethod<?> result = serviceMethodCache.get(method);
    if (result != null) return result;

    synchronized (serviceMethodCache) {
      result = serviceMethodCache.get(method);
      if (result == null) {
      	//没有缓存的话根据注解解析一个
      	//这里的返回是一个HttpServiceMethod,里面包含了一个适配好的call
        result = ServiceMethod.parseAnnotations(this, method);
        serviceMethodCache.put(method, result);
      }
    }
    return result;
  }
//ServiceMethod.java
static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
    RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);
    //检查:articleList方法返回类型不能用通配符和void...
    return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
}

可以看到ServiceMethod中首先创建了一个RequestFactory,RequestFactory的作用是根据注解来

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

class Builder {
    RequestFactory build() {
        //解析请求类型,methodAnnotations是method中的注解,通过getAnnotations()获取
        for (Annotation annotation : methodAnnotations) {
            parseMethodAnnotation(annotation);
        }
		//...
        //解析参数注解如Path,parameterAnnotationsArray是参数注解数组
        int parameterCount = parameterAnnotationsArray.length;
        parameterHandlers = new ParameterHandler<?>[parameterCount];
        for (int p = 0, lastParameter = parameterCount - 1; p < parameterCount; p++) {
            parameterHandlers[p] =
                parseParameter(p, parameterTypes[p], parameterAnnotationsArray[p], p == lastParameter);
        }
        //... 
        return new RequestFactory(this);
    }
}

从这里可以看出当build方法调用之后,事实上RequestFactory对传入的方法完成了解析,并且把解析的结果保存在自己成员变量中。

//HttpServiceMethod.java
//ResponseT响应类型如WanArticleBean,ReturnT返回类型如Call
static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
    Retrofit retrofit, Method method, RequestFactory requestFactory) {
    //...
    Annotation[] annotations = method.getAnnotations();
    //遍历找到合适的适配器
    CallAdapter<ResponseT, ReturnT> callAdapter =
        createCallAdapter(retrofit, method, adapterType, annotations);
    //得到响应类型
    Type responseType = callAdapter.responseType();
    //根据返回类型找到合适的转换器
    //最终又会回调到retrofit里面的callAdapterFactories
   	//callAdapterFactories在一开始builder时候传入
    Converter<ResponseBody, ResponseT> responseConverter =
        createResponseConverter(retrofit, method, responseType);
    okhttp3.Call.Factory callFactory = retrofit.callFactory;
    return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
}

static final class CallAdapted<ResponseT, ReturnT> extends HttpServiceMethod<ResponseT, ReturnT> {
    private final CallAdapter<ResponseT, ReturnT> callAdapter;

    CallAdapted(
        RequestFactory requestFactory,
        okhttp3.Call.Factory callFactory,
        Converter<ResponseBody, ResponseT> responseConverter,
        CallAdapter<ResponseT, ReturnT> callAdapter) {
      super(requestFactory, callFactory, responseConverter);
      this.callAdapter = callAdapter;
    }

    @Override
    protected ReturnT adapt(Call<ResponseT> call, Object[] args) {
      return callAdapter.adapt(call);
    }
    //父类HttpServiceMethod中的实现。
	 @Override
	  final @Nullable ReturnT invoke(Object[] args) {
	    Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
	    return adapt(call, args);
	  }
  }

结合之前的说明,可以看到当找到和是的适配器之后,loadServiceMethod会返回一个CallAdapted,并且当调用invoke方法时候,会根据CallAdapted内部的callAdapter,将Call转化为适配类型。
callAdapter又是从retrofit的callAdapterFactories中遍历得到,所以这里再回过来看看retrofit.builder()方法。

//Retrofit.Builder.java
public Retrofit build() {
    Executor callbackExecutor = this.callbackExecutor;
    //如果没设置线程池,则给android平台设置一个默认的MainThreadExecutor(用Handler将回调切回主线程)
    if (callbackExecutor == null) {
        callbackExecutor = platform.defaultCallbackExecutor();
    }
    List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
    //添加默认的DefaultCallAdapterFactory
    callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor));
}
List<? extends CallAdapter.Factory> defaultCallAdapterFactories(
      @Nullable Executor callbackExecutor) {
    DefaultCallAdapterFactory executorFactory = new DefaultCallAdapterFactory(callbackExecutor);
    return hasJava8Types
    	//默认的只有两个,如果需要使用其他的AdapterFactory需要在创建的时候传入
        ? asList(CompletableFutureCallAdapterFactory.INSTANCE, executorFactory)
        : singletonList(executorFactory);
  }

Converter

Converter的作用就是转化各种参数类型。

interface WanApi {
    //Long cur 当前时间
    @GET("article/list/{page}/json")
    Call<WanArticleBean> articleList(@Path("page") int page, @Query("cur") Long cur);
}

class TimeConverter implements Converter<Long, String> {
    private SimpleDateFormat mFormat = new SimpleDateFormat("yyyy-MM-dd-HHmmss");

    @Override
    public String convert(Long value) throws IOException {
        if (value > 1_000_000_000_000L) {//毫秒,不是很严谨 - -
            return mFormat.format(new Date(value));
        }
        return String.valueOf(value);
    }
}

class TimeConverterFactory extends Converter.Factory {

    @Override
    public Converter<?, String> stringConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
        if (type == Long.class) {
            //使用自定义TimeConverter
            return new TimeConverter();
        }
        return super.stringConverter(type, annotations, retrofit);
    }

    public static Converter.Factory create() {
        return new TimeConverterFactory();
    }
}

//addConverterFactory(TimeConverterFactory.create())

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/394111.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Lesson 8.2 CART 分类树的建模流程与 sklearn 评估器参数详解

文章目录一、CART 决策树的分类流程1. CART 树的基本生长过程1.1 规则评估指标选取与设置1.2 决策树备选规则创建方法1.3 挑选最佳分类规则划分数据集1.4 决策树的生长过程2. CART 树的剪枝二、CART 分类树的 Scikit-Learn 快速实现方法与评估器参数详解1. CART 分类树的 sklea…

Linux线程概念

重新认识一下进程 在之前写过的与进程相关的博文中&#xff0c;都把进程看作是只有一个PCB的进程。如图&#xff1a; 而实际上&#xff0c;在Linux中&#xff0c;进程不止一个执行流&#xff0c;而是可能会有几个或很多个。同一个进程中&#xff0c;每一个执行流都指向同一个虚…

【C语言】3天速刷C语言(初识)

【声明】本篇博客只用于对与刚学习C语言的同学的一个初始了解&#xff0c;具体内容请继续关注本专栏后续内容。什么是C语言C语言是一门通用计算机编程语言&#xff0c;广泛应用于底层开发。C语言的设计目标是提供一种能以简易的方式编译、处理低级存储器、产生少量的机器码以及…

模仿评论样式

主要用到了padding-left把左侧的空白给留出来&#xff0c;然后把头像定位到留出的空白位置。行内对齐样式&#xff0c;使用了display:inline-flex;align-items:center;图标本来要用字体比较方便&#xff0c;暂时用的从icon font下载的svg样式写的一塌糊涂&#xff0c;一点也没考…

说说转义字符 “\”

转义字符-escape character character 表示字符&#xff0c;包含两层含义&#xff0c; 1.字母 2.符号 转义&#xff1a; 改变含义 字符&#xff1a; 字母、符号 转义字符&#xff1a; 把 字母、符号 的含义改变了注意&#xff1a;这里有 2 个常常被忽视、忽略、轻视的转义规则&…

Java类加载器原理与实践

文章目录一、Java程序启动并运行的过程二、类加载器三、Java8内置的类加载器1. AppClassLoader2. ExtClassLoader3. BootStrap ClassLoader4. 3个类加载器之间的关系四、双亲委派模型五、关键类java.lang.ClassLoader1. loadClass(..)2. denfineClass(..)3. findClass(..)4. fi…

k8s client-go源码解析之informer 一

Informer(一) 注意&#xff1a;本文内容为学习笔记&#xff0c;内容为个人见解&#xff0c;不保证准确性&#xff0c;但欢迎大家讨论何指教。 本篇为先导篇&#xff0c; 介绍informer的入口工厂函数。 informer目录结构 (仅展示部分目录&#xff0c;省略的目录相似) clien…

项目实战典型案例27——对生产环境以及生产数据的敬畏之心

对生产环境以及生产数据的敬畏之心一&#xff1a;背景介绍总结升华一&#xff1a;背景介绍 本篇博客是对项目开发中出现的对生产环境以及生产数据的敬畏之心行的总结并进行的改进。目的是将经历转变为自己的经验。通过博客的方式分享给大家&#xff0c;大家一起共同进步和提高…

SpringCloud之 Gateway路由网关

文章目录Gateway 路由网关一、部署网关&#x1f34d;①添加依赖&#x1f34d;②设置配置文件&#x1f34d;③创建启动类&#x1f34d;④路由功能配置&#x1f34d;⑤路由访问服务二、路由过滤器2.1 单个过滤器: 配置文件2.2 全局过滤器: 自定义类提示&#xff1a;以下是本篇文章…

【数据结构初阶】详解链表OJ题

目录一.删除链表中等于给定值的节点二.合并有序链表并返回三.链表的回文结构1.反转单链表2.返回非空链表的中间节点四.输出链表倒数第K个节点五.基于给定值x分割单链表六.返回两个链表的第一个中间节点一.删除链表中等于给定值的节点 我们先来看第一题(题目链接): 因为我们需…

王道《操作系统》学习(二)—— 进程管理(一)

2.1 进程的概念、组成、特征、组织 2.1.1 进程的概念 这里像QQ这个程序执行了多次&#xff0c;虽然名字一样&#xff0c;但是它们的PID不同。 2.1.2 进程的组成 &#xff08;1&#xff09;PCB &#xff08;2&#xff09;程序段 和 数据段 小例子&#xff1a;程序是如何执行的&…

Nacos实现配置中心

文章目录配置中心提供的基础功能Nacos实现配置中心1、在配置文件增加配置&#xff08;application.yml&#xff09;2、使用Value来引用配置使用配置中心&#xff0c;怎样的配置方式呢一、安装nacos二、启动服务发现1、引入依赖2、配置文件3、开启服务注册发现功能4、启动服务三…

quarkus 生产环境与k8s集成总结

quarkus 生产环境与k8s集成总结 大纲 基础准备quarkus2.13.7脚手架工程配置GraalVM-java11 安装配置配置maven3.8.7linux环境下云原生二进制文件打包环境搭建编译运行quarkus二进制文件quarkus二进制文件制作为docker镜像并运行使用k8s部署quarkus二进制文件 基础准备 生产…

手把手交叉编译mysql

1.下载mysql&#xff08;注意下载boost版本&#xff0c;这样会少一步编译&#xff09; 下载mysql的时候一定要看好交叉编译工具链的版本。因为mysql 8.0需要的工具链版本较高&#xff0c;所以有可能不支持 查看链接如下&#xff1a; MySQL :: MySQL 8.0 Reference Manual :: …

InstructGPT方法简读

InstructGPT方法简读 引言 仅仅通过增大模型规模和数据规模来训练更大的模型并不能使得大模型更好地理解用户意图。由于数据的噪声极大&#xff0c;并且现在的大多数大型语言模型均为基于深度学习的“黑箱模型”&#xff0c;几乎不具有可解释性和可控性&#xff0c;因此&…

「SAP ABAP」OPEN SQL(四)【FROM语句】

&#x1f482;作者简介&#xff1a; THUNDER王&#xff0c;一名热爱财税和SAP ABAP编程以及热爱分享的博主。目前于江西师范大学会计学专业大二本科在读&#xff0c;同时任汉硕云&#xff08;广东&#xff09;科技有限公司ABAP开发顾问。在学习工作中&#xff0c;我通常使用偏后…

CIMCAI port ai shipping ai artificial intelligence smart port

上海人工智能独角兽中集集团高科技中集飞瞳&#xff0c;是全球应用落地最广&#xff0c;规模最大&#xff0c;最先进的的港航人工智能高科技企业&#xff0c;工业级成熟港航人工智能产品全球规模化落地应用&#xff0c;全球前三大船公司及港口码头应用落地。上海人工智能独角兽…

3.4 按键控制LED灯光敏传感器控制蜂鸣器

按键控制LED灯1.1 按键连接示意图1.2 代码设计1.21 设计思路我们要实现按键控制led&#xff0c;我们需要完成LED和按键驱动代码&#xff0c;但如果把这两部分代码都混在主函数里面&#xff0c;那么代码显得过于杂乱&#xff0c;不容易管理和移植&#xff0c;所以对于这种驱动代…

记一次反射型XSS

记一次反射型XSS1.反射型XSS1.1.前言1.2.测试过程1.3.实战演示1.3.1.输入框1.3.2.插入代码1.3.3.跳转链接2.总结1.反射型XSS 1.1.前言 关于这个反射型XSS&#xff0c;利用的方式除了钓鱼&#xff0c;可能更多的就是自娱自乐&#xff0c;那都说是自娱自乐了&#xff0c;并且对系…

Maxscale读写分离实施文档

Maxscale介绍 MaxScale是maridb开发的一个mysql数据中间件&#xff0c;其配置简单&#xff0c;能够实现读写分离&#xff0c;并且可以根据主从状态实现写库的自动切换。 使用Maxscale无需对业务代码进行修改&#xff0c;其自带的读写分离模块&#xff0c;能够解析SQL语句&…