zoukankan      html  css  js  c++  java
  • 从Retrofit的源码来看 HTTP

    关于Retrofit是啥,这里就不多解释了,还是先来瞅下官网:

    而这次主要是了解它的底层动作机制,而在了解底层之前先来回顾一下官网的整体使用步骤:

    咱们也以官网的这个例子为例,先从简单的使用开始逐步深入,先新建一个工程:

    然后增加retrofit的build引用 ,如下:

    然后按官网的步骤,首先创建一个API接口,如下:

    咱们以获取用户在github中的仓库为例,定义接口的API方法如下:

    然后具体来调用一下,也如官网的描述一样:

    然后此时并未发起HTTP请求,需要像okhttp那样调用一下这个方法,分同步和异步,当然这里得用异步喽,如下:

    然后增加访问网络的权限:

    先来查看一下我的github的仓库:

    然后运行一下:

    成功了,其实这个接口返回的格式就是json,用浏览器可以访问看下结果:

    接下来咱们将结果打印成我们看到的JSON格式一样,而咱们目前成功返回的是一个RsponseBody对像,它是来自okhttp的,如下:

    此时就需要注册一个转换器了,这里不细讲怎么用的,直接上结果,重点是通过简单的使用掌握其深层次的本质原理,也就是源码分析,下面来看怎么做这个转换:

    那此时怎么写这个转换工厂呢,这时需要再加一个库,也就是gson的支持,关于gson是啥就里就不多说了,直接添加依赖如下:

    此时就可以这么用了:

    接下来则需要修改API接口了,因为我们不想看到返回的ResonseBody对象,而想看到具体的JSON,从网站上返回的JSON可以看出其实就是一个JSON数组,所以返回的内容应该是一个List,所以修改一下:

    然后里面的每个对象则需要我们手动定义出来,先假设这个对像类为Repo,如下:

    接下来则定义该类:

    然后再定义里面的字段,这里可以通过JSON自动转成Java的字段,可以用JsonFormat工具,如下:

    然后将Json数组中的对象内容拷至其中:

    接下来咱们来修改一下返回值,如下:

    然后运行:

    ok,对于retrofit的简单用法就到此结束,重点是接下来分析它的源码:先从使用入口来进行分析的突破口,而使用入口就是它:

    能把它分析明白了,那对于retrofit的核心原理也就清楚啦,所以点进去看下它的源码:

    那此时就得看调用这个方法的对象是哪个了,如下:

    而这个API是咱们定义的接口,也是抽象的。。

    那此时就再得往前追溯了,得看它具体的对象:

    如果知道了gitHubService的具体对象那么最终我们就可以分析enqueue的具体实现了,所以定位其实现瞅一下:

    这个方法是retrofit的核心,其实可以看到有动态代理的东东,所以现在就集中来分析一下该实现:

    从字面意思来看是验证服务接口,看下究竟看了啥:

    不重要,继续往下读:

    这里是一个配置项的检查,表示是否要进行激进化的方法检查,具体就不细看了,不是核心,主要是对我们写的api的方法合法性的检查,如:

    如果开启了则会GitHubService一创建就会把所有的验证都做完了,很利于我们的调试,很早就可以发现代码写得不对,但是!!不利于性能,大致知道就行了,继续往下看:

    动态代理嘛,难道说retrofit的核心机制就是动态代理?其实确实是它,不过目前还不得而知,关于动态代理是啥这里就不过多解释了,j2se的基础,这里用伪代码来揭露其动态代码的本质,首先看第二个参数:

    其实动态代理就是首先生成一个实现了该接口的对象,伪代码表示一下:

    然后动态代理不是还有第三个参数InvocationHandler么?如下:

    其实它就会传到动态生成的代理对象里面,然后在每个具体方法实现中则会用到它来生成,伪代码如下:

    如果说我们在API接口中定义了多个方法,则在这个动态生成的对象中的实现也都是用invocationHandler来实现的,这就是动态代理的本质。 

    那接下来就把精力花在这个invoke方法的具体实现上了,只要分析清楚了它,那么就知道为啥我们仅仅声明一个API接口retrofit就可以实现一个网络请求了,所以,研究一下invoke方法的具体实现:

    而如果调用的是接口中的默认实现方法【这是Java8才有的】,直接也不做其它任何处理了,对于使用retrofit而言不可能有这种默认方法,所以可以略过这个判断细节,继续往下探究:

    好晕呀,这三行中涉及到完全陌生的ServiceMethod、OkHttpCall,完全不明白,这里就涉及到一个读源码的小技巧了,对于都看不懂的情况下,先对涉及到的类都大至认识一下既可,不用深究,所以咱们一个个先来大致瞅一下:

    啥意思?首先得理解一下什么是adapter,这个在我们listview的开发中必用的概念,还是先看一下它词的本义:

    也就是做转接用的,也就是可以猜测ServiceMethod的作用是:

    然后此类的代码量太大,也没法继续往下看了,还是返回到主调代码处继续了解其它的东东,继续看下它:

    然后咱们来看一下ServiceMethod是如何生成的,通过生成细节看是否能进对ServiceMethod有一个进一步的了解,如下:

    然后再看一下build()方法的细节:

    然后再通过构造来实例化:

    很经典的Builder模式,不过整个构建对象的细节完全看不懂,先暂且放着,等回过头按需再来查看,先来说一下Builder模式,人人皆知,这里简单说一下它的好处,通常我们用Builder模式通常会这样写:

    那它有啥好处呢?对于Person中有字段是有初始化成本的,什么意思?比如我们用正常的方式来初始化会这样写:

    首先就在内存中有person对象了,接着再来修改一下性别:

    而默认性别是女的,此句执行之后就需要在内存中将女姓给擦掉,然后用这个设置的男性来替代,这是有性能损耗的,接着再来修改年龄:

    如果默认年龄是24,那此时内存中又得将24给擦掉然后再画一个31岁的人,再接下来:

    默认人是走路的行为,此时又得内存进行擦除改掉用户的行为,所以说这种传统的方式是有性能损耗的,而Builder模式则在构建对象时没有提前生成内存,先生成一个配置清单,最终一起来构建对象,这是它的最大好处之一,另外一个好处就是当参数较多的时候这样写层次也比较清晰,关于builder模式这里简单提一下,还得回到咱们所关心的retrofit实现原理上来:

    打开瞅下它是啥?

    那不就是说:

    所以此时咱们可以看一下enqueue的具体实现:

    先跳出这个实现细节,总的来回顾一下:

    所以点进入再看最后一行的细节:

    没办法,还得硬着头皮点进去瞅下:

    那看不懂呀,怎么整,目前我们要了解的这三行代码,前两行大致猜到了一些意思,而最后一行完全不晓得其内部的细节,那接下来就从头来细看一下,看是否通过细看能发现一些线索:

    这个之前稍加看过,里在就是维护了一个缓存,不过这里还是要看一下ServiceMethod的创建过程:

    其中第一句看到了一个之前的疑问:

    其中这上callAdapter是一个接口,所以此时不就解惑了么,所以看一下callAdapter是如何创建的?

    跟进去:

    再往下跟:

    接下来就得看一下这段代码的实现了,先来瞅一下callAdapterFactories对象:

    所以看一下它的调用,其实就是在build()方法中,如我们在Activity写的:

    所以此时再看一下callAdapterFactories的创建来源:

    然后就得看下一句了:

    所以。。得看一下"platform.defaultCallAdapterFactory(callbackExecutor)"的细节:

    如:

    其中我们可以看到其实现中用到了一个“callbackExecutor”,通过它的执行然后再处理的回调:

    所以得看一下callBackExecutor是如何传递进来的,此时就又得回调Retrofit.build()方法来了:

    然后再进一步跟一下此callbackExecutor的创建细节:

    那。。原来我们看到的calladapter的作用是进行线程的转换哦,那我们继续回到ServiceMethod.build()方法分析:

    拿我们定义的API接口方法来说就是指的:

    接下来往下:

    继续往下

    这不就是指么:

    好,再继续往下:

    另外有一个细节需要注意retrofit会对我们写的注解的正确性做验证,会让我们更加规范的使用okhttp,比如multipart需要配合part来使用等,好对于ServiceMethod的build()方法可以发现其实就是对我们定义的API方法进行了解析并存下来,然后再实例化它:

    至此,咱们要想分析关键的第一句代码就彻底搞清楚其作用了,回顾一下:

    好,接下来再分析核心的第二句代码,比如好理解:

    接着再来看第三句代码,其实通过上面的分析也晓得其作用了,挼一下:

    然后此时得回顾一下callAdpater是如何创建出来的:

    然后此方法的调用是在调用build()时进行的,如下:

    所以最终调用adapt()方法的其实是ExecutorCallAdapterFactory里面的了,如下:

    也就是最终retrofit动态生成的对像在调用它里面的getRepos()方法返回的是ExecutorCallbackCall对像,如下:

    所以接下来我们再来分析最初我们分析不动的方法就顺其自然啦,也就是:

    那就是直接调用ExecutorCallbackCall.enqueue()方法,如下:

    而代理的call是在我们代理对像方法执行时动态创建的,如下:

    所以最终就会转到OkHttpCall.enqueue()方法来,如下:

    其中还是利用了ServiceMethod来对之前解析的东东来转换成了okhttp的call,如下:

    然后再利用Okhttp的Call进行异步请求,如下:

    @Override public void enqueue(final Callback<T> callback) {
        checkNotNull(callback, "callback == null");
    
        okhttp3.Call call;
        Throwable failure;
    
        synchronized (this) {
          if (executed) throw new IllegalStateException("Already executed.");
          executed = true;
    
          call = rawCall;
          failure = creationFailure;
          if (call == null && failure == null) {
            try {
              call = rawCall = createRawCall();
            } catch (Throwable t) {
              throwIfFatal(t);
              failure = creationFailure = t;
            }
          }
        }
    
        if (failure != null) {
          callback.onFailure(this, failure);
          return;
        }
    
        if (canceled) {
          call.cancel();
        }
    
        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) {
              callFailure(e);
              return;
            }
    
            try {
              callback.onResponse(OkHttpCall.this, response);
            } catch (Throwable t) {
              t.printStackTrace();
            }
          }
    
          @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) {
              t.printStackTrace();
            }
          }
        });
      }

    其中回调是先对OkHttp的Response进行解析,解析成Retrofit的Response:

    然后这个parseResponse()方法就可以体现出它与http的关系了,就是用了http的知识来编写的,大致瞅一下:

    其中从okhttp的reponse转成retrofit的response最终还用到了converter了,如下:

    最后还有一个知识就是retrofit如何集成rxjava,首先得集成一下rxjava,如下:

    然后此时需要在增加一个calladapter,如下:

    此时我们的API定义返回就不用返回Call对像了,而是可以返回一个Observable,如下:

    然后就可以用rxjava的那一套来进行接口请求及返回处理了,Retrofit是可以支持多个Adapter的,瞅一下:

    其中我们知道Retrofit默认的Adapter为CallAdapter,是可以将ResponseBody转换成一个Call对象,如下:

    其具体实现是:

    并达到一个切换线程的作用。

    而此时加了一个Rxjava的CallAdapter,如下:

    所以我们在api可以为:

    到此!!已经完整将Retrofit的整个核心机制分析完了,对于之后在实际工作中用Retrofit也更加踏实了~~说实话还是挺复杂的。

  • 相关阅读:
    gain 基尼系数
    luogu P5826 【模板】子序列自动机 主席树 vector 二分
    牛客挑战赛39 树与异或 离线 树上莫队 树状数组 约数
    4.22 省选模拟赛 三元组 manacher 回文自动机
    4.22 省选模拟赛 最优价值 网络流 最大权闭合子图
    4.18 省选模拟赛 消息传递 树剖 倍增 线段树维护等比数列
    luogu P4008 [NOI2003]文本编辑器 splay 块状链表
    牛客挑战赛39 密码系统 后缀数组
    luogu P1526 [NOI2003]智破连环阵 搜索+最大匹配+剪枝
    luogu P4095 [HEOI2013]Eden 的新背包问题 多重背包 背包的合并
  • 原文地址:https://www.cnblogs.com/webor2006/p/10502230.html
Copyright © 2011-2022 走看看