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


前言

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

場景一

明明是服務端的接口數據錯誤,而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
來源:掘金
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM