横观历史
一点感概
记得当年刚入行Android,让我历历在目的框架android-async-http,过后用的不可开交,随着工夫的变迁,官网的新宠Volley诞生,不久的不久官网发表本人放弃,坑爹,Android 4.4后,HttpURLConnection底层实现改为OkHttp,随即OkHttp是各个大牛封装的根基,Retrofit最为出名,能够说简直没有人没用过,起初不晓得谁刮起了RxJava大风,变成了Retrofit+RxJava+OkHttp,到目前为止我都很恶感RxJava,框架诚然很好,但咱们不适合,不爱就是不爱,没必要牵强。自从有了Coroutines协程,算是找到了最优解,为什么这么说呢?咱们先来剖析下这几种实现形式
android-async-http
基于Apache的HttpClient库构建的Android异步网络框架,大抵用法如下:
private AsyncHttpClient asyncHttpClient = new AsyncHttpClient() { @Override protected AsyncHttpRequest newAsyncHttpRequest(DefaultHttpClient client, HttpContext httpContext, HttpUriRequest uriRequest, String contentType, ResponseHandlerInterface responseHandler, Context context) { AsyncHttpRequest httpRequest = getHttpRequest(client, httpContext, uriRequest, contentType, responseHandler, context); return httpRequest == null ? super.newAsyncHttpRequest(client, httpContext, uriRequest, contentType, responseHandler, context) : httpRequest; } }; @Override public RequestHandle executeSample(AsyncHttpClient client, String URL, Header[] headers, HttpEntity entity, ResponseHandlerInterface responseHandler) { return client.get(this, URL, headers, null, responseHandler); } @Override public String getDefaultURL() { return "https://httpbin.org/get"; } @Override public ResponseHandlerInterface getResponseHandler() { return new AsyncHttpResponseHandler() { @Override public void onStart() { clearOutputs(); } @Override public void onSuccess(int statusCode, Header[] headers, byte[] response) { debugHeaders(LOG_TAG, headers); debugStatusCode(LOG_TAG, statusCode); debugResponse(LOG_TAG, new String(response)); } @Override public void onFailure(int statusCode, Header[] headers, byte[] errorResponse, Throwable e) { debugHeaders(LOG_TAG, headers); debugStatusCode(LOG_TAG, statusCode); debugThrowable(LOG_TAG, e); if (errorResponse != null) { debugResponse(LOG_TAG, new String(errorResponse)); } } @Override public void onRetry(int retryNo) { Toast.makeText(GetSample.this, String.format(Locale.US, "Request is retried, retry no. %d", retryNo), Toast.LENGTH_SHORT) .show(); } }; }
咱们暂且不提当初官网当初曾经不必HttpClient,框架自身有很多能够借鉴的优良设计,放在当初堪称是功能丰富,十分稳固,且bug极少。我少啰嗦几句,间接看下一个实现,最终咱们再宏观的看看,到底网络框架的前世今生是什么走向。
VolleyPlus
VolleyPlus库对Volley进行的我的项目改良以及残缺的图像缓存,波及应用RequestQueue,RequestTickle和Request
RequestQueue mRequestQueue = Volley.newRequestQueue(getApplicationContext());StringRequest stringRequest = new StringRequest(Request.Method.GET, url, new Response.Listener<String>() { @Override public void onResponse(String response) { .... }}, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { .... }});mRequestQueue.add(stringRequest);
比起AsyncHttpClient,退出了申请队列(AsyncHttpClient也有线程池),线程调度,缓存DiskLruCache,反对的缓存类型:
- 网络缓存
- 资源缓存
- 文件缓存
- 视频缓存
- 内容URI缓存
等等吧,也是给咱们网络层提供了不少的遍历,接下来看看Retrofit+RxJava
Retrofit+RxJava
public interface ApiService { @GET("demo") Observable<Demo> getDemo(@Query("start") int start, @Query("count") int count);} // 应用例子 apiService.getDemo(0, 10) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Observer<Demo>() { @Override public void onSubscribe(Disposable d) { Log.d(TAG, "onSubscribe: "); } @Override public void onNext(Demo demo) { Log.d(TAG, "onNext: " + demo.getTitle()); List<Subjects> list = demo.getSubjects(); for (Subjects sub : list) { Log.d(TAG, "onNext: " + sub.getId() + "," + sub.getYear() + "," + sub.getTitle()); } } @Override public void onError(Throwable e) { Log.e(TAG, "onError: " + e.getMessage()); } @Override public void onComplete() { Log.d(TAG, "onComplete: Over!"); } });
这种仿佛是很多很多App目前的应用形式,毋庸置疑,它解决了开发中网络层的很多问题,那为什么它会这么火呢?它背地的实质是什么?我这里啰嗦几句哈,大家应该都据说过后端的开发框架Spring,说到Spring,你必定会想到它设计的两个外围概念,IOC管制反转,AOP面向切面编程,Retrofit实质上用IOC管制反转,你只须要定义接口,对象由框架自身负责管理,接口有个特点就是不变,利用IOC使得你不得不定义接口,这样间接进步代码的稳定性,是不是很拜服大佬的思维,在这感激这些大佬们的致力,让咱们缓缓养成一个好的编程习惯,当然咱们也更应该关注他们设计的思维,这样咱们在本人做框架的时候,是不是能够借鉴(copy)一下呢。好了,明天的配角上场了,来看下最新的实现
Net(okhttp+coroutines+lifecycle)
Get
scopeNetLife { // 该申请是谬误的门路会在控制台打印出错误信息 Get<String>("error").await() }
Post
scopeDialog { tv_fragment.text = Post<String>("dialog") { param("u_name", "drake") param("pwd", "123456") }.await() }
这么简略的实现蕴含了什么?
- 生命周期监控
- 加载中显示框
- 主动序列化
- 异样解决
- 协程同步
- 非线程阻塞
劣势很显著,代码确实很简洁,让他们浏览代码不吃力,性能更欠缺,无需切换线程,打消相似Rxjava的observeOn,subscribe匿名外部类的模板代码,让人更加重视业务逻辑的编写。想必这就是我认为的最优解的一些理由吧。你必定也会说Rxjava也有这些性能(如:lifecycle),是的,你想怎么用是你的权力,我不能左右。我只想说
,你饿了吗?哈哈
小结
通过几个版本的代码比照,你是不是曾经察觉到,网络的前世今生,让咱们一起总结下进化的特点
- 简单化
- 生命感知力
- 更轻量级的调度
- 自我管理
- 高内聚(暗藏实现细节)
- 高可定制
这就是咱们网络框架的今生,你不重构一下吗?亲。
老师说常常教咱们知其然知其所以然,那么我就带你走进Net框架实现的底层,让咱们看看作者都做了些什么的封装。
源码剖析
首先来看下我的项目的目录构造
我的项目次要分为两个Lib
- Kalle https://github.com/yanzhenjie/Kalle Alibaba YanZhenjie 大佬写的,对okhttp的再次包装,具体文档见:https://yanzhenjie.com/Kalle/
- Net 此我的项目的外围实现
Kalle的源码咱们前期再剖析,尽管曾经一年零两个月未保护了,但毕竟大佬做的,有很多能够学习的中央,这期咱们针对强哥的框架Net做深刻的剖析,因为自己最近两年始终在做Rom相干,对Jetpack相干的应用也不是特地的相熟,有什么不对的还请各位手下留情,强哥说他还对Kalle的源码做了变更,所以才拿到我的项目里保护,前期还会降级一下okhttp的版本,因为YanZhenjie没有降级最新的okhttp,当然这不是咱们的重点,上面咱们来开始剖析源码
再来看下强哥总结的框架个性
- 协程
- 并发网络申请
- 串行网络申请
- 切换线程
- DSL编程
- 不便的缓存解决
- 主动错误信息吐司
- 主动异样捕捉
- 主动日志打印异样
- 主动JSON解析
- 主动解决下拉刷新和上拉加载
- 主动解决分页加载
- 主动缺省页
- 主动解决生命周期
- 主动解决加载对话框
- 协程作用域反对谬误和完结回调
- 反对先强制读取缓存后网络申请二次刷新
- 附带超强轮循器
最近强哥还加了个优先队列,同时发动10个申请,优先取第一个回来的后果,而且代码极其简洁好用。
Net源码目录
22个文件,不多不少,刚刚好,整体看目录,基本不必深刻看代码,咱们就能清晰的晓得这是干什么的,这就是一种好的目录构造,值得学习
- connect 实在的链接对象
- convert 序列化
- error 异样定义
- scope 作用域
- tag 日志tag
- time 工夫相干(联合它的个性,轮训应该在这里有相干实现)
- utils 工具类
- Net.kt 网络申请的外围实现
- NetConfig.kt 全局配置文件
类图
一张清晰可见的类图,是剖析一个源码无利的伎俩,随我将其源码类图搞个明确,请看下图
通过类图,咱们能够清晰看到,该框架源码的类构造,我大抵分以下模块,好对立剖析
- kalle扩大反对局部:ConnectFactory、Converter、NetException
- 作用域局部:CoroutineScope联合Android Lifecycle的扩大,以及Sate、Page、Dialog对NetCoroutineScope的实现
- 动静扩大局部,这是框架的外围:Net,组合协程、Kalle实现新的网络框架
- 配置、工具局部:NetConfig、Utils(省略未画)、Interval
接下来,具体看下各个模块的实现
kalle扩大反对局部
ConnectFactory只有一个性能:就是创立一个Connection对象,参数是Request。这里为什么这样做呢?其实是为了能兼容不同的URLConnection,强哥应用的okhttp,天然应用HttpClient
Converter就是大家相熟的Response解决,这里有个奇妙的设计,将接口返回的数据,通过传入的code作为key,默认是code,取到业务定义的code码,与传入的success参数比照,如果一样就是胜利,而后parsebody,如果返回其余,则抛出失败的后果。
NetException 异样对于一个框架来说简直不可避免,自定义的异样信息,有利于问题的归类和追踪。上图中就是作者自定义的RequestParamsException申请参数异样、ServerResponseException服务器异样,这个代码就不必看了,大家必定是明确了,上面来看作用域局部
作用域局部
作用域都继承自CoroutineScope,CoroutineScope持有CoroutineContext上下文,其实不难理解,就像Android的Context,别离有Applicaition Context和Activity Context,其实就是标示出不同的生命周期,那么顺着这个了解就好说了,作者形象AndroidScope,其实就是为了监听Lifecycle的生命周期,而后在默认的ON_DESTROY函数回调中cancel掉协程,如下图代码所示
看到了吧,继承CoroutineScope须要实现CoroutineContext对吧,那这里的context是啥呢?
协程上下文蕴含以后协程scope的信息,如CoroutineDispatcher,CoroutineExceptionHandler,Job,其实这三个都是继承实现CoroutineContext.Element,而这个+号不是咱们认为的加号,点击后看到如下源码,这里奇妙的使用操作符重载,不了解概念能够点击该链接:https://www.kotlincn.net/docs/reference/operator-overloading.html 学习。
咱们先不钻研这样做的用意哈,这里你要晓得的是,该协程上下文承载了三个对象
- Dispatchers.Main 主线程
- exceptionHandler 异样捕捉
- SupervisorJob 须要执行的协程
SupervisorJob 它相似于惯例的 Job,惟一的不同是:SupervisorJob 的勾销只会向下流传。看下官网的例子
import kotlinx.coroutines.*fun main() = runBlocking { val supervisor = SupervisorJob() with(CoroutineScope(coroutineContext + supervisor)) { // 启动第一个子作业——这个示例将会疏忽它的异样(不要在实践中这么做!) val firstChild = launch(CoroutineExceptionHandler { _, _ -> }) { println("The first child is failing") throw AssertionError("The first child is cancelled") } // 启动第二个子作业 val secondChild = launch { firstChild.join() // 勾销了第一个子作业且没有流传给第二个子作业 println("The first child is cancelled: ${firstChild.isCancelled}, but the second one is still active") try { delay(Long.MAX_VALUE) } finally { // 然而勾销了监督的流传 println("The second child is cancelled because the supervisor was cancelled") } } // 期待直到第一个子作业失败且执行实现 firstChild.join() println("Cancelling the supervisor") supervisor.cancel() secondChild.join() }}
这段代码的输入如下:
The first child is failing
The first child is cancelled: true, but the second one is still active
Cancelling the supervisor
The second child is cancelled because
其实我没怎么了解向下流传,哪位大佬能解释解释,咱们接着往下剖析。
作者形象这些高级函数,用于实现dsl的成果如:
catch用于捕捉协程异样信息处理,finally是在invokeOnCompletion里触发,invokeOnCompletion在协程进入实现状态时触发,包含异样和失常实现。
好滴,AndroidScope大抵就剖析完了,咱们来简略总结一下
- AndroidScope 在lifecycle ON_DESTROY时 主动cancel
- 能够监听异样catch,能够实现finally
- 主线程执行,作用域内能够刷新UI,不必切换线程
就这些,再来看下NetCoroutineScope,它是网络框架的扩大重心,NetCoroutineScope同样的具备主动勾销的逻辑,如:
看上面的变量,咱们其实能晓得,NetCoroutineScope次要是扩大了网络的缓存策略,需不需要缓存,是否缓存胜利等等,还有个animate,这里看作者正文,是管制是否在缓存胜利后仍然显示加载动画
而后笼罩launch函数,将逻辑插入到外面,其实这里违反了里氏替换准则,子类尽量不要重写父类的办法,继承蕴含这样一层含意:父类中但凡曾经实现好的办法(绝对于形象办法而言),实际上是在设定一系列的标准和契约,尽管它不强制要求所有的子类必须听从这些契约,然而如果子类对这些非形象办法任意批改,就会对整个继承体系造成毁坏。而里氏替换准则就是表白了这一层含意。
而后就是cache配置的实现
用例:
用起来简略的一批
接下来就是StateCoroutineScope、PageCoroutineScope、DialogCoroutineScope,这三个我就不一一解读了,同样的套路,对理论业务中的高发场景做的高度形象。如:StateCoroutineScope是对StateLayout的扩大解决,在StateLayout onViewDetachedFromWindow时 主动cancel()掉协程,等等吧。
Net的外围局部,动静扩大
不啰嗦,间接看代码哦
- 扩大CoroutineScope,增加get函数
- 内连函数,reified润饰M真泛型,真泛型不了解的能够看大佬的掘金介绍:https://juejin.im/post/6844904030662033422
- 执行完后对uid做cancel或者remove解决,开释缓存
- 联合NetConfig的host+path,造成残缺的申请门路
- SimpleUrlRequest创立新的申请
- async(Dispatchers.IO) 子线程的网络申请
Post同理,其实差异就在申请参数,其余Head、Options 就不必剖析喽
剖析到这里,咱们再总结一发
- 作用域局部继承扩大自CoroutineScope
- Net局部动静扩大自CoroutineScope
两局部如同有关联,其实没有任何的代码耦合,这也是十分值得学习的中央。这样做的益处其实就是限度了申请在作用域之外,造成代码的不标准。如图,在作用域外基本申请不了
好了,这部分根本就完结了,接下来看下源码中最初一部分
配置、工具局部
NetConfig对象缓存网络的配置,如域名,app上下文,弹窗,谬误,StateLayout的全局缺省页
- initNet 初始化
- onError 全局谬误信息处理
- onStateError 缺省页解决
用法:
在Application onCreate函数中初始化就行了,就这样就完了,没没没,还有,来看个例子
为什么有个ScopeNetLife,不应该是NetCoroutineScope吗?对的其实就是它,实现在这里,咱们一起来看下
这个是Utils中的ScopeUtils类,同样是相熟的动静扩大,简略的扩大,实现DSL格调就是这里的后果。
好了,基本上都讲完了吧,通过一系列的剖析,你是不是曾经按耐不住本人要去体验了呢?
源码地址
强哥首页:https://github.com/liangjingkanji
Net源码:https://github.com/liangjingkanji/Net
欢送关注骚扰哦,据说强哥最近有大动作,将来Net会更加的好用,也心愿你能喜爱这次解说,辛苦你点个赞呗,感激。
作者
i校长
简书 https://www.jianshu.com/u/77699cd41b28
掘金 https://juejin.im/user/131597127135687
集体网站 http://jetpack.net.cn 、 http://ibaozi.cn