Retrofit分析-经典设计模式案例

如果你还不知道Retrofit,没关系,okhttp你总知道吧。retrofit就是对okhttp再做了一层封装。你只需要通过简单的配置就能顺利使用retrofit来做网络请求了。还没有使用过retrofit的小伙伴们,不妨尝尝鲜。

本篇是retrofit番外篇。只讲retrofit中的设计模式以及我个人的理解与延伸。如果你还没看过retrofit源码,不妨先看看这篇Retrofit分析-漂亮的解耦套路

还是先上图:


retrofit00.png

以前用的volley, async-http-lib, xUtils。这些libs基本都是上图这个workflow。向服务器请求API总共分三步。

  1. build request(API参数配置)
  2. executor(这里可以有很多变体,比如有无队列,进出顺序,线程管理)
  3. parse callback(解析数据,返回T给上层)

如今的retrofit也是换汤不换药的。也是这三步。

  1. 通过注解配置API参数
  2. CallAdapter(你可以把它理解成executor)
  3. Converter(解析数据并转换成T)

本来还应该有个CallFactory来切换具体的http client的。就像volley那样>=9使用HttpUrlConnection, <9使用HttpClient。不过我想都是square出品,彼此互推也是理所当然的啊。我相信以后okhttp肯定是唯一的请求client。

上面说CallAdapter可以理解成executor,具体是什么,我们下面讲具体设计模式时再详细讨论。

好,铺垫了这么多,我要开车了。Stay先带你看看沿途一些的设计模式,等到终点了再来看整体的架构。

先来说个最简单的设计模式。

外观模式(门面模式)

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

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

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

比如我们有一个独立并公用的模块,需要供其他模块来调用。比如downloadlocationsocialshare
最好我们写一个module,将所有相关的代码都放在这个module中。这是第一步。
第二步,为你的module提供一个漂亮的门面。比如下载的DownloadManager, 经纬度的LocationTracker, 社交分享的SocialManager。它们做为功能模块的入口,要尽量的简洁,方法命名好记易理解,类上要有完整的示例注释
第三步,闭门造车。不管你在里面干什么,外面都是不知道的,就像薛定谔的那只猫,外层不调用它,永远不知道它是否好用。
不过为了以后好维护,不给他人留坑,还是尽量写的工整一些。

装饰模式

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

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

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

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

retrofit02.png

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

简单的解释下,enqueue()方法是异步的,也就是说,当你调用OkHttpCall的enqueue方法,回调的callback是在子线程中的,如果你希望在主线程接受回调,那需要通过Handler转换到主线程上去。ExecutorCallbackCall就是用来干这个事。当然以上是原生retrofit使用的切换线程方式。如果你用rxjava,那就不会用到这个ExecutorCallbackCall而是RxJava的Call了。这里不展开。

动态代理

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

retrofit03.png

简而言之,动态代理就是拦截调用的那个方法,在方法前后来做一些操作。Retrofit里的动态代理比较巧妙。实际上它根本就没有delegate。因为这个方法没有真正的实现。使用动态代理,只是单纯的为了拿到这个method上所有的注解。所有的工作都是由proxy做了。比起我们总说代理就是打log要高明多了。

我以前自己写数据库框架时,也碰到这样的场景。一个类里有很多一对一,一对多关系。如果从db里fetch出来都去做初始化,那会非常影响性能。但如果不初始化,到使用时再去手动初始化就更麻烦了。怎么办呢?

    class A{
        private B b;
        private ArrayList<C> cs;
        
        public B getB(){
            return b;
        }
        
        public ArrayList<C> getCs(){
            return cs;
        }
    }

当类A里的get方法被invoke时,我就判断,这个类有没有被初始化,如果有,那就不做任何操作。如果没有,那得等会,我把数据从数据库中fetch出来给你赋值后,再去invoke。这个场景可以叫懒加载,可以套用AOP面向切面编程。

动态代理能实现这个需求吗?可以,但是支持的很糟糕。因为动态代理依赖接口实现,总不能将所有的pojo中的方法都申明到接口里吧?那真是要命了。

所以我用了种替代方案,既然是AOP,有个面向切面的框架AspectJ。你可以通过它来切入这些get方法,先判断有没初始化,然后再返回。

retrofit04.png

差不多就是这样,没有Proxy的概念,只是在编译时,把这些切面织入进去。对于pojo而言完全是透明的。是不是很6。不过这里也有很多其他的性能瓶颈,比如说我在第一次调用时,要先去数据库fetch,这也是耗时操作。这个先跳过,有机会再跟大家八一八,我那数据库框架是怎么撸出来的。

适配器模式

如果你已经看过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}. 这个Call是OkHttpCall,它不能被我们直接使用吗?被转换后要去实现什么特殊的功能吗?

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

retrofit06.png

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

策略模式?

在retrofit里,这个适配器模式不是那么明显。而且和其他模式交错在一起,所以看起来很麻烦。比如这个CallAdapter又夹杂着策略模式(仅是个人看法)。你可以看看Rxjava里如何去创建adapter的,它是根据api方法声明的returnType来创建具体的CallAdapter实例的。上代码你就明白了。

retrofit05.png

retrofit07.png

是不是很像根据不同的策略使用不同的算法?不同的returnType声明就是set不同的Strategy。

总结

来张提纲挈领的流程图,没保存的赶紧存起来。以后就能照着它自己开车了。

retrofit01.png

好,大概就将这么多啦。这些就是retrofit的解耦套路了。通过一系列的设计模式,封装思想来解耦,看到现在,其实retrofit就是一个负责调度的controller。先给retrofit配置好,让它能够正常工作。你给它一个方法调用,它就在内部开始运转。这个方法以前我消化过吗,没消化那就用一个ServiceMethod来解析它。解析后要用来配置一个request请求。但它自己搞不定这事啊,所以需要给它一个转接头,通过转接头来使用okhttpcall。请求是做好了,但是response它又不认识,
所以又请来convertor来帮忙,转换完毕之后才吐出一个我们最终要的那个对象。

终点站到啦,请下车。觉得老司机开的稳,坐的舒心。不妨再刷个卡吧。。滴滴。。

如果看文章不够过瘾,可以看Stay精心录制的视频Retrofit分析-漂亮的解耦套路,看完你再也不怕看不懂retrofit了。而且你还可以用Stay这种分析套路来轻松看懂其他源码。

扩展阅读:

  1. 关于Retrofit源码分析可以看我另外一篇文章
    Retrofit分析-漂亮的解耦套路

  2. 没耐心自己分析源码的同学,还可以参考Stay录制的视频版,包学包会: )
    Retrofit分析-漂亮的解耦套路(视频版)

  3. 另外怎么选择开源library,可以参考这么多开源框架,该用哪个好?

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

推荐阅读更多精彩内容