便捷开发少不了的Stetho

最近,我负责将Stetho集成到我们项目中。集成过程中,免不了出现一些坑,在此记录填坑之路。

Stetho 是一个功能强大的 Android 应用调试桥,起到桥梁的作用,连接 Android 应用和 Chrome,通过 Chrome 开发者工具调试 Android 应用,提供视图元素检查,网络监控,数据库动态交互,Dumpapp(可扩展的命令行交互接口),JavaScript Console 等功能。

仅开发期间集成使用

Stetho如果集成到项目中,必然是只在debug模式下使用

  1. 添加依赖

    1
    2
    3
    4
    dependencies { 
    //这个依赖是必须的
    debugApi 'com.facebook.stetho:stetho:1.5.0'
    }

    我们肯定还要支持网络监控功能,所以还需要添加一个网络助手依赖,因为我们项目使用的网络库是okhttp3,所以添加如下依赖

    1
    2
    3
    dependencies { 
    debugApi 'com.facebook.stetho:stetho-okhttp3:1.5.0'
    }

    如果项目使用的是HttpURLConnection,需要添加如下依赖

    1
    2
    3
    dependencies { 
    debugApi 'com.facebook.stetho:stetho-urlconnection:1.5.0'
    }

    如果还需要支持JavaScript console的功能(个人感觉这个我们不太用的上,所以没集成)需要添加如下依赖

    1
    2
    3
    dependencies { 
    debugApi 'com.facebook.stetho:stetho-js-rhino:1.5.0'
    }
  2. 创建DebugApplication

    在app/src/debug/java目录下,创建一个DebugApplication,并继承于我们之前的MainApplication。由于我们项目使用了Tinker,而Tinker使用了Application代理机制来对Applicaition实现热修复。所以,这里我们对DebugApplication也添加注解,生成项目真正的Application。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    @DefaultLifeCycle(application = "com.application.PatientDebugApplication",
    flags = ShareConstants.TINKER_ENABLE_ALL,
    loadVerifyFlag = false)
    public class DebugApplication extends MainApplication {

    //省略构造方法..

    @Override
    public void onCreate() {
    super.onCreate();
    //每个进程都初始化Stetho
    initStetho();
    }

    private void initStetho() {
    //启用大多数默认配置 包括UI元素检查,SP及数据库的查看修改
    Stetho.initializeWithDefaults(getApplication());
    }

    }

    同时还要在debug目录下创建一个 AndroidManifest.xml,添加 debug 模式下需要的权限和修改 application 节点 android:name 值为Tinker注解生成的真正的Application,其余的配置把项目的原先的Application抄过来就行。(使用 tools:replace 覆盖 android:name 字段)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <!---->
    <application
    android:name="com.application.PatientDebugApplication"
    android:hardwareAccelerated="false"
    android:icon="@drawable/app_logo"
    android:label="${APP_NAME}"
    android:largeHeap="true"
    android:persistent="true"
    android:allowBackup="false"
    android:theme="@style/Theme.Guahao.white" tools:replace="android:icon,android:theme,android:hardwareAccelerated,android:label,android:largeHeap,android:persistent,android:allowBackup,android:name">
    <activity
    //省略工厂activity
    ....
    </application>
    </manifest>

    目录结构图

    1542769971646

    我们还需要支持网络监控,方便我们查看页面产生的请求。按照官网,我们还需要添加如下的代码。

    1
    2
    3
    OkHttpClient client = new OkHttpClient.Builder()
    .addNetworkInterceptor(new StethoInterceptor())
    .build()

    运行这段代码后,发现网络监控并没有作用。查看okhttp3源码及我们项目代码发现,1.拦截器要生效必须每次请求都要使用该Client。2.我们的网络库在这之前已经初始化了client并使用单例。也就是说,我们必须是要在已经生成的client中添加网络拦截器。

    查看我们的网络库代码,可以获取该Client

    1
    OkHttpClient okHttpClient = OkHttpServerClientImpl.getInstance().getmClient();

    查阅okhttp3源码,也发现这样一个方法。

    1
    2
    3
    4
    5
    6
    7
    8
    /**
    * Returns an immutable list of interceptors that observe a single network request and response.
    * These interceptors must call {@link Interceptor.Chain#proceed} exactly once: it is an error for
    * a network interceptor to short-circuit or repeat a network request.
    */
    public List<Interceptor> networkInterceptors() {
    return networkInterceptors;
    }

    看注释,这里返回的是一个不可变的List。所以如果直接在该List添加StethoInterceptor必然会报错。

    那还有别的办法么?嗯..好像只能靠反射大法了。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    //支持网络监控
    OkHttpClient okHttpClient = OkHttpServerClientImpl.getInstance().getmClient();
    List<Interceptor> list = new ArrayList<>();
    list.addAll(okHttpClient.networkInterceptors());
    list.add(new StethoInterceptor());
    Class<OkHttpClient> clz = OkHttpClient.class;
    try {
    Field field = clz.getDeclaredField("networkInterceptors");
    field.setAccessible(true);
    field.set(okHttpClient, list);
    } catch (Exception e) {
    e.printStackTrace();
    }

    运行代码后,问题已解决。

使用步骤

打开Chrome浏览器,输入:chrome://inspect,找到自己的应用并点击inspect

  1. View Hierarchy

    在弹出的页面选择Elements,会出现当前页面的UI结构。点击某个元素节点,手机界面中对应的控件会高亮。点击左上角的搜索按钮,再点击app当前界面的控件,View Hierarchy 会显示该控件在层次中的位置。

    从此,可以很方便地查看h5页面哪些元素是原生的,哪些元素是H5的,甩锅再也不是问题了

    1542716190580

  2. 网络监控

    在弹出的页面选择Network,会列出当前页面出现的所有请求的详细信息。包括请求方法,请求状态,请求大小,请求时间,请求头,响应头,响应体等等信息。

    从此,再也不会出现response过长,AS显示不全的问题了,再也不需要额外使用HiJson这样的格式化工具了。

    1542716743905

    1542716814617

  3. 数据库的可视化查看及控制台修改

    在弹出的页面选择Resources,数据库文件就放在Web SQL目录下,随意点击数据库,可以看到其下的表文件,点击表文件可以看到右边会显示里面的数据。

    点击具体的数据库,可以使用SQL语句对其中的表直接进行操作。并且在使用SQL语句的时候,还会自动提示补全。

  4. SP文件的可视化查看及直接修改

    在弹出的页面选择Resources,SP文件就放在Local Storage目录里。点击任意SP文件,可以看到里面的值,并支持对key-value直接修改。

    1542779550634

  5. JavaScript Console

    如果集成了JavaScript Console功能,可以在弹出的页面选择Console。JavaScript Console允许执行那些可以与应用或 Android SDK 交互的 JavaScript 代码。目前该功能比较鸡肋,因为自带的console只能关联到application的context,能进行的操作非常有限,且在控制台写js调用Java层的函数是没有自动补全的,容易写错不说,要换成Js的语法也是相当费劲

    Stetho 使用 Rhino 实现使用脚本方式调用 Java。

    关于 Rhino 相关语法可以参考下面的文档

  6. Dump App

    Dump App构造了一个命令行与Android App的交互通道,在命令行输入一行命令,App可以收到并且在命令行上进行反馈输出。并且支持自定义拓展命令行。

    没用过,感觉不是有很大用处。