前言
在平時開發中,你有沒有下面這樣的困擾呢?
場景一
明明是服務端的接口數據錯誤,而QA(測試)第一個找到的可能是客戶端開發的你,為什么這個頁面出現錯誤了?
而作為客戶端開發的你,可能要拿出測試機連上電腦,打一下Log,看一下到底返回了什么數據,導致頁面錯誤。
或者高級一點的QA,會自己打Log或者連接抓包工具看一下服務端返回的具體數據,然后把Bug提給對應的人,而大多數公司的業務測試,都僅僅是測試業務,不管技術層的。我司的大部分QA,屬於外派來的,一般也只測試業務,每次有問題,都先找客戶端。
場景二
你現在正在外面做地鐵,產品或者你領導突然給你反饋,你之前做的那塊業務,突然線上跑不起來了,不行了。你一想,這肯定是服務端的問題啊,但是怎么證明呢?
場景三
服務端上個線,每次都需要客戶端加班配合,說有問題,可以及時幫助排查問題。
推薦一個小工具
說了這么多,就是缺少一個端上的抓包小工具,來查看服務端的數據是否有問題,今天推薦的是一個基於OKHttp的抓包工具。 部分截圖如下


支持功能
- 自帶分類接口
- 抓包數據以時間為緯度,默認存儲到手機緩存下 /Android/Data/包名/Cache/capture/ 下
- 支持Http/Https協議的抓包,分類請求方式/請求URL/請求Header/請求體/響應狀態/響應Header/響應體
- 支持一鍵復制對應的狀態
- 響應體如果是JSON,支持自動格式化
- 抓包數據,默認緩存一天
Github地址
代碼已經托管到Github 地址:github.com/DingProg/Ne…
快速接入

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開發,說到OKHttp的Interceptor,肯定熟悉不過了。那么你對 Interceptor 又了解多少呢?你都使用過那些OKHttp的 Interceptor呢?
我們先來看一下最近滴滴很火的哆啦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
作者:北斗星_And
鏈接:https://juejin.im/post/5ddddd2a6fb9a07161483fb2
來源:掘金
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。