從OkHttp的使用談談App網(wǎng)絡層搭建的思維過程

前言

????在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)絡請求流程.png

????如圖示,網(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ā)學習~

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市萧锉,隨后出現(xiàn)的幾起案子珊随,更是在濱河造成了極大的恐慌,老刑警劉巖柿隙,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件叶洞,死亡現(xiàn)場離奇詭異,居然都是意外死亡禀崖,警方通過查閱死者的電腦和手機衩辟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來波附,“玉大人艺晴,你說我怎么就攤上這事昼钻。” “怎么了封寞?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵然评,是天一觀的道長。 經(jīng)常有香客問我狈究,道長碗淌,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任谦炒,我火速辦了婚禮贯莺,結果婚禮上,老公的妹妹穿的比我還像新娘宁改。我一直安慰自己缕探,他們只是感情好,可當我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布还蹲。 她就那樣靜靜地躺著爹耗,像睡著了一般。 火紅的嫁衣襯著肌膚如雪谜喊。 梳的紋絲不亂的頭發(fā)上潭兽,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天,我揣著相機與錄音斗遏,去河邊找鬼山卦。 笑死,一個胖子當著我的面吹牛诵次,可吹牛的內(nèi)容都是我干的账蓉。 我是一名探鬼主播,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼逾一,長吁一口氣:“原來是場噩夢啊……” “哼铸本!你這毒婦竟也來了?” 一聲冷哼從身側響起遵堵,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤箱玷,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后陌宿,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體锡足,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年壳坪,在試婚紗的時候發(fā)現(xiàn)自己被綠了舱污。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡弥虐,死狀恐怖扩灯,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情霜瘪,我是刑警寧澤珠插,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站颖对,受9級特大地震影響捻撑,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜缤底,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一顾患、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧个唧,春花似錦江解、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至魄梯,卻和暖如春桨螺,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背酿秸。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工灭翔, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人辣苏。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓肝箱,卻偏偏與公主長得像,于是被迫代替她去往敵國和親考润。 傳聞我的和親對象是個殘疾皇子狭园,可洞房花燭夜當晚...
    茶點故事閱讀 45,512評論 2 359

推薦閱讀更多精彩內(nèi)容