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

前言

在平時開發中,你有沒有下面這樣的困擾呢?java

場景一

明明是服務端的接口數據錯誤,而QA(測試)第一個找到的多是客戶端開發的你,爲何這個頁面出現錯誤了?android

而做爲客戶端開發的你,可能要拿出測試機連上電腦,打一下Log,看一下到底返回了什麼數據,致使頁面錯誤。git

或者高級一點的QA,會本身打Log或者鏈接抓包工具看一下服務端返回的具體數據,而後把Bug提給對應的人,而大多數公司的業務測試,都僅僅是測試業務,無論技術層的。我司的大部分QA,屬於外派來的,通常也只測試業務,每次有問題,都先找客戶端。github

場景二

你如今正在外面作地鐵,產品或者你領導忽然給你反饋,你以前作的那塊業務,忽然線上跑不起來了,不行了。你一想,這確定是服務端的問題啊,可是怎麼證實呢?緩存

場景三

服務端上個線,每次都須要客戶端加班配合,說有問題,能夠及時幫助排查問題。cookie

推薦一個小工具

說了這麼多,就是缺乏一個端上的抓包小工具,來查看服務端的數據是否有問題,今天推薦的是一個基於OKHttp的抓包工具。 部分截圖以下網絡

支持功能

  • 自帶分類接口
  • 抓包數據以時間爲緯度,默認存儲到手機緩存下 /Android/Data/包名/Cache/capture/ 下
  • 支持Http/Https協議的抓包,分類請求方式/請求URL/請求Header/請求體/響應狀態/響應Header/響應體
  • 支持一鍵複製對應的狀態
  • 響應體若是是JSON,支持自動格式化
  • 抓包數據,默認緩存一天

Github地址

代碼已經託管到Github 地址:github.com/DingProg/Ne…app

快速接入

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中添加 Interceptormaven

new OkHttpClient.Builder()
        .addInterceptor(new CaptureInfoInterceptor())
        .build();
複製代碼

原理及涉及知識詳解

做爲Android開發,說到OKHttp的Interceptor,確定熟悉不過了。那麼你對 Interceptor 又瞭解多少呢?你都使用過那些OKHttp的 Interceptor呢?ide

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

DoraemonKit

長下面這個樣子

其中關於網絡模塊OK Http的監聽以下

OkHttpClient client = new OkHttpClient().newBuilder()
                //用於模擬弱網的攔截器
                .addNetworkInterceptor(new DoraemonWeakNetworkInterceptor())
                //網絡請求監控的攔截器 ,用於網絡流量監聽等
                .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,根據不一樣的狀態來進行延遲,例如以下的模擬超時

/** * 模擬超時 * * @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中,並執行到以下代碼

@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,鏈接請求,發起請求到服務端等。咱們來看一下最後幾行 代碼,RealInterceptorChain是一個Interceptor.Chain類型,並執行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。

能夠用以下圖大體描述

講了那麼多相關的知識點,咱們來回到正題,上述推薦小工具的實現步驟介紹

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

添加一個抓包入口

在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();
複製代碼

另外一種方式 採用字節碼注入的形式,關於字節碼注入,能夠簡單參考個人另外一篇Gradle學習筆記,自定義 Transform部分。

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

存儲時,爲了避免影響到主APP的網絡請求效率,須要在單獨的線程中執行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);
}
複製代碼

好了,關於這個小工具,就介紹那麼多了,具體細節代碼,能夠直接查看Github代碼倉庫,github.com/DingProg/Ne…

總結

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

  • 電腦端的有Fiddler、Charels,Wireshark等,可是不是特別方便。
  • APP能夠抓其餘包的工具,如NetWorkPacketCapture/抓包精靈/AndroidHttpCapture,可是都一些限制條件,要麼代碼沒開源,廣告多。要麼就是隻能在WIFI下,或者要麼就是須要Root等,不太好定製。

本文,主要是介紹OkHttp的攔截器,並從中發現能夠幹不少事情。如文中有錯誤,還忘指正,感謝。

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

相關文章
相關標籤/搜索