前言
在平時開發(fā)中寝蹈,你有沒有下面這樣的困擾呢?
場景一
明明是服務端的接口數據錯誤箫老,而QA(測試)第一個找到的可能是客戶端開發(fā)的你封字,為什么這個頁面出現錯誤了耍鬓?
而作為客戶端開發(fā)的你阔籽,可能要拿出測試機連上電腦牲蜀,打一下Log笆制,看一下到底返回了什么數據,導致頁面錯誤涣达。
或者高級一點的QA,會自己打Log或者連接抓包工具看一下服務端返回的具體數據度苔,然后把Bug提給對應的人,而大多數公司的業(yè)務測試奕删,都僅僅是測試業(yè)務疗认,不管技術層的。我司的大部分QA横漏,屬于外派來的,一般也只測試業(yè)務缎浇,每次有問題,都先找客戶端二蓝。
場景二
你現在正在外面做地鐵,產品或者你領導突然給你反饋刊愚,你之前做的那塊業(yè)務,突然線上跑不起來了商玫,不行了牡借。你一想,這肯定是服務端的問題啊钠龙,但是怎么證明呢?
場景三
服務端上個線刻像,每次都需要客戶端加班配合,說有問題并闲,可以及時幫助排查問題细睡。
推薦一個小工具
說了這么多,就是缺少一個端上的抓包小工具溜徙,來查看服務端的數據是否有問題犀填,今天推薦的是一個基于OKHttp的抓包工具。 部分截圖如下
支持功能
- 自帶分類接口
- 抓包數據以時間為緯度,默認存儲到手機緩存下 /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
長下面這個樣子
其中關于網絡模塊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。
可以用如下圖大致描述
講了那么多相關的知識點瘟滨,我們來回到正題能颁,上述推薦小工具的實現步驟介紹
抓包工具實現主要步驟介紹
添加一個抓包入口
在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