谈谈网络库和Retrofit

简介: 本文目录如下 网络模块需要具备什么能力 为什么Retrofit是个好选择 Retrofit业务分析 Retrofit技术点 设计模式

本文目录如下

  1. 网络模块需要具备什么能力
  2. 为什么Retrofit是个好选择
  3. Retrofit业务分析
  4. Retrofit技术点
  5. 设计模式

一.网络模块需要具备什么能力?

常见的需求下图:
screenshot.png

下面解释一下重要的部分.

1.支持缓存

为了让页面快速展现,很多页面需要先加载缓存.
通用策略是

1.先加载缓存;

2.然后做网络请求
3.网络请求成功后刷新页面,并且更新缓存数据

这里涉及到,缓存要存在哪里. 一般也是两种方案:

  1. 业务侧做缓存. 每个页面自己维护缓存,一般存在db/sp/文件系统中
  2. 网络侧做缓存, 业务做网络请求的时候,可以要求返回缓存数据.

方案1:适用于存储需要检索数据的情况. 比如数据存在db中,可以用sql查询. 缺点是业务侧需要实现缓存策略,较麻烦.
方案2: 业务侧调用简单,适用场景非常广泛.

所以在网路侧支持缓存, 是一个很普遍的方案.

2.AccessToken失效后重发失败请求

这个是基于这几种前提.

App有登录功能
App登录通过 AccessToken/RefreshToken实现

简单说明 AccessToken失效后会发生什么:

  1. AccessToken失效
  1. 做网络请求,服务端会返回结果说AccessToken失效
  2. 此时网络请求是失败了
  3. 客户端利用RefreshToken 重新换AccessToken.
  4. 拿着新AccessToken 重发网络请求.
  5. 业务侧拿到正确结果

一般AccessToken失效是比较频繁的, 如果没有自动重新请求机制, 每次AccessToken失效用户都能感知到,这样体验很不好. 所以一般要在网络库/登录模块之间或者上层做一个封装解决这个问题..

3. 多级别取消网络请求

最好是能:

  1. 取消一次网络请求
  1. 某个页面退出时,能取消整个页面对应的网络请求.

资源吗,能省点就省点

4. 支持"同步/异步"请求

5. 支持选中在"异步线程/UI线程"回调

很多请求是需要需要刷UI的, 这样就不需要主动post到UI线程了.

二.为什么Retrofit是个好选择

Retrofit-Github

选开源库,有几个点很重要.

1.开源库要稳定,优先选择有人维护,用户量大的库

成熟的项目明显的坑少. 所以一个应用广泛的库是一个稳妥的选择.

Retrofit: Square出品, 2万+ star,一直在更新

2.开源库的代码结构不能太庞大,太复杂.

是代码总会有bug的, 有问题要能看明白,可以选择规避或者自行解决. 代码庞大或者逻辑复杂时,排查问题就是一个很痛苦的过程

Retrofit 代码量少,结构清晰

screenshot.png

3.拓展性好

业务是变化的, 要在预期的业务变化内, 当前库要能cover住, 如果开发几版发现不能满足拓展的需求, 临时换库,那会带来很多工作量和bug

Retrofit 在解耦上做的很棒,拓展性高

4.开源库自身的依赖库不能太多

在这点上很重要,引入一个库如果要一并带入很多它的依赖库,会有很大的问题:

  1. 会增大包的体积
  2. 会让库变得不好维护,链条上的每个库都可能有问题.
  3. 最大的问题是, 这些依赖库可能会和App的其他依赖库冲突. 会产生类冲突,版本冲突. 排查这些冲突是一个很痛苦的过程.(在这点上, 集团的依赖库这个问题尤为严重)

Retrofit目前依赖于Okhttp3, 考虑到OKhttp3也很流行,可以一并引入

三. Retrofit业务分析

做为一个网络库Okhttp的封装, Retrofit需要处理3部分问题:

  1. 允许业务拓展哪些环节.
  2. 这些拓展的注册,和什么情况下生效
  3. 拓展是否灵活

1.拓展性

Retrofit允许配置的是 CallAdapter,RequestBodyConverter,ResponseConvert.

CallAdapter: 负责调用OkHttpCall发起请求,处理回调分发
RequestBodyConverter: 在构建Request的时候, 构建body
ResponseConverter: 转换网络请求结果.

Retrofit发起网络请求最终走的的OKHttpCall.
网络请求的流程套路大家都一样, 都是:
"业务发起请求"->"构建Request"->"发起请求"->"解释请求"->"回调业务".
下面是他的调用流程, 蓝色部分是自定义模块.

screenshot.png

2. 功能的注册和获取

注册

在Retrofit初始化的时候, 允许添加CallAdapterFactory和ConverterFactory.

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("http://httpbin.org")
    .addCallAdapterFactory(new ErrorHandlingCallAdapterFactory())
    .addConverterFactory(GsonConverterFactory.create())
    .build();

获取

注册和获取的流程如下.
按注册的顺序获取,只要找到了就就结束查找.

screenshot.png

3.灵活的处理能力

每一个请求声明是一个函数, 他包含3部分信息:

  1. Return Type
  2. Method Annotation
  3. Parameter Annotation

screenshot.png

对于CallAdapter/RequestBodyConverter/ResponseConvert可以获取到这3部分信息.然后做相应操作,过程是非常灵活.

//CallAdapter.Fatory 可以获取到 
//ReturnType,Method Annotation  
public abstract @Nullable CallAdapter<?, ?> get(Type returnType, Annotation[] annotations,
        Retrofit retrofit);

//Response Convert可以获取到 
//Type,Method Annotation
Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
        Retrofit retrofit);
        
//Request Convert可以获取到 
//Type,Method Annotation,Parameter Annotation  
Converter<?, RequestBody> requestBodyConverter(Type type,
        Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) ;

四.Retrofit技术点

1.大量泛型/反射的操作

关于Type下面这个文章讲的比较好
Retrofit完全解析(三):Type<基础详解>

对于方法

@GET("/repos/{owner}/{repo}/contributors")
Call<List<Contributor>> contributors(
            @Path("owner") String owner,
            @Path("repo") String repo);

方法有三部分信息

  1. 函数返回值
  1. 方法体上的Annotaion
  2. 参数中的Annotation

Retrofit中根据这3部分信息, 匹配CallAdapter/Converter,生成ServiceMethod, 中间用了大量的泛型解释.代码集中体现在 Utils/ServiceMethod/CallAdapter/Converter上.

用到类型示例:  
-Type
-ParameterizedType 
-TypeVariable  
-GenericArrayType    
-WildcardType    
-Class.isAssignableFrom
-Class.isInterface()
-Class<?>[] getInterfaces()   
-Type[] Method.getGenericParameterTypes() 
-Annotation[][] Method.getParameterAnnotations()
-Method.getGenericReturnType()

2.类和参数保护

1.不能修改的类用final修饰
2.new ConcurrentHashMap<>()处理并发,取得时候加上synchronized判断
3.adapterFactories = unmodifiableList(adapterFactories); // Defensive copy at call site. 做参数保护

3.类中自定义static实例化方法

public final class GsonConverterFactory extends Converter.Factory {

 public static GsonConverterFactory create() {
    return create(new Gson());
  }
}

4.网络模拟服务MockWebServer

MockWebServer-github
com.squareup.okhttp3:mockwebserver
比较强大的网络模拟工具, 配合Okhttp用很方便

MockWebServer server = new MockWebServer();
server.start();
server.enqueue(new MockResponse().setBody("{\"name\": \"Jason\"}"));
server.enqueue(new MockResponse().setBody("<user name=\"Eximel\"/>"));

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl(server.url("/"))
    .addConverterFactory(new QualifiedTypeConverterFactory(
        GsonConverterFactory.create(),
        SimpleXmlConverterFactory.create()))
    .build();
Service service = retrofit.create(Service.class);

User user1 = service.exampleJson().execute().body();

5.对Object进行Array操作

Object values;
(T) Array.get(values, i);   
Array.getLength(values);  

6.利用了方法上的各种信息

screenshot.png

7.Retrofit业务流程图

转自:Retrofit分析-漂亮的解耦套路

五.设计模式

摘在:Retrofit分析-经典设计模式案例,
作者: stay4it

1.外观模式

Retrofit给我们暴露的方法和类不多。核心类就是Retrofit,我们只管配置Retrofit,然后做请求。剩下的事情就跟上层无关了,只需要等待回调。这样大大降低了系统的耦合度。对于这种写法,我们叫外观模式(门面模式)。

几乎所有优秀的开源library都有一个门面。比如Glide.with(), ImageLoader.load(), Alamofire.request()。有个门面方便记忆,学习成本低,利于推广品牌。 Retrofit的门面就是retrofit.create()

当我们自己写的代码的时候尽量也要这样来做。

比如我们有一个独立并公用的模块,需要供其他模块来调用。比如download,location,socialshare等

最好我们写一个module,将所有相关的代码都放在这个module中。这是第一步。

第二步,为你的module提供一个漂亮的门面。比如下载的DownloadManager, 经纬度的LocationTracker, 社交分享的SocialManager。它们做为功能模块的入口,要尽量的简洁,方法命名好记易理解,类上要有完整的示例注释

第三步,闭门造车。不管你在里面干什么,外面都是不知道的,就像薛定谔的那只猫,外层不调用它,永远不知道它是否好用。
不过为了以后好维护,不给他人留坑,还是尽量写的工整一些。

2.建造者模式

 Retrofit retrofit = new Retrofit.Builder()
    .baseUrl(server.url("/"))
    .addConverterFactory(new StringConverterFactory())
    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
    .build();

3.动态代理

Java静态代理和动态代理介绍

再来说动态代理。以往的动态代理和静态代理使用的场景是类似的。都想在delegate调用方法前后做一些操作。如果我的代理类有很多方法,那我得额外写很多代码,所以这时候就引入了动态代理。通过动态设置delegate,可以处理不同代理的不同方法。看不懂没关系,直接上代码:

Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
     new InvocationHandler() {
          ....
          //为方法生成ServiceMethod
          ServiceMethod<Object, Object> serviceMethod =
                (ServiceMethod<Object, Object>) loadServiceMethod(method);
            OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
            //ServiceMethod返回真正可以进行请求的CallAdapter
            return serviceMethod.callAdapter.adapt(okHttpCall);
          }
        });
  }

4.抽象工厂模式

内部Converter,CallAdapter都用了抽象工厂模式, InterfaceFactory放到了一个类中,减少类文件的数量, Factory生成对应Interface的类,明确匹配关系.

 public interface Converter<F, T> {
  T convert(F value) throws IOException;
 
  abstract class Factory {
    
    public @Nullable Converter<ResponseBody, ?> responseBodyConverter(Type type,
        Annotation[] annotations, Retrofit retrofit) {
      return null;
    } 
}

5.适配器模式

如果你已经看过retrofit源码,很可能被CallAdapter玩坏。这个CallAdapter不是那么好理解。先抛开代码,我们来看看适配器模式。

Adapter简单来说,就是将一个已存在的东西转换成适合我们使用的东西。就比方说电源Adapter。出国旅游都要带转接头。比方说,RecyclerView里的Adapter是这么定义的。 Adapters provide a binding from an app-specific data set to views

再回来看看Retrofit,为什么我们需要转接头呢。那个被转换的是谁?我们看看CallAdapter的定义。Adapts a {@link Call} into the type of {@code T}. 这个CallOkHttpCall,它不能被我们直接使用吗?被转换后要去实现什么特殊的功能吗?

我们假设下。一开始,retrofit只打算在android上使用,那就通过静态代理ExecutorCallbackCall来切换线程。但是后来发现rxjava挺好用啊,这样就不需要Handler来切换线程了嘛。想要实现,那得转换一下。将OkHttpCall转换成rxjava(Scheduler)的写法。再后来又支持java8(CompletableFuture)甚至居然还有iOS支持。大概就是这样一个套路。当然我相信square的大神肯定一开始就考虑了这种情况,从而设计了CallAdapter。

适配器模式就是,已经存在的OkHttpCall,要被不同的标准,平台来调用。设计了一个接口CallAdapter,让其他平台都是做不同的实现来转换,这样不花很大的代价就能再兼容一个平台。666。

6.装饰模式

装饰模式跟静态代理很像。

每次一说装饰模式,就想成decorator,实际上叫wrapper更直观些。既然是wrapper,那就得有源的句柄,在构造wrapper时得把source作为参数传进来。wrapper了source,同样还wrapper其他功能。

代理模式,Proxy Delegate,实际上Delegate也不知道自己被代理了,Proxy伪装成Delegate来执行,既然是proxy,那proxy不应该提供delegate没有的public方法,以免被认出来。

抛开理论的描述,我们直接来看下面的代码。

你可以将ExecutorCallbackCall当作是Wrapper,而真正去执行请求的源Source是OkHttpCall。之所以要有个Wrapper类,是希望在源Source操作时去做一些额外操作。这里的操作就是线程转换,将子线程切换到主线程上去。

分析好文

Retrofit分析-漂亮的解耦套路
Retrofit分析-经典设计模式案例
Android主流网络请求开源库的对比(Android-Async-Http、Volley、OkHttp、Retrofit)
Android:手把手带你深入剖析 Retrofit 2.0 源码

目录
相关文章
|
1月前
|
数据采集 JavaScript 前端开发
实用工具推荐:适用于 TypeScript 网络爬取的常用爬虫框架与库
实用工具推荐:适用于 TypeScript 网络爬取的常用爬虫框架与库
|
3月前
|
消息中间件 NoSQL Linux
workFlow c++异步网络库编译教程与简介
搜狗公司C++服务器引擎,编程范式。支撑搜狗几乎所有后端C++在线服务,包括所有搜索服务,云输入法,在线广告等,每日处理数百亿请求。这是一个设计轻盈优雅的企业级程序引擎,可以满足大多数后端与嵌入式开发需求。 编程范式 结构化并发与任务隐藏回调与内存回收机制
53 0
|
3月前
|
安全 API Android开发
Android网络和数据交互: 解释Retrofit库的作用。
Android网络和数据交互: 解释Retrofit库的作用。
39 0
|
16天前
|
数据采集 大数据 数据安全/隐私保护
掌握网络抓取技术:利用RobotRules库的Perl下载器一览小红书的世界
本文探讨了使用Perl和RobotRules库在遵循robots.txt规则下抓取小红书数据的方法。通过分析小红书的robots.txt文件,配合亿牛云爬虫代理隐藏真实IP,以及实现多线程抓取,提高了数据采集效率。示例代码展示了如何创建一个尊重网站规则的数据下载器,并强调了代理IP稳定性和抓取频率控制的重要性。
掌握网络抓取技术:利用RobotRules库的Perl下载器一览小红书的世界
|
22天前
|
数据采集 网络协议 API
python中其他网络相关的模块和库简介
【4月更文挑战第4天】Python网络编程有多个流行模块和库,如requests提供简洁的HTTP客户端API,支持多种HTTP方法和自动处理复杂功能;Scrapy是高效的网络爬虫框架,适用于数据挖掘和自动化测试;aiohttp基于asyncio的异步HTTP库,用于构建高性能Web应用;Twisted是事件驱动的网络引擎,支持多种协议和异步编程;Flask和Django分别是轻量级和全栈Web框架,方便构建不同规模的Web应用。这些工具使网络编程更简单和高效。
|
1月前
|
数据采集 存储 Scala
挖掘网络宝藏:利用Scala和Fetch库下载Facebook网页内容
本文介绍了如何使用Scala和Fetch库下载Facebook网页内容,同时通过爬虫代理服务(以亿牛云为例)绕过网络限制。代码示例展示了配置代理服务器、多线程爬取及内容存储的过程。注意实际应用时需替换代理服务器配置和目标URL,并考虑应对复杂的反爬虫机制。此方法兼顾匿名性和效率。
挖掘网络宝藏:利用Scala和Fetch库下载Facebook网页内容
|
1月前
|
机器学习/深度学习 算法框架/工具 Python
如何使用Python的Keras库构建神经网络模型?
如何使用Python的Keras库构建神经网络模型?
9 0
|
1月前
|
Python
如何使用Python的Requests库进行网络请求和抓取网页数据?
如何使用Python的Requests库进行网络请求和抓取网页数据?
13 0
|
2月前
|
数据采集 安全 网络协议
构建网络下载器:Wt库指南让您轻松获取豆瓣网的美图
Wt(Web Toolkit)是一个用C编写的开源库,它可以让您使用C开发Web应用程序。Wt提供了一套丰富的组件,包括窗口、按钮、表单、图表、布局等,让您可以像使用GUI库一样,使用C++构建Web界面。 除了提供Web界面的组件,Wt还提供了一个网络模块,它可以让您使用C++进行网络编程,包括HTTP请求、响应、会话、Cookie等。这个网络模块非常适合用来开发网络爬虫,因为它可以让您方便地发送HTTP请求,获取网页的内容,解析HTML,提取所需的数据,保存到本地或数据库等。
|
2月前
|
XML 数据采集 存储
挖掘网络宝藏:R和XML库助你轻松抓取 www.sohu.com 图片
网络上有无数的图片资源,但是如何从特定的网站中快速地抓取图片呢?本文将介绍一种使用 R 语言和 XML 库的简单方法,让你可以轻松地从 www.sohu.com 网站上下载你感兴趣的图片。本文将涉及以下几个方面: ● 为什么选择 R 语言和 XML 库作为图片爬虫的工具? ● 如何使用 R 语言和 XML 库来访问、解析和提取网页上的图片链接? ● 如何使用代理 IP 技术,参考亿牛云爬虫代理的设置,避免被网站屏蔽或限制? ● 如何实现多线程技术,提高图片爬取的效率和速度? ● 如何将爬取到的图片保存到本地或云端,进行数据分析和可视化?