共计 9011 个字符,预计需要花费 23 分钟才能阅读完成。
横观历史
一点感概
记得当年刚入行 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