Android-stetho

Android-stetho

rainbowYao Lv3

概述

本文是对Facebook开源的Android调试工具Stetho的全面学习总结。Stetho是一个功能强大的Android应用调试桥梁,它可以通过Chrome浏览器的开发者工具来调试Android应用,提供了包括视图层级检查、数据库查看、网络抓包、文件系统管理等多种调试功能。

文章主要内容包括:

  1. 基础集成指南 - 详细介绍了Stetho的下载、集成方法,以及与新版Chrome的兼容性问题解决方案
  2. 核心功能展示 - 通过截图演示了布局视图检查、SQLite数据库查看、网络抓包、DumpApp命令行工具等主要特性
  3. 源码深度解析 - 对Stetho的核心模块进行了详细的代码分析,包括:
    • 初始化流程和配置系统
    • DumpApp插件机制实现
    • 数据库检查模块(inspector/database)
    • 网络拦截模块(inspector/network)
  4. DumpApp实战开发 - 介绍了内置的四个DumpApp插件,并提供了自定义插件的实现思路,特别是MMKV数据管理插件的完整实现
  5. 通信机制深度剖析 - 详细分析了dumpapp脚本与Android应用之间的通信原理,包括ADB连接建立、Unix域套接字通信、自定义协议设计等底层实现

学习网站来源

下载

  • 在gradle中添加依赖
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // Gradle dependency on Stetho 
    dependencies {
    debugCompile 'com.facebook.stetho:stetho:1.5.1'
    //下面依赖是非必须的,只有在需要监控网络的时候需要添加
    debugCompile 'com.facebook.stetho:stetho-urlconnection:1.5.1'
    debugCompile 'com.facebook.stetho:stetho-okhttp3:1.5.1'
    //下面也非必须,在需要JS命令行的时候添加
    debugCompile 'com.facebook.stetho:stetho-js-rhino:1.6.0'
    }

上面是官网的做法,经本人测试,stetho与新版的chrome(130)联动有bug,官方仓库修复后并没有release,故建议导入模块或者使用MavenLocal

集成(来自官网)

  • 在application中注册

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class MyApplication extends Application {
    @Override
    public void onCreate() {
    super.onCreate();
    // 在调试模式下,启动stetho服务
    if (BuildConfig.DEBUG) {
    Stetho.initializeWithDefaults(this);
    }
    }
    }

    <application
    android:name=".MyApplication" <!-- 注册自定义 Application 类 -->
    <!-- 其他组件声明 -->
    </application>
  • 启用网络检查

    1
    2
    3
    4
    5
    OkHttpClient.Builder builder = new OkHttpClient.Builder();
    if (BuildConfig.DEBUG) {
    builder.addInterceptor(new StethoInterceptor());
    }
    instance = builder.build();
  • 自定义 dumpapp 插件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    Stetho.initialize(Stetho.newInitializerBuilder(context)
    .enableDumpapp(new DumperPluginsProvider() {
    @Override
    public Iterable<DumperPlugin> get() {
    return new Stetho.DefaultDumperPluginsBuilder(context)
    .provide(new MyDumperPlugin())
    .finish();
    }
    })
    .enableWebKitInspector(Stetho.defaultInspectorModulesProvider(context))
    .build());

    自定义插件是扩展 dumpapp 系统的首选方式,可以在配置过程中轻松添加。

stetho-sample

官方的示例Demo,可以供学习DumpApp的写法或者测试Chrome功能

  • git clone https://github.com/facebook/stetho.git
  • 记得使用低版本的JDK,比如1.8
  • javaDoc报错,故注释掉这段代码即可暂时编译成功:
    1
    2
    3
    4
    5
    source = android.sourceSets.main.java.srcDirs
    classpath += files(android.bootClasspath)
    if (JavaVersion.current().isJava8Compatible()) {
    options.addStringOption('Xdoclint:none', '-quiet')
    }

    若发现stetho-sample可以在chrome中展示,但是自己集成的项目不行,一种可能的原因是stetho-1.6.0与新版Chrome有兼容性问题,建议将github代码仓库中的stetho上传到MavenLocal或者当作模块导入(stetho已经停止维护,且其后续有更改并没有release到maven中)

特征

  • 从chrome://inspect/#devices点击inspect进入chrome调试界面
    1
  • 布局视图,可以看view树,视图对应的style、properties
    2
  • 可以查看设备的数据存储,包括SQLite
    3
  • 可以抓包
    4
  • DumpApp的使用
    • 使用自带的SharedPreferencesDumperPlugin,可以实现运行模式下SP的读写
      5
    • 自行拓展DumpPlugin,如:MmkvDumperPlugin,实现MMKV的增删改查
      6

值得注意得是需要在github将script文件夹下载下来,里面包含了用python3实现的命令dumpapp

核心代码解析

com/facebook/stetho/Stetho.java

  • initialize、InitializerBuilder初始化
  • enableDumpapp、enableWebKitInspector添加配置
  • PluginBuilder 配置 Dumper 插件和 Inspector 模块
  • DefaultInspectorModulesBuilder、defaultInspectorModulesProvider,finish中会自动把SqliteDatabaseDriver加进来,也可以自己继承或者exclude
  • DefaultDumperPluginsBuilder、defaultDumperPluginsProvider,finish中会自动添加内置的四个插件:
    1
    2
    3
    4
    provideIfDesired(new HprofDumperPlugin(mContext));
    provideIfDesired(new SharedPreferencesDumperPlugin(mContext));
    provideIfDesired(new CrashDumperPlugin());
    provideIfDesired(new FilesDumperPlugin(mContext));

scripts

  • dumpapp:主要脚本,负责解析命令行参数并连接到 Stetho 启用的应用程序
    • main:这段代码是主入口,处理命令行参数并建立连接
    • read_frames:这个函数处理从 Stetho 服务器返回的帧。每个帧有不同的代码表示不同类型的数据
  • stetho_open.py:提供连接到 Android 设备上的 Stetho socket 的底层功能
    • stetho_open:这个函数建立到 Stetho 启用应用的连接。它首先连接到 ADB 服务器,然后使用 ADB 转发到设备上的 socket
    • _find_only_stetho_socket:这个函数在设备上查找运行 Stetho 的进程,通过检查 /proc/net/unix 的输出
    • _connect_to_device:依托设备、端口以及 AdbSmartSocketClient 连接设备
    • AdbSmartSocketClient:实现了 ADB 协议,用于与 Android Debug Bridge 服务器通信
  • hprof_dump.sh:用于生成和转换 HPROF 内存分析文件的辅助脚本

com/facebook/stetho/dumpapp/

  • Dumper.java
    • Dumper 类是 Stetho 框架中的一个核心组件,主要负责处理来自命令行的转储请求并将其路由到相应的插件。它位于 Java 端,作为接收和执行命令的处理器。
  • GlobalOptions.java
    • 配置命令行的选项
  • DumperContext.java
    • Dumper上下文:mStdin、mStdout、mStderr、mParser、mArgs
  • DumperPlugin.java
    • Dumper的插件接口,需要实现getName、dump(DumperContext dumpContext)方法
  • ArgsHelper.java
    • nextOptionalArg、nextArg、drainToArray等处理命令行参数的工具类
  • plugins/SharedPreferencesDumperPlugin.java
    • 实现具读写SP功能的plugin,需要实现接口DumperPlugin
  • DumpUsageException.class、DumpException.class等异常类

Stetho 的 Dumper 类与 Python 脚本形成了一个完整的客户端-服务器系统:

  • Python 脚本作为客户端,负责解析命令行参数、建立连接并发送请求
  • Java 端的 Dumper 类作为服务器端处理程序,接收请求并路由到适当的插件
  • 通过一个自定义二进制协议,两者可以进行双向通信

com/facebook/stetho/inspector/database/

  • DatabaseConnectionProvider.java、DatabaseFilesProvider.java

    • 提供数据库文件以及SQLite对象的接口
  • DefaultDatabaseFilesProvider.java

    • 实现DatabaseFilesProvider接口
    • 负责收集应用中的数据库文件,通过mContext.databaseList()获得应用中的所有数据库文件
  • DefaultDatabaseConnectionProvider.java

    • 实现DatabaseConnectionProvider接口
    • 负责创建 SQLite 数据库连接
  • SqliteDatabaseDriver.java

    • 数据库功能的核心实现类、处理SQLite数据库查询与展示
    • getDatabaseNames获取数据库文件、排序、过滤临时文件,将每个文件包装为描述符返回
    • getTableNames获得数据库中表和视图的名字
    • executeSQL根据 SQL 类型分发到不同处理方法
  • SQLiteDatabaseCompat.java

    • 兼容性层,用于在不同的 Android 版本中管理 SQLite 数据库的特性,如写前日志(WAL)和外键约束
    • 这个文件和DefaultDatabaseConnectionProvider中处理打开数据库特性部分的设计挺好的,值得学习,也有一些我没见过的语法
  • Database.java

    • 实现 Chrome DevTools 协议中数据库功能的核心类
    • 处理来自 Chrome DevTools 的数据库相关请求
    • 管理数据库驱动和连接,执行 SQL 查询并格式化结果
  • BaseDatabaseDriver.java、DatabaseDriver2

    • 定义数据库驱动的核心接口

com/facebook/stetho/inspector/network

  • StethoInterceptor.java

    • 核心作用: OkHttp3 的网络拦截器,是 Stetho 抓包功能的入口点和关键实现
    • 拦截流程:
      • intercept(): 主要拦截方法,完整的网络请求生命周期管理
      • 生成唯一 requestId 用于追踪单个请求
      • 创建 RequestBodyHelper 处理请求体数据
      • 调用 chain.proceed() 执行实际网络请求
      • 异常处理: 捕获 IOException 并通过 httpExchangeFailed() 报告
    • 响应流拦截:
      • 通过 interpretResponseStream() 拦截响应流
      • 使用 ForwardingResponseBody 包装响应体,替换原始 InputStream
      • 确保拦截后的流能正常传递给应用层
    • Connection 验证: 检查是否使用了正确的 addNetworkInterceptor,而非 addInterceptor
  • NetworkEventReporter.java

    • 网络事件报告的核心接口,定义了完整的网络监控生命周期
    • requestWillBeSent(): 请求即将发送时调用
    • responseHeadersReceived(): 接收到响应头时调用
    • interpretResponseStream(): 关键方法 - 拦截响应流,实现数据捕获和流包装
    • dataSent()/dataReceived(): 报告实际传输的数据量(压缩前后)
    • httpExchangeFailed(): 处理网络异常情况
    • 定义了 InspectorRequest、InspectorResponse、InspectorWebSocketFrame 等核心接口
  • NetworkEventReporterImpl.java

    • 实现具体的网络事件报告逻辑,单例模式管理全局网络监控
    • 将 Android 网络数据转换为 Chrome DevTools Protocol 格式
    • 使用 NetworkPeerManager 管理调试客户端连接状态
    • 通过 sendNotificationToPeers() 向所有连接的调试客户端发送事件
    • 支持请求体的自动解压缩和格式化显示,处理 JSON 序列化
  • NetworkPeerManager.java

    • 网络模块的核心管理器,继承自 ChromePeerManager
    • 响应体文件管理: 持有 ResponseBodyFileManager 实例,负责临时文件的生命周期
    • 美化打印器管理: 管理 AsyncPrettyPrinterRegistry,用于格式化不同类型的响应数据
    • 连接状态监听: 通过 PeersRegisteredListener 监听调试客户端的连接状态
    • 延迟初始化和自动清理机制
  • AsyncPrettyPrinter 系列

    • AsyncPrettyPrinter.java: 异步美化打印器接口,支持不同数据格式
    • AsyncPrettyPrinterRegistry.java: 管理不同类型数据的美化器注册表
    • AsyncPrettyPrinterExecutorHolder.java: 线程池管理,避免阻塞主线程
    • DownloadingAsyncPrettyPrinterFactory.java: 支持大文件的分块美化处理
  • Network.java (协议模块)

    • 定义了 Chrome DevTools Network 协议的所有数据结构
    • Request、Response、Initiator 等核心类型定义
    • 支持 JSON 序列化注解,确保与调试客户端的协议兼容性
    • 包含请求/响应的完整元数据:URL、方法、头部、时间戳等
  • ResponseBodyFileManager.java

    • 管理网络响应的临时文件存储,使用应用私有目录
    • 支持 Base64 编码标识和数据压缩
    • 集成异步美化打印器,支持 JSON/XML 等格式的格式化显示
    • 自动文件清理机制,避免存储空间泄漏
  • ResponseHandlingInputStream.java

    • 抓包核心实现 - 装饰器模式包装原始 InputStream
    • 实现数据的双写机制:流向应用的同时写入临时存储
    • 支持数据解压缩计数和进度报告
    • 透明处理,对应用代码完全无感知
  • RequestBodyHelper.java / DefaultResponseHandler.java

    • RequestBodyHelper: 处理请求体的压缩、解压缩和数据统计
    • DefaultResponseHandler: 响应数据读取的默认处理器,报告读取进度和完成状态
    • 支持 gzip/deflate 等标准 HTTP 压缩格式
  • 工具类组件

    • CountingOutputStream.java: 统计输出流写入字节数的装饰器
    • DecompressionHelper.java: HTTP 响应解压缩帮助类(gzip/deflate)
    • GunzippingOutputStream.java: 专门的 gzip 解压缩输出流
    • ResourceTypeHelper.java: 根据 Content-Type 判断资源类型
    • MimeMatcher.java: MIME 类型匹配和识别
  • WebSocket 支持

    • SimpleBinaryInspectorWebSocketFrame.java: 二进制 WebSocket 帧封装
    • SimpleTextInspectorWebSocketFrame.java: 文本 WebSocket 帧封装
    • 完整支持 WebSocket 握手、数据帧传输等生命周期监控

DumpApp

自带的四个DumpApp:

  • HprofDumperPlugin:用于导出应用的 Heap Dump (HPROF 文件),即应用内存中的对象及其引用关系
  • SharedPreferencesDumperPlugin:读写应用的 SharedPreferences 数据
  • FilesDumperPlugin:导出应用的 文件系统 中的文件
  • CrashDumperPlugin:用于导出 崩溃日志 (Crash logs)

自行实现几个DumpApp:

  • 根据sample实现HelloWorldDumperPlugin
  • 根据自己的Demo实现PosterMemoryDumperPlugin,实现内存海报数据的转储
  • 完成MmkvDumperPlugin,实现MMKV数据的增删改查

实现DumpApp时需要实现DumperPlugin接口,只需改写getName、dump函数即可;可以使用DumperContext获得输入输出流、命令行传递来的参数等

MmkvDumperPlugin实现思路

综述

MmkvDumperPlugin 通过实现 DumperPlugin 接口来提供 MMKV 数据的增删改查操作。核心实现包括以下几点:

  1. 插件构建:
    • 实现 getName 和 dump 方法。dump 方法根据命令行传入的操作命令(如 list、get、write 等)执行对应的 MMKV 操作。
  2. 获取 MMKV 实例:
    • 根据命令行参数动态选择 MMKV 实例。如果指定了 -t mmkvId,则使用指定的实例;否则,使用默认实例。
  3. 支持以下命令:
    • list:列出所有 MMKV 键值。
    • get:获取指定键的值。
    • write:写入数据到 MMKV。
    • remove:删除指定键。
    • clear:清空所有数据。
  4. 错误处理:
    • 使用 DumpUsageException 处理错误,如参数缺失或 MMKV 实例获取失败。

主要代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override
public void dump(DumperContext dumpContext) throws DumpUsageException {
List<String> args = dumpContext.getArgsAsList();
String commandName = args.isEmpty() ? "" : args.remove(0);
mMmkv = getMmkvFromParams(args);

if (mMmkv == null) {
throw new DumpUsageException("无法获取 MMKV 实例");
}

switch (commandName) {
case "list": doList(dumpContext.getStdout(), args); break;
case "get": doGet(dumpContext.getStdout(), args); break;
case "write": doWrite(args, dumpContext.getStdout()); break;
case "remove": doRemove(args); break;
case "clear": doClear(); break;
default: doUsage(dumpContext.getStdout());
}
}

Stetho 通信机制深度解析

dumpapp 与安卓应用的连接原理

架构概览

7

连接建立流程

PC端连接准备(dumpapp脚本)

1.1 参数解析
1
2
3
4
5
6
7
8
9
10
# 从命令行参数或环境变量获取进程名
process = os.environ.get('STETHO_PROCESS')
if len(args) > 0 and (args[0] == '-p' or args[0] == '--process'):
process = args[1]

# 获取设备序列号(可选)
device = os.environ.get('ANDROID_SERIAL')

# 获取ADB服务器端口(默认5037)
port = get_adb_server_port()
1.2 建立ADB连接
1
2
# 通过stetho_open建立到设备的连接
sock = stetho_open(device, process, port)

2. stetho_open连接机制详解

2.1 ADB服务器连接
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def stetho_open(device=None, process=None, port=None):
# 1. 连接到ADB服务器
adb = _connect_to_device(device, port)

# 2. 确定目标socket名称
if process is None:
socket_name = _find_only_stetho_socket(device, port)
else:
socket_name = _format_process_as_stetho_socket(process)

# 3. 通过ADB转发连接到应用的Unix域套接字
adb.select_service('localabstract:%s' % socket_name)

return adb.sock
2.2 Socket名称格式化
1
2
3
def _format_process_as_stetho_socket(process):
# 格式: stetho_{package_name}_devtools_remote
return 'stetho_%s_devtools_remote' % process
2.3 自动发现Stetho进程
1
2
3
4
5
6
7
8
9
10
11
12
def _find_only_stetho_socket(device, port):
# 1. 通过shell命令查看Unix域套接字
adb.select_service('shell:cat /proc/net/unix')

# 2. 解析输出,查找@stetho_开头的套接字
for line in adb.sock.makefile():
row = re.split(r'\s+', line.rstrip())
socket_name = row[7]
if socket_name.startswith('@stetho_'):
# 3. 验证是服务器套接字
if int(row[3], 16) == 0x10000 and int(row[5]) == 1:
return socket_name[1:] # 去掉@前缀

3. 协议握手与通信

3.1 协议握手
1
2
# 发送DUMP协议标识和版本号
sock.send(b'DUMP' + struct.pack('!l', 1))
3.2 命令参数传输
1
2
3
4
5
6
# 构造参数帧
enter_frame = b'!' + struct.pack('!l', len(args))
for arg in args:
argAsUTF8 = arg.encode('utf-8')
enter_frame += struct.pack('!H%ds' % len(argAsUTF8), len(argAsUTF8), argAsUTF8)
sock.send(enter_frame)
3.3 数据帧解析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def read_frames(sock):
while True:
# 读取帧头:1字节代码 + 4字节长度
code = read_input(sock, 1, 'code')
n = struct.unpack('!l', read_input(sock, 4, 'int4'))[0]

if code == b'1': # 标准输出
sys.stdout.buffer.write(read_input(sock, n, 'stdout blob'))
elif code == b'2': # 错误输出
sys.stderr.buffer.write(read_input(sock, n, 'stderr blob'))
elif code == b'_': # 输入请求
data = sys.stdin.buffer.read(n)
sock.send(b'-' + struct.pack('!l', len(data)) + data)
elif code == b'x': # 退出码
sys.exit(n)

Android端实现分析

1. 服务器初始化

1.1 LocalSocketServer创建
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class LocalSocketServer {
private final String mAddress; // Unix域套接字地址
private final SocketHandler mSocketHandler;

public void run() throws IOException {
mServerSocket = bindToSocket(mAddress);
LogUtil.i("Listening on @" + mAddress);

while (!Thread.interrupted()) {
LocalSocket socket = mServerSocket.accept();
// 为每个连接创建工作线程
Thread t = new WorkerThread(socket, mSocketHandler);
t.start();
}
}
}
1.2 套接字地址生成
1
2
// 在Stetho初始化时生成
String socketName = "stetho_" + packageName + "_devtools_remote";

2. 协议处理

2.1 DumpappSocketLikeHandler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class DumpappSocketLikeHandler implements SocketLikeHandler {
public static final byte[] PROTOCOL_MAGIC = new byte[] { 'D', 'U', 'M', 'P' };
public static final int PROTOCOL_VERSION = 1;

@Override
public void onAccepted(SocketLike socket) throws IOException {
DataInputStream in = new DataInputStream(socket.getInput());

// 1. 协议握手验证
establishConversation(in);

// 2. 创建帧处理器
Framer framer = new Framer(in, socket.getOutput());

// 3. 读取命令参数
String[] args = readArgs(framer);

// 4. 执行dump命令
dump(mDumper, framer, args);
}
}
2.2 协议验证
1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void establishConversation(DataInputStream in) throws IOException {
// 验证魔数
byte[] magic = new byte[4];
in.readFully(magic);
if (!Arrays.equals(PROTOCOL_MAGIC, magic)) {
throw new IOException("Incompatible protocol");
}

// 验证版本
int version = in.readInt();
if (version != PROTOCOL_VERSION) {
throw new IOException("Expected version=" + PROTOCOL_VERSION + "; got=" + version);
}
}

3. 命令执行框架

3.1 Dumper接口
1
2
3
public interface Dumper {
int dump(InputStream stdin, PrintStream stdout, PrintStream stderr, String[] args);
}
3.2 插件系统

Stetho支持多种内置插件、也可自行实现插件

总结

Stetho的连接机制巧妙地利用了:

  1. ADB的端口转发功能:将TCP连接转发到Unix域套接字
  2. Android的LocalSocket API:提供高效的进程间通信
  3. 自定义协议:支持双向数据流和命令执行
  4. 插件化架构:可扩展的调试功能

其他相关联源码,并未在这部分展示:

  1. Framer类: 负责协议帧的编码/解码
  2. 各种DumperPlugin: 具体功能实现
  3. ProtocolDetectingSocketHandler: 协议检测和路由
  4. Stetho初始化代码: 整个服务的启动过程
  • 标题: Android-stetho
  • 作者: rainbowYao
  • 创建于 : 2025-09-02 17:20:06
  • 更新于 : 2025-09-02 17:21:39
  • 链接: https://redefine.ohevan.com/2025/09/02/Android-stetho/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。