前言
????在Android開發(fā)中蝴光,網(wǎng)絡請求是每個開發(fā)者的必備技能俊嗽。當前也有很多優(yōu)秀墨辛、開源的網(wǎng)絡請求庫。例如:
其中Retrofit是對OkHttp的封裝癣防,Android-async-http是對HttpClient的封裝蜗巧,利用這些網(wǎng)絡庫開發(fā)者可以極大提升編碼效率。即便這些優(yōu)秀的網(wǎng)絡庫可以很方便進行網(wǎng)絡請求蕾盯,但大多數(shù)團隊依舊要搭建App網(wǎng)絡層幕屹,甚至把網(wǎng)絡層單獨封裝成庫使用,為什么呢级遭?所以本文首先就要討論:
??1 為什么要搭建App網(wǎng)絡層呢望拖?
????當知道搭建網(wǎng)絡層的必要性之后,便摩拳擦掌準備去大干一番挫鸽,但很快便面對一個問題:
??2 如何一步步搭建App網(wǎng)絡層呢说敏?
????終于搭建好了網(wǎng)絡層,但使用時肯定能發(fā)現(xiàn)不少bug和可以優(yōu)化的地方丢郊,那么:
??3 應該用什么樣的思想來指導改進網(wǎng)絡層呢盔沫?
在回答上述問題之前,先了解下網(wǎng)絡請求的基本流程:
網(wǎng)絡請求基本流程
????網(wǎng)絡請求的實質(zhì)是去查看枫匾、修改遠程計算機(包括服務器)上的信息架诞,僅從客戶端來看基本流程如下:
????如圖示,網(wǎng)絡請求的基本流程就是如此簡單干茉,和把大象放入冰箱一樣谴忧,都是三步。接下來我們在代碼級別來看看:
如何進行網(wǎng)絡請求
??以使用OkHttp框架訪問百度首頁為例子
//構造一個HttpClient 相當于設置個人郵箱角虫。
OkHttpClient client=new OkHttpClient();
//創(chuàng)建Request 對象沾谓,相當于寫信。
Request request = new Request.Builder()
.url("http://www.baidu.com")
.build()
//將Request封裝為call上遥,相當于把信放進郵箱搏屑,成為設置后待發(fā)送的信件
Call call = client.newCall(request);
// 放置到請求隊列骤宣、開始發(fā)送并等待回復忆首,相當于郵箱開始發(fā)動信件咒劲,并等待對方回復圈纺。
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
// 當請求被取消檐薯、連接中斷顶籽、找不到服務器等問題會調(diào)用這個接口
}
@Override
public void onResponse(Call call, final Response response) throws IOException {
// 遠程服務器成功返回調(diào)用
final String res = response.body().string();
runOnUiThread(new Runnable() {
@Override
public void run() {
Log.e("TAG"," "+res);
}
});
}
});
}
????由上可以看到用OkHttp框架進行網(wǎng)絡請求邏輯清晰簡單仔夺,跟我們用郵件跟朋友交流差不多扼雏。首先是設置郵箱(如果沒有什么特殊需求就用默認設置燃异、如上文)+寫郵件內(nèi)容携狭,然后把寫好郵件后放到設置好的郵箱里面,最后點擊發(fā)送回俐,等待朋友的回復逛腿,當朋友回復了就去查看處理稀并。
為什么要搭建網(wǎng)絡層
????知道如何用OkHttp后很興奮,于是用這一套開始了網(wǎng)絡請求之旅单默,so easy5饩佟!復制-粘貼-修改搁廓,復制-粘貼-修改引颈,復制-粘貼-修改...終于做了七八個網(wǎng)絡請求,一看任務量完成了六分之一境蜕,啊--累死寶寶了r !
????不行了不行了粱年,要喝杯luckin coffee鼓舞下士氣售滤,說走就走,喝著咖啡想著回來就一口氣加班搞定它逼泣。突然想起OOP重要原則-代碼復用原則,一拍腦子我TM真是個憨貨趴泌,把這些請求的共同部分提取出來,對外提供更簡單的接口拉庶、這樣用著方便不容易出錯、以后有問題修改工作量也大大減少了秃励,這就是我們搭建網(wǎng)絡層第一個原因:
????一氏仗、近似業(yè)務模型的代碼復用--方便使用和修改;
????說干就干夺鲜,擼起袖子正準備上場皆尔,再次靈光一閃,不對不對币励,這次要好好思考下整個完美的慷蠕,半途而廢啥的最浪費時間了!
????這里要說明下食呻,App網(wǎng)絡層是我們抽離出來的一個單獨模塊流炕,所以網(wǎng)絡層搭建跟設計實現(xiàn)一個單獨的網(wǎng)絡請求庫基本是一樣的!
????做事第一步向優(yōu)秀同行學習仅胞,去github上看了star比較多的一些OkHttp庫封裝每辟,整理了下功能列表:
- 一般的get請求
- 一般的post請求
- 基于Http Post的文件上傳(類似表單)
- 文件下載/加載圖片
- 上傳下載的進度回調(diào)
- 支持取消某個請求
- 支持自定義Callback
- 支持HEAD、DELETE干旧、PATCH渠欺、PUT
- 支持session的保持
- 支持自簽名網(wǎng)站https的訪問,提供方法設置下證書就行
- 支持RxJava
- 支持自定義緩存策略
????這些網(wǎng)絡庫是很好參考借鑒椎眯,但是挠将,它們跟我們的業(yè)務結合不緊密胳岂、直接用還是會造成:
????1 每個網(wǎng)絡請求都要加上業(yè)務邏輯;
????2 有不少多余功能舔稀,導致網(wǎng)絡層很大旦万,可能影響效率 ;
????3 團隊特殊要求達不到镶蹋,比如利用三方實現(xiàn)DNS防劫持成艘。
????所以需要仔細去分析業(yè)務、梳理網(wǎng)絡請求類型贺归。很快我們發(fā)現(xiàn)需要四種緩存策略:立即請求淆两、緩存10s、緩存1h拂酣、緩存24h秋冰,所以搭建網(wǎng)絡層第二個原因也是功能點:
????二、 全局婶熬,全團隊統(tǒng)一的緩存策略剑勾;
????接下來需要去沉下心,去谷歌搜索下網(wǎng)絡請求問題赵颅,針對業(yè)務場景去思考下一旦上線會遇到什么樣的問題虽另,很快確定了第二個問題,DNS劫持問題饺谬,所以我們搭建網(wǎng)路層第二個原因也是重要功能點:
????三捂刺、 全局、團隊統(tǒng)一的 DNS反劫持募寨;
????為了防止遺漏族展,又去找團隊成員、上級老大聊天請教拔鹰,看有什么特殊要求仪缸。這時候運營部門提了個需求,統(tǒng)計dns劫持率列肢,老大說出現(xiàn)網(wǎng)絡問題要能夠快速定位恰画。所以搭建App網(wǎng)絡層第四個重點:
????四、 全局例书、團隊統(tǒng)一的網(wǎng)絡請求統(tǒng)計和關鍵log
????明確了什么要搭建App的網(wǎng)絡層锣尉,以及必須具備哪些功能,接下便開始正面遭遇問題:
如何一步步搭建App網(wǎng)絡層呢
????人類做事習慣上是順序進行的决采,這就決定了人類的可靠性思維-邏輯思維是線性的自沧,進而決定了人類的可靠性表達也是線性的!越順滑的思路,越順滑的表達拇厢,越容易被人理解與接受爱谁。
????所以當遭遇事件類相關任務,又不知道怎么做的時候孝偎,從事件整體業(yè)務流程進行分析是一個很好的切入點访敌。
????從整體業(yè)務流程出發(fā)、使我們不至于迷失衣盾,但到了每一個環(huán)節(jié)該如何做寺旺,就需要一些指導與規(guī)范,那必須就是:
????SDK設計原則: A 簡潔易用 B 功能完備 C 擴展性好势决。
????當然 “簡單嗎阻塑?優(yōu)美嗎?” 也是每個軟件工程師須時時反問的果复。
一 陈莽、簡潔易用-從用戶使用與理解角度考慮模塊劃分與接口設計
????現(xiàn)在假設網(wǎng)絡層已經(jīng)搭建好了,用戶網(wǎng)絡層發(fā)起網(wǎng)絡請求虽抄,所以遇到第一個問題節(jié)點就是網(wǎng)絡層對外接口設計走搁。接口設計原則是簡單!簡單迈窟!簡單私植!不僅僅是代碼看著的簡單,而是在于用戶易于理解和使用的簡單菠隆!
????上文提到通過OkHttp網(wǎng)絡請求大概分為四步:
????1 構建兵琳、設置OkHttpClient--相當于設置電子郵箱;
????2 構建Request請求內(nèi)容--相當于寫信骇径;
????3 用OkHttpClient把Request轉(zhuǎn)換成為待發(fā)送的Request-Call--相當于把信件放入郵箱,變?yōu)猷]件者春;
????4 發(fā)送請求破衔,等待回復,并處理钱烟。
????現(xiàn)在就設想最簡單的網(wǎng)絡請求是怎么樣的:額晰筛、大概是這樣的吧----客戶端添加一個請求(由Url構建出來)- 發(fā)送出去-等待回復處理,比如如下
new HttpClient.HttpClientBuilder().build() // 設置郵箱
.addRequest(new GetRequest("http://www.baidu.com")) //寫信并添加到郵箱或者叫寫郵件
.sendRequest(new DefaultCallback(){ //發(fā)送郵件等待回復
@Override
public void onResponse(Call call, Response response) {
super.onResponse(call, response);
Log.e("JG","response="+response.toString()); //服務器成功返回
}
@Override
public void onFailure(Call call, IOException e) {
super.onFailure(call, e);
}
});
????從這個簡單流程出發(fā)拴袭、App網(wǎng)絡層可抽象出如下模塊:
????1 HttpClient 客戶端模塊 读第;
????2 Request 請求模塊 ;
????3 Callback 回復處理模拥刻。
????據(jù)此我們可以把App網(wǎng)絡層劃分為這三個大的模塊怜瞒。同理,在每一個大模塊內(nèi)部也要根據(jù)流程劃分為更細的模塊
????這一小節(jié)主要探討模塊劃分與接口設計,核心思想是跳出代碼邏輯吴汪,從整體業(yè)務流程出發(fā)惠窄,找到關鍵的處理節(jié)點,從而對網(wǎng)路層進行模塊劃分漾橙,從用戶易于使用和理解角度進行接口設計杆融。但這種設計是否行得通,還要從每一個具體業(yè)務實現(xiàn)進行重新審核霜运。
二脾歇、功能完備-從業(yè)務需求實現(xiàn)出發(fā)審查模塊劃分的合理性
????接下來我們開始分析每一個業(yè)務需求、驗證剛才的模塊劃分是否合理淘捡。
1 get/post請求
????最初我們從get/post請求開始思考業(yè)務邏輯藕各,所以這個可以跟模塊完美結合,get與post的區(qū)別在我們這里就是不同的Request封裝案淋。
2 DNS防劫持
????DNS-Domin Name System域名解析系統(tǒng)座韵,將域名(例如:“http://www.baidu.com”)轉(zhuǎn)換為IP地址(例如:220.181.112.244),這個解析過程涉及到本地緩存踢京,運營商緩存誉碴,各級別域名服務器等,它是Http協(xié)議的一部分瓣距。
????DNS劫持劫持又稱域名劫持黔帕,本質(zhì)就是通過攻破DNS解析過程中某些環(huán)節(jié)與節(jié)點,來給用戶返回假網(wǎng)址IP蹈丸。
????既然DNS劫持結果是返回錯誤的IP成黄,那是否直接用Ip來訪問就可以防止DNS劫持了?這就是DNS反劫持的主要思想:拿到域名->通過Http請求訪問權威三方(比如阿里的HTTPDNS)提供的DNS解析服務器->三方DNS解析服務器告訴你IP逻杖,便可通過該IP來進行網(wǎng)絡請求了奋岁。
????OkHttp實現(xiàn)DNS反劫持:由上文可知看到DNS反劫持關鍵在于,用三方的DNS解析系統(tǒng)代替系統(tǒng)默認的DNS解析系統(tǒng)荸百!所以OkHttp提供了一個抽象的DNS類闻伶,用戶只用繼承這個類,便可以方便的接入自定義的DNS解析系統(tǒng)够话。
public class HttpDns implements Dns {
private static final String TAG = HttpDns.class.getSimpleName();
@Override
public List<InetAddress> lookup(String hostname) throws UnknownHostException {
Log.v(TAG, "lookup:" + hostname);
//只需要在lookup方法中調(diào)用HttpDns的SDK去獲取IP
// 如果獲取到了就返回一個List<InetAddress>的值
// 如果購買了阿里的HttpDns服務就可以用
//默認又返回系統(tǒng)的DNS解析蓝翰,這就叫DNS降級
return SYSTEM.lookup(hostname);
}
}
然后通過OkHttClient設置此DNS,HttpClient模塊主要就是封裝OkHttClient女嘲,所以審核通過畜份!可行再次+1。
3 緩存設置
????緩存設置指緩存的位置欣尼、大小爆雹、時間,OkHttp通過兩種方式可以實現(xiàn)緩存設置:
//1 通過庫cache接口
new OkHttpClient.Builder()
.cache(new Cache(file, cacheSize)) // 配置緩存
// 2 通過攔截器
new OkHttpClient.Builder()
.cache(new CacheInterceptor(){
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
return response;
}
});
具體做法請參照:okhttp 緩存實踐
它依舊可以通過OkHttpClient完成,我們依舊只需要把這部分封裝在HttpClient模塊就好顶别。
4 全局log統(tǒng)計
????全局log統(tǒng)計依舊是使用攔截器完成谷徙,上代碼
public class LogInterceptor implements Interceptor {
private static final String TAG=LogInterceptor.class.getSimpleName();
@Override
public Response intercept(Chain chain) throws IOException {
// 把請求request攔截下來
Request request = chain.request();
//可以打印請求內(nèi)容
Log.v(TAG,"request method="+request.method()+",request url="+request.url());
//繼續(xù)向下一個傳遞處理,并攔截到處理結果response
Response response = chain.proceed(request);
// 可以打印返回內(nèi)容
Log.v(TAG,"response="+response.toString());
return response;
}
}
依舊是封裝在HttpClient模塊驯绎。全部審核通過完慧,不過從最初設計也可以知道,我們從OkHttp用法借鑒思想剩失,肯定是可行的屈尼。
三、 擴展性好-從開閉原則進行模塊間解耦操作
????現(xiàn)在各個模塊分工明確拴孤、業(yè)務功能基本全部實現(xiàn)了脾歧,但隨著業(yè)務的發(fā)展、我們可能會有新類型的請求演熟、新種類的返回處理等鞭执,所以一開始我們就要考慮整個網(wǎng)路層的擴展性。
????擴展性好的關鍵在于模塊間耦合度低芒粹,解耦的關鍵在于依賴抽象兄纺,也就是實體模塊(比如一個實體類)之間沒有直接調(diào)用關系,實體模塊之間數(shù)據(jù)傳遞要通過中間層(抽象類或者接口)化漆。
再次回顧下我們最初設計的調(diào)用接口:
new HttpClient.HttpClientBuilder().build() // 設置郵箱
.addRequest(new GetRequest("http://www.baidu.com")) //寫信并添加到郵箱或者叫寫郵件
.sendRequest(new DefaultCallback(){ //發(fā)送郵件等待回復
@Override
public void onResponse(Call call, Response response) {
super.onResponse(call, response);
Log.e("JG","response="+response.toString()); //服務器成功返回
}
@Override
public void onFailure(Call call, IOException e) {
super.onFailure(call, e);
}
});
其中 addRequest
public ReadyRequest addRequest(BaseRequest baseRequest){
return new ReadyRequest(this,baseRequest);
}
其中GetRequest是繼承自BaseRequest估脆,這樣HttpClient這個實體類就沒有直接和實體類GetRequest相關,這就是通過依賴抽象進行了解耦合座云。當我們需要一種新的Request疙赠,只需繼承BaseRequest就可以方便的擴展使用。
如何不斷優(yōu)化App網(wǎng)絡層
????現(xiàn)在我們做好了一個基本可以使用朦拖、并且具備一定擴展性的網(wǎng)絡層圃阳,但是使用過程中肯定可以發(fā)現(xiàn)bug和可以優(yōu)化地方,那么如何一步步把我們的網(wǎng)絡層從普通變?yōu)樽吭侥兀?br>
????那首先我還是會問一個問題璧帝,對一個具體APP來說怎么樣才是一個頂級的網(wǎng)絡層呢限佩?
????1 安全性高;
????2 網(wǎng)絡訪問速度快裸弦、性能優(yōu)越;
????3 用戶使用方便作喘。
看了一些優(yōu)秀的網(wǎng)絡層改進過程理疙,暫時總結出來點如下:
1 統(tǒng)計網(wǎng)絡請求常見bug與風險,增加預防處理泞坦;
2 統(tǒng)計業(yè)務流程想關性窖贤,進行預加載或者緩存;
3 統(tǒng)計用戶使用習慣和思維、統(tǒng)一智能設置或者修改接口赃梧;
4 不斷學習優(yōu)秀軟件的設計思維滤蝠,進行部分重構。
這些都是大數(shù)據(jù)與AI思維的延伸授嘀,這部分還在繼續(xù)思考中物咳,如果各位有什么想法、歡迎在下面交流評論L阒濉览闰!
總結
????本文從簡單的網(wǎng)絡請求開始,談到了為了業(yè)務需求和方便使用來搭建自己App的網(wǎng)絡層巷折,進而探討了如何一步步實現(xiàn)一個App網(wǎng)絡層压鉴,以一個怎么樣的指導思想去完善和優(yōu)化。其中重點是去重現(xiàn)了搭建锻拘、優(yōu)化一個功能層或者說SDK的思維過程:
????1 跳出代碼油吭,從整體業(yè)務流程進行初步模塊劃分;
????2 從方便(用戶)理解使用的角度設計調(diào)用接口署拟;
????3 從業(yè)務具體實現(xiàn)出發(fā)婉宰,重新審視模塊劃分;
????4 用軟件設計思維與模式再次審查當前結構設計芯丧,增加擴展性芍阎、方便用戶靈活擴展。
????5 用大數(shù)據(jù)與AI進化思維進行不停優(yōu)化缨恒;
????同時谴咸,也花了一兩天時間,手動搭建一個App網(wǎng)絡層來驗證思維過程的可行性骗露。gitHub地址:https://github.com/kingkong-li/networklib
歡迎各位大神前來交流岭佳、共同開發(fā)學習~