從OkHttp的使用談談App網絡層搭建的思維過程

前言

    在Android開發中,網絡請求是每個開發者的必備技能。當前也有很多優秀、開源的網絡請求庫。例如:

其中Retrofit是對OkHttp的封裝,Android-async-http是對HttpClient的封裝,利用這些網絡庫開發者可以極大提升編碼效率。即便這些優秀的網絡庫可以很方便進行網絡請求,但大多數團隊依舊要搭建App網絡層,甚至把網絡層單獨封裝成庫使用,爲什麼呢?所以本文首先就要討論:

  1 爲什麼要搭建App網絡層呢?

    當知道搭建網絡層的必要性之後,便摩拳擦掌準備去大幹一番,但很快便面對一個問題:

  2 如何一步步搭建App網絡層呢?

    終於搭建好了網絡層,但使用時肯定能發現不少bug和可以優化的地方,那麼:

  3 應該用什麼樣的思想來指導改進網絡層呢?

在回答上述問題之前,先了解下網絡請求的基本流程:

網絡請求基本流程

    網絡請求的實質是去查看、修改遠程計算機(包括服務器)上的信息,僅從客戶端來看基本流程如下:


    如圖示,網絡請求的基本流程就是如此簡單,和把大象放入冰箱一樣,都是三步。接下來我們在代碼級別來看看:

如何進行網絡請求

  以使用OkHttp框架訪問百度首頁爲例子

       //構造一個HttpClient 相當於設置個人郵箱。
       OkHttpClient client=new OkHttpClient();
       //創建Request 對象,相當於寫信。
       Request request = new Request.Builder()
               .url("http://www.baidu.com")
               .build()
       //將Request封裝爲call,相當於把信放進郵箱,成爲設置後待發送的信件
       Call call = client.newCall(request);
       // 放置到請求隊列、開始發送並等待回覆,相當於郵箱開始發動信件,並等待對方回覆。
       call.enqueue(new Callback() {
           @Override
           public void onFailure(Call call, IOException e) {
              // 當請求被取消、連接中斷、找不到服務器等問題會調用這個接口
           }

           @Override
           public void onResponse(Call call, final Response response) throws IOException {
               // 遠程服務器成功返回調用
               final String res = response.body().string();
               runOnUiThread(new Runnable() {
                   @Override
                   public void run() {
                       Log.e("TAG"," "+res);
                   }
               });
           }
       });

   }

    由上可以看到用OkHttp框架進行網絡請求邏輯清晰簡單,跟我們用郵件跟朋友交流差不多。首先是設置郵箱(如果沒有什麼特殊需求就用默認設置、如上文)+寫郵件內容,然後把寫好郵件後放到設置好的郵箱裏面,最後點擊發送,等待朋友的回覆,當朋友回覆了就去查看處理。

爲什麼要搭建網絡層

    知道如何用OkHttp後很興奮,於是用這一套開始了網絡請求之旅,so easy!!複製-粘貼-修改,複製-粘貼-修改,複製-粘貼-修改...終於做了七八個網絡請求,一看任務量完成了六分之一,啊--累死寶寶了!!
    不行了不行了,要喝杯luckin coffee鼓舞下士氣,說走就走,喝着咖啡想着回來就一口氣加班搞定它。突然想起OOP重要原則-代碼複用原則,一拍腦子我TM真是個憨貨,把這些請求的共同部分提取出來,對外提供更簡單的接口、這樣用着方便不容易出錯、以後有問題修改工作量也大大減少了,這就是我們搭建網絡層第一個原因:

    一、近似業務模型的代碼複用--方便使用和修改;

    說幹就幹,擼起袖子正準備上場,再次靈光一閃,不對不對,這次要好好思考下整個完美的,半途而廢啥的最浪費時間了!這裏要說明下,App網絡層是我們抽離出來的一個單獨模塊,所以網絡層搭建跟設計實現一個單獨的網絡請求庫一樣,首先看看網絡上優秀的開源庫都有哪些功能可以借鑑。
    去github上看了star比較多的一些OkHttp庫封裝,整理了下功能列表:

  • 一般的get請求
  • 一般的post請求
  • 基於Http Post的文件上傳(類似表單)
  • 文件下載/加載圖片
  • 上傳下載的進度回調
  • 支持取消某個請求
  • 支持自定義Callback
  • 支持HEAD、DELETE、PATCH、PUT
  • 支持session的保持
  • 支持自簽名網站https的訪問,提供方法設置下證書就行
  • 支持RxJava
  • 支持自定義緩存策略

    這些網絡庫是很好參考借鑑,但是,它們跟我們的業務結合不緊密、直接用還是會造成:

    1 每個網絡請求都要加上業務邏輯;
    2 有不少多餘功能,導致網絡層很大,可能影響效率 ;
    3 團隊特殊要求達不到,比如利用三方實現DNS防劫持。

    所以需要仔細去分析業務、梳理網絡請求類型。很快我們發現需要四種緩存策略:立即請求、緩存10s、緩存1h、緩存24h,所以搭建網絡層第二個原因也是功能點:

    二、 全局,全團隊統一的緩存策略;

    接下來需要去沉下心,去谷歌搜索下網絡請求問題,針對業務場景去思考下一旦上線會遇到什麼樣的問題,很快確定了第二個問題,DNS劫持問題,所以我們搭建網路層第二個原因也是重要功能點:

    三、 全局、團隊統一的 DNS反劫持;

    爲了防止遺漏,又去找團隊成員、上級老大聊天請教,看有什麼特殊要求。這時候運營部門提了個需求,統計dns劫持率,老大說出現網絡問題要能夠快速定位。所以搭建App網絡層第四個重點:

    四、 全局、團隊統一的網絡請求統計和關鍵log

    明確了什麼要搭建App的網絡層,以及必須具備哪些功能,接下便開始正面遭遇問題:

如何一步步搭建App網絡層呢

    人類做事習慣上是順序進行的,這就決定了人類的可靠性思維-邏輯思維是線性的,進而決定了人類的可靠性表達也是線性的!越順滑的思路,越順滑的表達,越容易被人理解與接受。
    所以當遭遇事件類相關任務,又不知道怎麼做的時候,從事件整體業務流程進行分析是一個很好的切入點。
    從整體業務流程出發、使我們不至於迷失,但到了每一個環節該如何做,就需要一些指導與規範,那必須就是:
    SDK設計原則: A 簡潔易用 B 功能完備 C 擴展性好。
    當然 “簡單嗎?優美嗎?” 也是每個軟件工程師須時時反問的。

一 簡潔易用-從用戶使用與理解角度考慮對外接口與模塊劃分

    現在假設網絡層已經搭建好了,用戶網絡層發起網絡請求,所以遇到第一個問題節點就是網絡層對外接口設計。接口設計原則是簡單!簡單!簡單!不僅僅是代碼看着的簡單,而是在於用戶易於理解和使用的簡單!
    上文提到通過OkHttp網絡請求大概分爲四步:
    1 構建、設置OkHttpClient--相當於設置電子郵箱;
    2 構建Request請求內容--相當於寫信;
    3 用OkHttpClient把Request轉換成爲待發送的Request-Call--相當於把信件放入郵箱,變爲郵件;
    4 發送請求,等待回覆,並處理。
    現在就設想最簡單的網絡請求是怎麼樣的:額、大概是這樣的吧----客戶端添加一個請求(由Url構建出來)- 發送出去-等待回覆處理,比如如下

   new HttpClient.HttpClientBuilder().build()                        // 設置郵箱
                .addRequest(new GetRequest("http://www.baidu.com"))  //寫信並添加到郵箱或者叫寫郵件
                .sendRequest(new DefaultCallback(){          //發送郵件等待回覆
                    @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);
                    }
                });
 

    從這個簡單流程出發、App網絡層可抽象出如下模塊:
    1 HttpClient 客戶端模塊 ;
    2 Request 請求模塊 ;
    3 Callback 回覆處理模。
    據此我們可以把App網絡層劃分爲這三個大的模塊。同理,在每一個大模塊內部也要根據流程劃分爲更細的模塊
    這一小節主要探討模塊設計與劃分,核心思想是跳出代碼邏輯,從整體業務流程出發,找到關鍵的處理節點,從而對網路層進行模塊設計與劃分。但到底這種設計是否行得通,還要從每一個具體業務實現進行重新審覈。

功能完備-從業務需求實現出發審查模塊劃分的合理性

    接下來我們開始分析每一個業務需求、驗證剛纔的模塊劃分是否合理。

1 get/post請求

    最初我們從get/post請求開始思考業務邏輯,所以這個可以跟模塊完美結合,get與post的區別在我們這裏就是不同的Request封裝。

2 DNS防劫持

    DNS-Domin Name System域名解析系統,將域名(例如:“http://www.baidu.com”)轉換爲IP地址(例如:220.181.112.244),這個解析過程涉及到本地緩存,運營商緩存,各級別域名服務器等,它是Http協議的一部分。
    DNS劫持劫持又稱域名劫持,本質就是通過攻破DNS解析過程中某些環節與節點,來給用戶返回假網址IP。
    既然DNS劫持結果是返回錯誤的IP,那是否直接用Ip來訪問就可以防止DNS劫持了?這就是DNS反劫持的主要思想:拿到域名->來通過Http請求->訪問權威三方(比如阿里的HTTPDNS)提供的DNS解析服務器->三方告訴你IP,便可通過該IP來進行網絡請求了。
    OkHttp實現DNS反劫持由上文可知看到DNS反劫持關鍵在於,用三方的DNS解析系統代替系統默認的DNS解析系統!所以OkHttp提供了一個抽象的DNS類,用戶只用繼承這個類,便可以方便的接入自定義的DNS解析系統。

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方法中調用HttpDns的SDK去獲取IP
        // 如果獲取到了就返回一個List<InetAddress>的值
        // 如果購買了阿里的HttpDns服務就可以用
        //默認又返回系統的DNS解析,這就叫DNS降級
        return SYSTEM.lookup(hostname);
    }
}

然後通過OkHttClient設置此DNS,HttpClient模塊主要就是封裝OkHttClient,所以審覈通過!可行再次+1。

3 緩存設置

    緩存設置指緩存的位置、大小、時間,OkHttp通過兩種方式可以實現緩存設置:

//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統計

    全局log統計依舊是使用攔截器完成,上代碼

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();
       //可以打印請求內容
        Log.v(TAG,"request method="+request.method()+",request url="+request.url());
        //繼續向下一個傳遞處理,並攔截到處理結果response
        Response response = chain.proceed(request);
        // 可以打印返回內容
        Log.v(TAG,"response="+response.toString());
        return response;
    }
}

依舊是封裝在HttpClient模塊。全部審覈通過,不過從最初設計也可以知道,我們從OkHttp用法借鑑思想,肯定是可行的。

三、 擴展性好-從開閉原則進行模塊間解耦操作

    現在各個模塊分工明確、業務功能基本全部實現了,但隨着業務的發展、我們可能會有新類型的請求、新種類的返回處理等,所以一開始我們就要考慮整個網路層的擴展性。
    擴展性好的關鍵在於模塊間耦合度低,解耦的關鍵在於依賴抽象,也就是實體模塊(比如一個實體類)之間沒有直接調用關係,實體模塊之間數據傳遞要通過中間層(抽象類或者接口)。
再次回顧下我們最初設計的調用接口:

   new HttpClient.HttpClientBuilder().build()                        // 設置郵箱
                .addRequest(new GetRequest("http://www.baidu.com"))  //寫信並添加到郵箱或者叫寫郵件
                .sendRequest(new DefaultCallback(){          //發送郵件等待回覆
                    @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就可以方便的擴展使用。

如何不斷優化App網絡層

    現在我們做好了一個基本可以使用、並且具備一定擴展性的網絡層,但是使用過程中肯定可以發現bug和可以優化地方,那麼如何一步步把我們的網絡層從普通變爲卓越呢?
    那首先我還是會問一個問題,對一個具體APP來說怎麼樣纔是一個頂級的網絡層呢?
    1 安全性高;
    2 網絡訪問速度快、性能優越;
    3 用戶使用方便。
看了一些優秀的網絡層改進過程,暫時總結出來點如下:
1 統計網絡請求常見bug與風險,增加預防處理;
2 統計業務流程想關性,進行預加載或者緩存;
3 統計用戶使用習慣和思維、統一智能設置或者修改接口;
4 不斷學習優秀軟件的設計思維,進行部分重構。
這些都是大數據與AI思維的延伸,這部分還在繼續思考中,如果各位有什麼想法、歡迎在下面交流評論!!

總結

    本文從簡單的網絡請求開始,談到了爲了業務需求方便使用來搭建自己的App網絡層,進而探討了如何一步步實現一個App網絡層,並不斷優化、完善。其中重點是想重現了下架構,優化功能層或者說SDK的一個思維過程:

    1 跳出代碼,從整體業務流程進行初步模塊劃分;

    2 從方便(用戶)理解使用的角度設計調用接口;

    3 從業務具體實現出發,重新審視模塊劃分;

    4 用軟件設計思維與模式再次審視當前結構設計,增加擴展性、方便用戶靈活擴展。

    5 用大數據與AI進化思維進行不停優化;

    同時,也花了一兩天時間,手動搭建一個App網絡層來驗證思維過程的可行性。gitHub地址:https://github.com/kingkong-li/networklib
歡迎各位大神前來交流、共同開發學習~~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章