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
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

原文地址:https://www.cnblogs.com/yizijianxin/p/12000283.html

时间: 2024-11-11 07:41:14

Android OKHttp 可能你从来没用过的拦截器 【实用推荐】的相关文章

Android OkHttp文件上传与下载的进度监听扩展

相信大家对OkHttp也是相当的熟悉了,毕竟是Square的东西,对于其种种优点,这里也不再叙说.优秀是优秀,但是毕竟优秀的东西给我们封装了太多,那么问题来了,我们使用OkHttp作为我们的网络层,简单地进行GET/POST请求是毫无问题.近日看了产品的设计稿,毛估估会有文件的上传与下载的需求,如果使用OkHttp作为网络层进行封装,你会惊讶的发现,简直封装的太"完美"了.如果现在有这么一个需求,要求对文件进行上传或下载,但是在上传或者下载前,你需要给用户一个友好的提示,在上传或者下载

Android OkHttp 文件上传和下载

相信大家对OkHttp也是相当的熟悉了,毕竟是Square的东西,对于其种种优点,这里也不再叙说.优秀是优秀,但是毕竟优秀的东西给我们封装了太多,那么问题来了,我们使用OkHttp作为我们的网络层,简单地进行GET/POST请求是毫无问题.近日看了产品的设计稿,毛估估会有文件的上传与下载的需求,如果使用OkHttp作为网络层进行封装,你会惊讶的发现,简直封装的太"完美"了.如果现在有这么一个需求,要求对文件进行上传或下载,但是在上传或者下载前,你需要给用户一个友好的提示,在上传或者下载

Android OkHttp详解

来源 http://frodoking.github.io/2015/03/12/android-okhttp/ 编辑推荐:稀土掘金,这是一个针对技术开发者的一个应用,你可以在掘金上获取最新最优质的技术干货,不仅仅是Android知识.前端.后端以至于产品和设计都有涉猎,想成为全栈工程师的朋友不要错过! Android为我们提供了两种HTTP交互的方式:HttpURLConnection 和 Apache HTTP Client,虽然两者都支持HTTPS,流的上传和下载,配置超时,IPv6和连接

Android OkHttp完全解析 是时候来了解OkHttp了

Android OkHttp完全解析 是时候来了解OkHttp了

Android OkHttp与物理存储介质缓存:DiskLruCache(2)

 Android OkHttp与物理存储介质缓存:DiskLruCache(2) 本文在附录文章8,9的基础之上,把Android OkHttp与DiskLruCache相结合,综合此两项技术,实现基于OkHttp的物理存储介质缓存DiskLruCache. 用一个完整的例子加以说明.该例子的代码要实现这样的过程:代码启动后,要往一个ImageView里面加载一张网络图片,首先检查DiskLruCache是否已经存在该图片的缓存,如果存在,则直接复用缓存,如果不存在则使用OkHttp把图片异

(转)Android OkHttp完全解析 是时候来了解OkHttp了

转载自: Android OkHttp完全解析 是时候来了解OkHttp了 一.概述 最近在群里听到各种讨论okhttp的话题,可见okhttp的口碑相当好了.再加上Google貌似在6.0版本里面删除了HttpClient相关API,对于这个行为不做评价.为了更好的在应对网络访问,学习下okhttp还是蛮必要的,本篇博客首先介绍okhttp的简单使用,主要包含: 一般的get请求一般的post请求基于Http的文件上传文件下载加载图片支持请求回调,直接返回对象.对象集合支持session的保持

Android Okhttp完美同步持久Cookie实现免登录

通过对Retrofit2.0的<Retrofit 2.0 超能实践,完美支持Https传输>基础入门和案例实践,掌握了怎么样使用Retrofit访问网络,加入自定义header,包括加入SSL证书,基本的调试基础,但是正常的开发中会涉及cookie同步问题,可以实现一些自动或免登录登陆问题,接下来进入cookie同步姿势 Cookie Cookies是一种能够让网站服务器把少量数据储存到客户端的硬盘或内存,或是从客户端的硬盘读取数据的一种技术.Cookies是当你浏览某网站时,由Web服务器置

Android OkHttp + Retrofit 取消请求的方法

本文链接 前言 在某一个界面,用户发起了一个网络请求,因为某种原因用户在网络请求完成前离开了当前界面,比较好的做法是取消这个网络请求.对于OkHttp来说,具体是调用Call的cancel方法. 如何找到这一个网络请求并取消掉它呢? 操作大致分为3步.第一步,在建立请求时,给请求(request)添加标记:第二步,根据标记,找到请求:最后,取消这个请求. OkHttp中的tag 要取消一个请求,OkHttp中可以使用cancel方法,参考. OkHttp的request对象有tag.可以根据ta

Android OkHttp + Retrofit 下载文件与进度监听

本文链接 下载文件是一个比较常见的需求.给定一个url,我们可以使用URLConnection下载文件. 使用OkHttp也可以通过流来下载文件. 给OkHttp中添加拦截器,即可实现下载进度的监听功能. 使用流来实现下载文件 代码可以参考:https://github.com/RustFisher/android-Basic4/tree/master/appdowloadsample 获取并使用字节流,需要注意两个要点,一个是服务接口方法的 @Streaming 注解,另一个是获取到Respo