监控自己APP的http/https网络请求的地址和请求耗时

关于监控http/https网络请求的思路, 目前想到两种实现思路:

第一种实现思路是hook http请求的根接口, 目前大致是分为HttpURLConnection和Apache-Http-Client这两种, 当然也有可能使用square/okhttp或者koush/AndroidAsync, 但本质上是一样的, 不过因为使用的接口的不同, 有两个方案:
  • 一个方案是URLStreamHandlerFactory, 当使用(HttpURLConnection)URL.openConnection()时, 里面是去请求URLStreamHandlerFactory获得一个URLStreamHandler, 并由URLStreamHandler提供URLConnection子类(这里是HttpURLConnection或HttpsURLConnection), 所以控制住URLStreamHandlerFactory即可使用代理模式来监控, 可喜的是URL有个静态方法setURLStreamHandlerFactory来让末端程序员注入这种控制, 这里可参考’com.squareup.okhttp3:okhttp-urlconnection:3.3.1’ 或https://github.com/square/okhttp/tree/master/okhttp-urlconnection/src/main/java/okhttp3里的OkUrlFactory.java;

  • 另一个方案比较彻底, 是通过以下三个静态方法设置默认的tcp/udp socket实现类SocketImpl和DatagramSocketImpl:
    java.net.Socket.setSocketImplFactory(SocketImplFactory);
    java.net.ServerSocket.setSocketFactory(SocketImplFactory);
    java.net.DatagramSocket.setDatagramSocketImplFactory(DatagramSocketImplFactory); //不常用
    其中, 无网络代理(比如SOCKS或http代理)的情况下SocketImpl和DatagramSocketImpl可参考java.net.PlainSocketImpl和java.net.PlainDatagramSocketImpl, 通过反射和委托实现监控;
    注意: 不是javax.net.ServerSocketFactory, javax.net.SocketFactory, javax.net.ssl.SSLServerSocketFactory, javax.net.ssl.SSLSocketFactory;
    问题: 不过也很遗憾, 这种方式基本对https(SSL方式)无效, 不过也能理解, 按理说ssl方式的请求不应该被hook拦截. 不过还是讨论一下, 仅就javax.net.ssl.SSLSocketFactory调研发现, Android中, 它是com.android.org.conscrypt.OpenSSLSocketFactoryImpl类型, 其创建的SSLSocket, 是com.android.org.conscrypt.OpenSSLSocketImpl; 而在java中, 它是sun.security.ssl.SSLSocketFactoryImpl类型, 其创建的SSLSocket, 是sun.security.ssl.SSLSocketImpl; 而且虽然这两个SSLSocket子类间接继承自Socket, 但是它们可能没有使用SocketImpl, 所以setSocketImplFactory对其无作用, 由SSLSocket的一般使用方式: SSLSocketFactory.getDefault().createSocket()而知, 可能的方法是反射SSLSocketFactory, 既然是getDefault(), 肯定里面有个私有静态属性保存这个默认的SSLSocketFactory实例, 那么将这个私有静态属性赋值你的SSLSocketFactory的实现并实现SSLSocket, 我想应该就可以了, 但是工作量比较大;

第二种实现思路是aop/动态代理, 这种实现思路有非常高的参考价值, 它可以用于其他监控, 但是目前看它又是很难实践的, 具体分静态和动态两个方案:
  • 一个方案是静态织入字节码或dex文件,可以使用aspectj或者其他处理字节码的工具, 或者使用dex2jar/smali的类库操作dex文件, 目的是在所有网络请求的调用前后座aop切面;
    理论上可行, 因为我们织入的切点在method call而非method execute, 但是使用网络库的方式多种多样, 有的是multi-part/form-data, 有的是没返回值, 如果想统计一次网络耗时的话, 很难找到合适的开始点和结束点, 比如HttpURLConnection的connect方法调用前切入, 但是用户可能会调用getOutputStream/getInputStream, 可能close这些stream, 也可能调用disconnect, 也可能不调用, 找不到合适的结束切入点, 而且如何做到匹配开始和结束切入点呢? 因为开始和结束的method显然不同, 你也许会说分装一个http网络辅助类, 但是对于你依赖的类库显然不会遵守这个规矩;

  • 另一个方案是运行时动态生成代理类或者干脆利用DexClassLoader的DexPathList实现替换http实现类, 比如动态代理出HttpURLConnection的子类并替换掉ClassLoader中现存的HttpURLConnection实现类;
    可以使用的工具有java自带的Proxy和InvocationHandler, 也可以使用ow2/asmdex或crimsonwoods/javassist-android工具;
    实践发现, 由于HttpURLConnection这样的基础类是由BootClassLoader加载的, 目前很难能做到替换, 这不像你的自定义类那样实现热修复替换, 或许以后在这方面会有更好的思路, 我一直在关注alibaba/AndFix, 它在这方面的实现方式是少有限制的, 不知道兼容性上有无风险;

监控自己app的网络请求的需求还是现实的, 如果只是自己的代码, 那么封装一个类库, 比如基于okhttp即可, 但是有很多依赖类库/三方SDK会在内部做网络请求, 你不希望这些外部因素没用好网络的责任推在你的身上吧, 不过理想很美好, 但这不是坦途~

你可能感兴趣的:(android)