橫觀歷史
一點(diǎn)感概
記得當(dāng)年剛?cè)胄蠥ndroid迅皇,讓我記憶猶新的框架android-async-http昧辽,當(dāng)時(shí)用的不亦樂乎,隨著時(shí)間的變遷登颓,官方的新寵Volley誕生搅荞,不久的不久官方宣布自己放棄,坑爹框咙,Android 4.4后取具,HttpURLConnection底層實(shí)現(xiàn)改為OkHttp,隨即OkHttp是各個(gè)大牛封裝的根基扁耐,Retrofit最為知名暇检,可以說幾乎沒有人沒用過,后來不知道誰刮起了RxJava大風(fēng)婉称,變成了Retrofit+RxJava+OkHttp块仆,到目前為止我都很反感RxJava,框架固然很好王暗,但我們不合適悔据,不愛就是不愛,沒必要牽強(qiáng)俗壹。自從有了Coroutines協(xié)程科汗,算是找到了最優(yōu)解,為什么這么說呢绷雏?我們先來分析下這幾種實(shí)現(xiàn)方式
android-async-http
基于Apache的HttpClient庫構(gòu)建的Android異步網(wǎng)絡(luò)框架头滔,大致用法如下:
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();
}
};
}
我們暫且不提現(xiàn)在官方現(xiàn)在已經(jīng)不用HttpClient,框架本身有很多可以借鑒的優(yōu)秀設(shè)計(jì)涎显,放在當(dāng)初可謂是功能豐富坤检,非常穩(wěn)定,且bug極少期吓。我少啰嗦幾句早歇,直接看下一個(gè)實(shí)現(xiàn),最終我們?cè)俸暧^的看看,到底網(wǎng)絡(luò)框架的前世今生是什么走向箭跳。
VolleyPlus
VolleyPlus庫對(duì)Volley進(jìn)行的項(xiàng)目改進(jìn)以及完整的圖像緩存晨另,涉及使用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谱姓,加入了請(qǐng)求隊(duì)列(AsyncHttpClient也有線程池)拯刁,線程調(diào)度,緩存DiskLruCache逝段,支持的緩存類型:
- 網(wǎng)絡(luò)緩存
- 資源緩存
- 文件緩存
- 視頻緩存
- 內(nèi)容URI緩存
等等吧垛玻,也是給我們網(wǎng)絡(luò)層提供了不少的遍歷,接下來看看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目前的使用方式奶躯,毋庸置疑帚桩,它解決了開發(fā)中網(wǎng)絡(luò)層的很多問題,那為什么它會(huì)這么火呢嘹黔?它背后的本質(zhì)是什么账嚎?我這里啰嗦幾句哈,大家應(yīng)該都聽說過后端的開發(fā)框架Spring儡蔓,說到Spring郭蕉,你肯定會(huì)想到它設(shè)計(jì)的兩個(gè)核心概念,IOC控制反轉(zhuǎn)喂江,AOP面向切面編程召锈,Retrofit本質(zhì)上用IOC控制反轉(zhuǎn),你只需要定義接口获询,對(duì)象由框架本身負(fù)責(zé)管理涨岁,接口有個(gè)特點(diǎn)就是不變,利用IOC使得你不得不定義接口吉嚣,這樣間接提高代碼的穩(wěn)定性梢薪,是不是很佩服大佬的思想,在這感謝這些大佬們的努力尝哆,讓我們慢慢養(yǎng)成一個(gè)好的編程習(xí)慣秉撇,當(dāng)然我們也更應(yīng)該關(guān)注他們?cè)O(shè)計(jì)的思想,這樣我們?cè)谧约鹤隹蚣艿臅r(shí)候秋泄,是不是可以借鑒(copy)一下呢琐馆。好了,今天的主角上場(chǎng)了印衔,來看下最新的實(shí)現(xiàn)
Net(okhttp+coroutines+lifecycle)
Get
scopeNetLife {
// 該請(qǐng)求是錯(cuò)誤的路徑會(huì)在控制臺(tái)打印出錯(cuò)誤信息
Get<String>("error").await()
}
Post
scopeDialog {
tv_fragment.text = Post<String>("dialog") {
param("u_name", "drake")
param("pwd", "123456")
}.await()
}
這么簡(jiǎn)單的實(shí)現(xiàn)蘊(yùn)含了什么啡捶?
- 生命周期監(jiān)控
- 加載中顯示框
- 自動(dòng)序列化
- 異常處理
- 協(xié)程同步
- 非線程阻塞
優(yōu)勢(shì)很明顯,代碼的確很簡(jiǎn)潔奸焙,讓他們閱讀代碼不吃力,功能更完善,無需切換線程与帆,消除類似Rxjava的observeOn了赌,subscribe匿名內(nèi)部類的模板代碼,讓人更加注重業(yè)務(wù)邏輯的編寫玄糟。想必這就是我認(rèn)為的最優(yōu)解的一些理由吧勿她。你肯定也會(huì)說Rxjava也有這些功能(如:lifecycle),是的阵翎,你想怎么用是你的權(quán)利逢并,我不能左右。我只想說
郭卫,你餓了嗎砍聊?哈哈小結(jié)
經(jīng)過幾個(gè)版本的代碼對(duì)比,你是不是已經(jīng)察覺到贰军,網(wǎng)絡(luò)的前世今生玻蝌,讓我們一起總結(jié)下進(jìn)化的特點(diǎn)
- 簡(jiǎn)單化
- 生命感知力
- 更輕量級(jí)的調(diào)度
- 自我管理
- 高內(nèi)聚(隱藏實(shí)現(xiàn)細(xì)節(jié))
- 高可定制
這就是我們網(wǎng)絡(luò)框架的今生,你不重構(gòu)一下嗎词疼?親俯树。
老師說經(jīng)常教我們知其然知其所以然,那么我就帶你走進(jìn)Net框架實(shí)現(xiàn)的底層贰盗,讓我們看看作者都做了些什么的封裝许饿。
源碼分析
首先來看下項(xiàng)目的目錄結(jié)構(gòu)
項(xiàng)目主要分為兩個(gè)Lib
- Kalle https://github.com/yanzhenjie/Kalle Alibaba YanZhenjie 大佬寫的,對(duì)okhttp的再次包裝舵盈,詳細(xì)文檔見:https://yanzhenjie.com/Kalle/
- Net 此項(xiàng)目的核心實(shí)現(xiàn)
Kalle的源碼我們后期再分析米辐,雖然已經(jīng)一年零兩個(gè)月未維護(hù)了,但畢竟大佬做的书释,有很多可以學(xué)習(xí)的地方翘贮,這期我們針對(duì)強(qiáng)哥的框架Net做深入的分析,由于本人最近兩年一直在做Rom相關(guān)爆惧,對(duì)Jetpack相關(guān)的使用也不是特別的熟悉狸页,有什么不對(duì)的還請(qǐng)各位手下留情,強(qiáng)哥說他還對(duì)Kalle的源碼做了變更扯再,所以才拿到項(xiàng)目里維護(hù)芍耘,后期還會(huì)升級(jí)一下okhttp的版本,因?yàn)閅anZhenjie沒有升級(jí)最新的okhttp熄阻,當(dāng)然這不是我們的重點(diǎn)斋竞,下面我們來開始分析源碼
再來看下強(qiáng)哥總結(jié)的框架特性
- 協(xié)程
- 并發(fā)網(wǎng)絡(luò)請(qǐng)求
- 串行網(wǎng)絡(luò)請(qǐng)求
- 切換線程
- DSL編程
- 方便的緩存處理
- 自動(dòng)錯(cuò)誤信息吐司
- 自動(dòng)異常捕獲
- 自動(dòng)日志打印異常
- 自動(dòng)JSON解析
- 自動(dòng)處理下拉刷新和上拉加載
- 自動(dòng)處理分頁加載
- 自動(dòng)缺省頁
- 自動(dòng)處理生命周期
- 自動(dòng)處理加載對(duì)話框
- 協(xié)程作用域支持錯(cuò)誤和結(jié)束回調(diào)
- 支持先強(qiáng)制讀取緩存后網(wǎng)絡(luò)請(qǐng)求二次刷新
- 附帶超強(qiáng)輪循器
最近強(qiáng)哥還加了個(gè)優(yōu)先隊(duì)列,同時(shí)發(fā)起10個(gè)請(qǐng)求秃殉,優(yōu)先取第一個(gè)回來的結(jié)果坝初,而且代碼極其簡(jiǎn)潔好用浸剩。
Net源碼目錄
22個(gè)文件,不多不少鳄袍,剛剛好绢要,整體看目錄,根本不用深入看代碼拗小,我們就能清晰的知道這是干什么的重罪,這就是一種好的目錄結(jié)構(gòu),值得學(xué)習(xí)
- connect 真實(shí)的鏈接對(duì)象
- convert 序列化
- error 異常定義
- scope 作用域
- tag 日志tag
- time 時(shí)間相關(guān)(結(jié)合它的特性哀九,輪訓(xùn)應(yīng)該在這里有相關(guān)實(shí)現(xiàn))
- utils 工具類
- Net.kt 網(wǎng)絡(luò)請(qǐng)求的核心實(shí)現(xiàn)
- NetConfig.kt 全局配置文件
類圖
一張清晰可見的類圖剿配,是分析一個(gè)源碼有利的手段,隨我將其源碼類圖搞個(gè)明白阅束,請(qǐng)看下圖
通過類圖呼胚,我們可以清晰看到,該框架源碼的類結(jié)構(gòu)围俘,我大致分以下模塊砸讳,好統(tǒng)一分析
- kalle擴(kuò)展支持部分:ConnectFactory、Converter界牡、NetException
- 作用域部分:CoroutineScope結(jié)合Android Lifecycle的擴(kuò)展簿寂,以及Sate、Page宿亡、Dialog對(duì)NetCoroutineScope的實(shí)現(xiàn)
- 動(dòng)態(tài)擴(kuò)展部分常遂,這是框架的核心:Net,組合協(xié)程挽荠、Kalle實(shí)現(xiàn)新的網(wǎng)絡(luò)框架
- 配置克胳、工具部分:NetConfig、Utils(省略未畫)圈匆、Interval
接下來漠另,詳細(xì)看下各個(gè)模塊的實(shí)現(xiàn)
kalle擴(kuò)展支持部分
ConnectFactory只有一個(gè)功能:就是創(chuàng)建一個(gè)Connection對(duì)象,參數(shù)是Request跃赚。這里為什么這樣做呢笆搓?其實(shí)是為了能兼容不同的URLConnection,強(qiáng)哥使用的okhttp纬傲,自然使用HttpClient
Converter就是大家熟悉的Response處理满败,這里有個(gè)巧妙的設(shè)計(jì),將接口返回的數(shù)據(jù)叹括,通過傳入的code作為key算墨,默認(rèn)是code,取到業(yè)務(wù)定義的code碼汁雷,與傳入的success參數(shù)對(duì)比净嘀,如果一樣就是成功报咳,然后parsebody,如果返回其他面粮,則拋出失敗的結(jié)果少孝。
NetException 異常對(duì)于一個(gè)框架來說幾乎不可避免继低,自定義的異常信息熬苍,有利于問題的歸類和追蹤。上圖中就是作者自定義的RequestParamsException請(qǐng)求參數(shù)異常袁翁、ServerResponseException服務(wù)器異常柴底,這個(gè)代碼就不用看了,大家肯定是明白了粱胜,下面來看作用域部分
作用域部分
作用域都繼承自CoroutineScope柄驻,CoroutineScope持有CoroutineContext上下文,其實(shí)不難理解焙压,就像Android的Context鸿脓,分別有Applicaition Context和Activity Context,其實(shí)就是標(biāo)示出不同的生命周期涯曲,那么順著這個(gè)理解就好說了野哭,作者抽象AndroidScope,其實(shí)就是為了監(jiān)聽Lifecycle的生命周期幻件,然后在默認(rèn)的ON_DESTROY函數(shù)回調(diào)中cancel掉協(xié)程拨黔,如下圖代碼所示
看到了吧,繼承CoroutineScope需要實(shí)現(xiàn)CoroutineContext對(duì)吧绰沥,那這里的context是啥呢篱蝇?
協(xié)程上下文包含當(dāng)前協(xié)程scope的信息,如CoroutineDispatcher徽曲,CoroutineExceptionHandler零截,Job,其實(shí)這三個(gè)都是繼承實(shí)現(xiàn)CoroutineContext.Element秃臣,而這個(gè)+號(hào)不是我們以為的加號(hào)涧衙,點(diǎn)擊后看到如下源碼,這里巧妙的運(yùn)用操作符重載甜刻,不理解概念可以點(diǎn)擊該鏈接:https://www.kotlincn.net/docs/reference/operator-overloading.html 學(xué)習(xí)绍撞。
我們先不研究這樣做的用意哈,這里你要知道的是得院,該協(xié)程上下文承載了三個(gè)對(duì)象
- Dispatchers.Main 主線程
- exceptionHandler 異常捕獲
- SupervisorJob 需要執(zhí)行的協(xié)程
SupervisorJob 它類似于常規(guī)的 Job傻铣,唯一的不同是:SupervisorJob 的取消只會(huì)向下傳播∠榻剩看下官方的例子
import kotlinx.coroutines.*
fun main() = runBlocking {
val supervisor = SupervisorJob()
with(CoroutineScope(coroutineContext + supervisor)) {
// 啟動(dòng)第一個(gè)子作業(yè)——這個(gè)示例將會(huì)忽略它的異常(不要在實(shí)踐中這么做7侵蕖)
val firstChild = launch(CoroutineExceptionHandler { _, _ -> }) {
println("The first child is failing")
throw AssertionError("The first child is cancelled")
}
// 啟動(dòng)第二個(gè)子作業(yè)
val secondChild = launch {
firstChild.join()
// 取消了第一個(gè)子作業(yè)且沒有傳播給第二個(gè)子作業(yè)
println("The first child is cancelled: ${firstChild.isCancelled}, but the second one is still active")
try {
delay(Long.MAX_VALUE)
} finally {
// 但是取消了監(jiān)督的傳播
println("The second child is cancelled because the supervisor was cancelled")
}
}
// 等待直到第一個(gè)子作業(yè)失敗且執(zhí)行完成
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
其實(shí)我沒怎么理解向下傳播鸭限,哪位大佬能解釋解釋,我們接著往下分析两踏。?
作者抽象這些高級(jí)函數(shù)败京,用于實(shí)現(xiàn)dsl的效果如:
catch用于捕獲協(xié)程異常信息處理,finally是在invokeOnCompletion里觸發(fā)梦染,invokeOnCompletion在協(xié)程進(jìn)入完成狀態(tài)時(shí)觸發(fā)赡麦,包括異常和正常完成。
好滴帕识,AndroidScope大致就分析完了泛粹,我們來簡(jiǎn)單總結(jié)一下
- AndroidScope 在lifecycle ON_DESTROY時(shí) 自動(dòng)cancel
- 可以監(jiān)聽異常catch,可以實(shí)現(xiàn)finally
- 主線程執(zhí)行肮疗,作用域內(nèi)可以刷新UI晶姊,不用切換線程
就這些,再來看下NetCoroutineScope伪货,它是網(wǎng)絡(luò)框架的擴(kuò)展重心叫搁,NetCoroutineScope同樣的具有自動(dòng)取消的邏輯欠橘,如:
看下面的變量,我們其實(shí)能知道,NetCoroutineScope主要是擴(kuò)展了網(wǎng)絡(luò)的緩存策略雹洗,需不需要緩存倾剿,是否緩存成功等等檀夹,還有個(gè)animate蛋逾,這里看作者注釋,是控制是否在緩存成功后依然顯示加載動(dòng)畫
然后覆蓋launch函數(shù)懊悯,將邏輯插入到里面蜓谋,其實(shí)這里違背了里氏替換原則,子類盡量不要重寫父類的方法炭分,繼承包含這樣一層含義:父類中凡是已經(jīng)實(shí)現(xiàn)好的方法(相對(duì)于抽象方法而言)桃焕,實(shí)際上是在設(shè)定一系列的規(guī)范和契約,雖然它不強(qiáng)制要求所有的子類必須遵從這些契約捧毛,但是如果子類對(duì)這些非抽象方法任意修改观堂,就會(huì)對(duì)整個(gè)繼承體系造成破壞。而里氏替換原則就是表達(dá)了這一層含義呀忧。
然后就是cache配置的實(shí)現(xiàn)
用例:
用起來簡(jiǎn)單的一批
接下來就是StateCoroutineScope师痕、PageCoroutineScope、DialogCoroutineScope而账,這三個(gè)我就不一一解讀了胰坟,同樣的套路,對(duì)實(shí)際業(yè)務(wù)中的高發(fā)場(chǎng)景做的高度抽象泞辐。如:StateCoroutineScope是對(duì)StateLayout的擴(kuò)展處理笔横,在StateLayout onViewDetachedFromWindow時(shí) ?自動(dòng)cancel()掉協(xié)程竞滓,等等吧。
Net的核心部分吹缔,動(dòng)態(tài)擴(kuò)展
不啰嗦商佑,直接看代碼哦
- 擴(kuò)展CoroutineScope,添加get函數(shù)
- 內(nèi)連函數(shù)厢塘,reified修飾M真泛型茶没,真泛型不理解的可以看大佬的掘金介紹:https://juejin.im/post/6844904030662033422
- 執(zhí)行完后對(duì)uid做cancel或者remove處理,釋放緩存
- 結(jié)合NetConfig的host+path俗冻,形成完整的請(qǐng)求路徑
- SimpleUrlRequest創(chuàng)建新的請(qǐng)求
- async(Dispatchers.IO) 子線程的網(wǎng)絡(luò)請(qǐng)求
Post同理礁叔,其實(shí)差別就在請(qǐng)求參數(shù)牍颈,其他Head迄薄、Options 就不用分析嘍
分析到這里,我們?cè)倏偨Y(jié)一發(fā)
- 作用域部分繼承擴(kuò)展自CoroutineScope
- Net部分動(dòng)態(tài)擴(kuò)展自CoroutineScope
兩部分好像有關(guān)聯(lián)煮岁,其實(shí)沒有任何的代碼耦合讥蔽,這也是非常值得學(xué)習(xí)的地方。這樣做的好處其實(shí)就是限制了請(qǐng)求在作用域之外画机,造成代碼的不規(guī)范冶伞。如圖,在作用域外根本請(qǐng)求不了
好了步氏,這部分基本就結(jié)束了响禽,接下來看下源碼中最后一部分
配置、工具部分
NetConfig對(duì)象緩存網(wǎng)絡(luò)的配置荚醒,如域名芋类,app上下文,彈窗界阁,錯(cuò)誤侯繁,StateLayout的全局缺省頁
- initNet 初始化
- onError 全局錯(cuò)誤信息處理
- onStateError 缺省頁處理
用法:
在Application onCreate函數(shù)中初始化就行了,就這樣就完了泡躯,沒沒沒贮竟,還有,來看個(gè)例子
為什么有個(gè)ScopeNetLife较剃,不應(yīng)該是NetCoroutineScope嗎咕别?對(duì)的其實(shí)就是它,實(shí)現(xiàn)在這里写穴,我們一起來看下
這個(gè)是Utils中的ScopeUtils類惰拱,同樣是熟悉的動(dòng)態(tài)擴(kuò)展,簡(jiǎn)單的擴(kuò)展确垫,實(shí)現(xiàn)DSL風(fēng)格就是這里的結(jié)果弓颈。
好了帽芽,基本上都講完了吧,經(jīng)過一系列的分析翔冀,你是不是已經(jīng)按耐不住自己要去體驗(yàn)了呢导街?
源碼地址
強(qiáng)哥首頁:https://github.com/liangjingkanji
Net源碼:https://github.com/liangjingkanji/Net
歡迎關(guān)注騷擾哦,聽說強(qiáng)哥最近有大動(dòng)作纤子,未來Net會(huì)更加的好用搬瑰,也希望你能喜歡這次講解,辛苦你點(diǎn)個(gè)贊唄控硼,感謝泽论。
作者
i校長(zhǎng)
簡(jiǎn)書 http://www.reibang.com/u/77699cd41b28
掘金 https://juejin.im/user/131597127135687
個(gè)人網(wǎng)站 http://jetpack.net.cn 、 http://ibaozi.cn