横观历史
一点感概
记得当年刚入行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