Android OKHttp 可能你從來沒用過的攔截器 【實用推薦】

前言

在平時開發(fā)中寝蹈,你有沒有下面這樣的困擾呢?

場景一

明明是服務端的接口數據錯誤箫老,而QA(測試)第一個找到的可能是客戶端開發(fā)的你封字,為什么這個頁面出現錯誤了耍鬓?

而作為客戶端開發(fā)的你阔籽,可能要拿出測試機連上電腦牲蜀,打一下Log笆制,看一下到底返回了什么數據,導致頁面錯誤涣达。

或者高級一點的QA,會自己打Log或者連接抓包工具看一下服務端返回的具體數據度苔,然后把Bug提給對應的人,而大多數公司的業(yè)務測試奕删,都僅僅是測試業(yè)務疗认,不管技術層的。我司的大部分QA横漏,屬于外派來的,一般也只測試業(yè)務缎浇,每次有問題,都先找客戶端二蓝。

場景二

你現在正在外面做地鐵,產品或者你領導突然給你反饋刊愚,你之前做的那塊業(yè)務,突然線上跑不起來了商玫,不行了牡借。你一想,這肯定是服務端的問題啊钠龙,但是怎么證明呢?

場景三

服務端上個線刻像,每次都需要客戶端加班配合,說有問題并闲,可以及時幫助排查問題细睡。

推薦一個小工具

說了這么多,就是缺少一個端上的抓包小工具溜徙,來查看服務端的數據是否有問題犀填,今天推薦的是一個基于OKHttp的抓包工具。 部分截圖如下

image

在這里插入圖片描述

支持功能

  • 自帶分類接口
  • 抓包數據以時間為緯度,默認存儲到手機緩存下 /Android/Data/包名/Cache/capture/ 下
  • 支持Http/Https協(xié)議的抓包图贸,分類請求方式/請求URL/請求Header/請求體/響應狀態(tài)/響應Header/響應體
  • 支持一鍵復制對應的狀態(tài)
  • 響應體如果是JSON冕广,支持自動格式化
  • 抓包數據,默認緩存一天

Github地址

代碼已經托管到Github 地址:https://github.com/DingProg/NetworkCaptureSelf

快速接入

[圖片上傳失敗...(image-a6788-1575277280106)]

allprojects {
    repositories {
       maven { url 'https://jitpack.io' }
    }
}

dependencies {
    debugImplementation 'com.github.DingProg.NetworkCaptureSelf:library:v1.0.1'
    releaseImplementation 'com.github.DingProg.NetworkCaptureSelf:library_no_op:v1.0.1'  
}

在你的全局OkHttp中添加 Interceptor

new OkHttpClient.Builder()
        .addInterceptor(new CaptureInfoInterceptor())
        .build();

原理及涉及知識詳解

作為Android開發(fā)沟优,說到OKHttp的Interceptor睬辐,肯定熟悉不過了宾肺。那么你對 Interceptor 又了解多少呢侵俗?你都使用過那些OKHttp的 Interceptor呢?

我們先來看一下最近滴滴很火的哆啦A夢

DoraemonKit

長下面這個樣子

image

其中關于網絡模塊OK Http的監(jiān)聽如下

OkHttpClient client = new OkHttpClient().newBuilder()
                //用于模擬弱網的攔截器
                .addNetworkInterceptor(new DoraemonWeakNetworkInterceptor())
                //網絡請求監(jiān)控的攔截器 黔酥,用于網絡流量監(jiān)聽等
                .addInterceptor(new DoraemonInterceptor()).build();

這里舉例說一下弱網模擬

弱網模擬

看一下他的實現代碼

public class DoraemonWeakNetworkInterceptor implements Interceptor {

    @Override
    public Response intercept(Chain chain) throws IOException {
        if (!WeakNetworkManager.get().isActive()) {
            Request request = chain.request();
            return chain.proceed(request);
        }
        final int type = WeakNetworkManager.get().getType();
        switch (type) {
            case WeakNetworkManager.TYPE_TIMEOUT:
                //超時
                final HttpUrl url = chain.request().url();
                throw WeakNetworkManager.get().simulateTimeOut(url.host(), url.port());
            case WeakNetworkManager.TYPE_SPEED_LIMIT:
                //限速
                return WeakNetworkManager.get().simulateSpeedLimit(chain);
            default:
                //斷網
                throw WeakNetworkManager.get().simulateOffNetwork(chain.request().url().host());
        }
    }
}

實現一個OkHttp的Intercepter,根據不同的狀態(tài)來進行延遲棵帽,例如如下的模擬超時

/**
     * 模擬超時
     *
     * @param host
     * @param port
     */
    public SocketTimeoutException simulateTimeOut(String host, int port) {
        SystemClock.sleep(mTimeOutMillis);
        return new SocketTimeoutException(String.format("failed to connect to %s (port %d) after %dms", host, port, mTimeOutMillis));
    }

根據Interceptor 可以干很多事情逗概,那么Interceptor到底是什么樣的原理呢?

Interceptor原理

先看一下Interceptor的原型

public interface Interceptor {
  Response intercept(Chain chain) throws IOException;
}

再看一下OkHttp源碼逾苫,可以知道,我們的請求最終都會被調用到RealCall中瑟押,并執(zhí)行到如下代碼

 @Override protected void execute() {
    boolean signalledCallback = false;
    try {
        Response response = getResponseWithInterceptorChain();
    }
    ...
}

 Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    interceptors.add(retryAndFollowUpInterceptor);
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    interceptors.add(new CacheInterceptor(client.internalCache()));
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
    interceptors.add(new CallServerInterceptor(forWebSocket));

    Interceptor.Chain chain = new RealInterceptorChain(
        interceptors, null, null, null, 0, originalRequest);
    return chain.proceed(originalRequest);
  }

在getResponseWithInterceptorChain 添加了很多OkHttp自定義的攔截器星掰,其中有重定向,Cache怀偷,連接請求播玖,發(fā)起請求到服務端等。我們來看一下最后幾行 代碼,RealInterceptorChain是一個Interceptor.Chain類型蜀踏,并執(zhí)行chain.proceed,接著看一下proceed方法

//RealInterceptorChain
 public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
    ...
    if (index >= interceptors.size()) throw new AssertionError();
    calls++;
    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(
        interceptors, streamAllocation, httpCodec, connection, index + 1, request);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);
    ....
    return response;
  }

重點看一下Call the next interceptor in the chain 下面幾行代碼木西,他把當前的interceptor.intercept()時随静,傳入的是下一個interceptor的包裝類吗讶,RealInterceptorChain 這樣就實現了恋捆,鏈式遞歸調用了,直到最后一個response返回沸停,才會依次返回到第一個interceptor。

可以用如下圖大致描述

image

講了那么多相關的知識點瘟滨,我們來回到正題能颁,上述推薦小工具的實現步驟介紹

抓包工具實現主要步驟介紹

添加一個抓包入口

在Manifest中注冊即可,如下

  <activity
        android:name="com.ding.library.internal.ui.CaptureInfoActivity"
        android:configChanges="keyboardHidden|orientation|screenSize|smallestScreenSize|locale"
        android:launchMode="singleInstance"
        android:screenOrientation="portrait"
        android:theme="@style/AppTheme.NoActionBar" />

    <activity-alias
        android:label="抓包入口"
        android:name="CaptureInfoActivity"
        android:targetActivity="com.ding.library.internal.ui.CaptureInfoActivity">
        <intent-filter>
            <action android:name="android.intent.action.MAIN"/>
            <category android:name="android.intent.category.LAUNCHER"/>
        </intent-filter>
    </activity-alias>

暴露Interceptor

public final class CaptureInfoInterceptor implements Interceptor{
    @Override public Response intercept(Chain chain) throws IOException {
    //獲取request 所有信息
    ...
    //獲取response 所有信息
    ...
    
    //存儲抓包數據
    CacheUtils.getInstance().saveCapture(request.url().toString(),captureEntity);
    }
}

這里其中有兩種方式败玉,添加到OkHttp的Interceptor镜硕,一種硬編碼,如下

new OkHttpClient.Builder()
   .addInterceptor(new CaptureInfoInterceptor())
   .build();

另一種方式 采用字節(jié)碼注入的形式血淌,關于字節(jié)碼注入财剖,可以簡單參考我的另一篇Gradle學習筆記,自定義 Transform部分峰伙。

存儲和讀取抓包數據 效率問題

存儲時,為了不影響到主APP的網絡請求效率策彤,需要在單獨的線程中執(zhí)行IO操作匣摘,這里使用了單線程池

public class DiskIOThreadExecutor implements Executor {
   private final Executor mDiskIO;
   public DiskIOThreadExecutor() {
       mDiskIO = Executors.newSingleThreadExecutor();
   }
   @Override
   public void execute(@NonNull Runnable command) {
       mDiskIO.execute(command);
   }
}

存儲

   public void saveCapture(final String url, final CaptureEntity value) {
       Runnable runnable = new Runnable() {
           @Override
           public void run() {
               String saveUrl = url;
               if (url.contains("?")) {
                   saveUrl = saveUrl.substring(0, saveUrl.indexOf("?"));
               }
               String key = urlMd5(saveUrl);
               sp.edit().putString(key, saveUrl).apply();
               checkOrCreateFilePath(key);
               File file = new File(captureFilePath + "/" + key + "/" + getCurrentTime() + ".txt");
               BufferedSink bufferedSink = null;
               try {
                   file.createNewFile();
                   bufferedSink = Okio.buffer(Okio.sink(file));
                   bufferedSink.writeString(JSON.toJSONString(value), StandardCharsets.UTF_8);
                   bufferedSink.flush();
               } catch (Exception e) {
                   e.printStackTrace();
               } finally {
                   if (bufferedSink != null) {
                       try {
                           bufferedSink.close();
                       } catch (IOException e) {
                           e.printStackTrace();
                       }
                   }
               }
           }
       };
       diskIOThreadExecutor.execute(runnable);
   }

讀取

讀取抓包數據時,不直接讀取全部的數據庞瘸,只讀取當前抓包的目錄赠叼,數據违霞,點擊時瞬场,在去加載對應的數據

public List<String> getCapture() {
   File file = new File(captureFilePath);
   return getFileList(file);
}

public List<String> getCapture(String key) {
   File file = new File(captureFilePath + "/" + key);
   return getFileList(file);
}

好了,關于這個小工具眼五,就介紹那么多了彤灶,具體細節(jié)代碼看幼,可以直接查看Github代碼倉庫幌陕,https://github.com/DingProg/NetworkCaptureSelf

總結

其實關于抓包工具,有一些成熟的方案。

  • 電腦端的有Fiddler逗物、Charels,Wireshark等契邀,但是不是特別方便失暴。
  • APP可以抓其他包的工具,如NetWorkPacketCapture/抓包精靈/AndroidHttpCapture逗扒,但是都一些限制條件,要么代碼沒開源现恼,廣告多黍檩。要么就是只能在WIFI下,或者要么就是需要Root等刽酱,不太好定制。

本文润文,主要是介紹OkHttp的攔截器,并從中發(fā)現可以干很多事情转唉。如文中有錯誤,還忘指正麦轰,感謝。

最后也感謝你的點贊及Github的Star NetworkCaptureSelf

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末款侵,一起剝皮案震驚了整個濱河市侧纯,隨后出現的幾起案子,更是在濱河造成了極大的恐慌妹笆,老刑警劉巖娜氏,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異贸弥,居然都是意外死亡,警方通過查閱死者的電腦和手機哲鸳,發(fā)現死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進店門盔憨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人懒豹,你說我怎么就攤上這事驯用。” “怎么了蝴乔?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長片酝。 經常有香客問我,道長雕沿,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任肥哎,我火速辦了婚禮疾渣,結果婚禮上,老公的妹妹穿的比我還像新娘杈女。我一直安慰自己吊圾,他們只是感情好达椰,可當我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布啰劲。 她就那樣靜靜地躺著板丽,像睡著了一般趁尼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上酥泞,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天芝囤,我揣著相機與錄音,去河邊找鬼悯姊。 笑死,一個胖子當著我的面吹牛悯许,可吹牛的內容都是我干的。 我是一名探鬼主播瘩扼,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼规辱!你這毒婦竟也來了栽燕?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤炫贤,失蹤者是張志新(化名)和其女友劉穎付秕,沒想到半個月后兰珍,有當地人在樹林里發(fā)現了一具尸體询吴,經...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡猛计,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了奉瘤。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡藕赞,死狀恐怖卖局,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情砚偶,我是刑警寧澤,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布均芽,位于F島的核電站单鹿,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜囤萤,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一是趴、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧唆途,春花似錦、人聲如沸没佑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽陶贼。三九已至,卻和暖如春拜秧,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背志衍。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工聊替, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人佃牛。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓俘侠,卻偏偏與公主長得像蔬将,于是被迫代替她去往敵國和親爷速。 傳聞我的和親對象是個殘疾皇子霞怀,可洞房花燭夜當晚...
    茶點故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內容

  • 1.OkHttp源碼解析(一):OKHttp初階2 OkHttp源碼解析(二):OkHttp連接的"前戲"——HT...
    隔壁老李頭閱讀 11,952評論 31 62
  • 丁酉年夏,因部堂差遣颓遏,余由湘入秦滞时,于管院受業(yè)叁幢。管院地處燕秦舊地坪稽,渤海之濱。匯天地之精華窒百,養(yǎng)浩然之正氣,聚人世之英才...
    styje閱讀 311評論 1 1
  • 今天是讀書營的第十周顷帖,也是我第一次沒能在一周之內讀完一本書渤滞,接下來還會繼續(xù)把七個習慣看完,也會堅持記錄每天的心得體...
    倆果媽咪閱讀 222評論 1 1
  • 早上9點畫畫課蔼水,一早跟我來到店里,忙活忙活就到點了吊说,今天就上一節(jié)課10點就下課,吃完飯夢琪說有點累颁井,就把她...
    高夢琪媽媽閱讀 172評論 0 6