关于 lionscope
lionscope 是在浏览器中运行的 Android 性能分析器和布局检查器,面向开发者及其 AI Agent。它可将性能、布局、网络和日志信息放在统一时间轴中,并可通过可选 SDK 提供更深入的代码级数据。
主要功能
- 01统一时间轴查看 CPU、内存、帧与日志
- 02检查视图属性及 Compose 界面
- 03定位主线程卡顿及其来源代码
- 04查看 HTTP 流量并模拟响应
- 05操作模拟器与 USB 设备
适用场景
- 01排查应用卡顿及高耗时帧
- 02检查布局层级并编辑视图属性
- 03分析请求耗时或测试模拟响应
- 04让 AI Agent 查询设备和性能数据
MCP 相关
通过 MCP 或 CLI 将设备和分析数据提供给 AI Agent 使用。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
16收录的工具列表 · 更新于 今天
此工具无需参数。
详细介绍
Lionscope
Lionscope 是一个轻量级的 Android 性能分析器和浏览器中的布局检查器,AI Agent 也可以使用它。无需 Android Studio:运行 npx lionscope@latest,选择设备和应用。
- 性能分析:在同一条共享时间轴上,查看单个应用的 CPU、内存、帧渲染、主线程卡顿、布局加载和 logcat。使用 SDK 时,每次卡顿都会标出造成阻塞的代码行,以及相关工作来自哪里(RxJava 链及其订阅代码行、协程、已提交的 Runnable、点击操作)。
- 检查:冻结屏幕,点击一个视图并查看其属性;使用 SDK 时,还可以查看 XML 布局及其来源代码行和所属 Fragment、Compose 屏幕的 composable 及其调用代码所在的 Kotlin 行,以及在运行中的应用里实时修改 View 属性(内边距、尺寸、颜色选择器、文本等)。
- 网络:使用 SDK 的 OkHttp 拦截器,在同一条时间轴上查看应用的 HTTP 请求(标头、请求体、耗时),并通过仪表板模拟响应或故障。
- 操作:在图表旁查看并控制模拟器(也可以无头启动)和 USB 设备,并在地图上将它们移动到其他位置。
- AI Agent:通过 MCP 或命令行使用相同的数据作为工具(Claude Code、Cursor、VS Code、Codex 等)。在检查器中选择一个视图,然后询问你的 Agent“这个元素”。
它运行起来很轻量。在模拟器上使用示例应用测得:应用空闲时,服务占用不到 1% 的单个 CPU 核心;滚动时约占 4%,内存占用为 100–150 MB。SDK 在应用中增加约 0.5% 的 CPU 占用。
它支持两种模式:
- 仅使用 ADB(无需修改应用):所有数据均通过
adb shell(/proc、dumpsys、logcat)收集。 - 在应用的 debug 构建中使用可选 SDK:增加当前最上层屏幕的信息(Activity、Fragment、DialogFragment)、逐帧
FrameMetrics及其分阶段明细(按屏幕分组)、由导致卡顿的应用代码行及相关工作来源提供的主线程卡顿信息(以及选择启用的 StrictMode 违规信息)、布局加载耗时、布局检查器自身的视图树及布局来源和所属对象、Compose 屏幕的 composable、View 的实时修改,以及(在OkHttpClient中添加一行代码后)应用的 HTTP 流量和模拟响应。
无头模拟器上运行的内置示例应用,由 Device 列进行操作:所有图表中都有屏幕区段,Rendering 轨道中有 SDK FrameMetrics,时间轴下方显示 logcat,Screens 标签页按卡顿程度排列屏幕。
仪表板显示内容
仪表板是一个单页界面:顶部栏用于选择设备和应用,以及启动或停止会话;左侧显示模拟器屏幕;中间是堆叠的时间轴图表,共用一条时间轴(最近 1、5 或 10 分钟),并且可以暂停;下方抽屉显示应用的 logcat(拖动其横条可调整大小,双击横条可最大化;点击、按住 Shift 点击或按住 Cmd/Ctrl 点击行,然后按 Cmd/Ctrl+C 即可复制);使用 SDK 时,右侧还会显示 Screens、Stalls、Layouts 和 Network 标签页。宽度小于 1280 px 时,屏幕会从顶部栏展开为抽屉(在 Profiler 视图中;另外两个视图会将其保留在页面上)。
顶部栏右侧的切换开关用于选择页面的用途:
- Profiler(默认):包含以上所有内容。
- Inspector:仅显示设备和 Layout Inspector, 后者会显示房间,以便并排查看视图树和属性。
- Device:仅显示更大的设备屏幕,以及如何让 AI Agent 操作设备(参见 与 AI Agent 配合使用)。
Inspector 和 Device 视图会隐藏图表和 logcat,并停止绘制它们,因此浏览器中不会有任何隐藏内容持续重绘;运行中的会话仍会继续记录,切回时,图表和 logcat(及其筛选器)会保持原样。视图属于地址的一部分(http://localhost:8701/?view=inspect、?view=device),因此可以为其添加书签,页面也会记住你上次使用的视图。
| 面板 | 来源 | 间隔 |
|---|---|---|
| CPU % + 线程 | /proc/<pid>/stat | 1 s |
| 内存(PSS:Java / Native / Graphics / Code / Other) | dumpsys meminfo --local <pid> | 3 s |
| 帧(FPS、卡顿、p50/p90 帧时间) | dumpsys gfxinfo <pid> framestats reset(+ /proc/uptime,用于设备端经过时间) | 0.5 s |
| Logcat(仅应用 PID) | logcat -v epoch --pid=<pid> | 流式 |
| 进程启动 / 重启 / 终止标记 | pidof <pkg> | 1 s |
在应用的调试构建中安装 SDK 后,仪表板还会显示:
| 面板 | 来源(应用内 SDK) |
|---|---|
| 屏幕轨道 + 所有图表上的色调 | Activity / Fragment / DialogFragment 生命周期(顶部屏幕) |
渲染 / 帧时间(来源 SDK) | Window.OnFrameMetricsAvailableListener(API 24+) |
| 帧细分(input … swap、delay、GPU) | FrameMetrics 阶段,按每 500 ms 聚合 |
| 每屏幕表格(帧、卡顿率、p90、主要阶段) | 上述数据,按屏幕分组 |
| 主线程轨道(每次卡顿一条) | 主线程看门狗;StrictMode 违规以刻度标示 |
| Stalls 标签页(违规项、事件、堆栈详情) | 采样的主线程堆栈;StrictMode 违规(选择启用) |
| Layouts 标签页、Inflate 列、金色轨道标记 | XML 布局膨胀计时(Android 10+) |
| Views 列(每个屏幕的数量和深度) | 屏幕首帧后的 View 层级快照 |
| Network 标签页、Network 轨道、模拟响应 | 应用 OkHttpClient 中的 Lionscope.okHttpInterceptor() |
SDK 帧持续传入时,gfxinfo 轮询会暂停(避免重复观测和 reset 冲突);停止传入 3 s 后恢复。仪表板会显示 SDK 是否已连接(及其版本),或说明如何添加 SDK。
Screens 标签页按卡顿率对屏幕排序,并标出每个屏幕的主要帧阶段(面板更宽时,还会显示 p90 和慢帧)。如果应用为屏幕设置了别名(home_tab、legacy_page_1),则使用该别名;否则使用类名。

从 Device 列点击示例中的 Home 按钮(这些按钮会直接通过 RxJava 调用链或协程阻塞主线程)后显示的 Stalls 页签,违规项按 来源 分组:工作来自哪里、发生频率以及持续时间。按行号 则会对发生阻塞的代码行进行排序。每次卡顿都列出其持续时间、行号和来源;启用 StrictMode 后,磁盘和网络违规也会连同调用栈一起加入列表。
找出阻塞主线程的代码行
使用 SDK(SDK 和服务版本为 0.2.0+)时,看门狗线程每隔 50 ms 向主线程发送一次 tick。如果 tick 未能在阈值内运行,它会每隔 50 ms 对主线程的调用栈进行采样,直到阻塞结束,然后报告一次卡顿:开始时间、持续时长、当前最上层的屏幕,以及采样次数最多的调用栈(阻塞时间最长记录为 30 s)。控制面板会按引发卡顿的代码行(Class.method (File.kt:87))对卡顿进行排序:首先显示第一个应用代码栈帧;对于负责 Inflation 的代码,会跳过生成的 ViewBinding/DataBinding 类。如果调用栈中没有应用代码栈帧,则显示 Android、Java、Kotlin 和 AndroidX 包之外的第一个库代码栈帧(即阻塞主线程的第三方 SDK)。调用栈中的框架代码栈帧会折叠;若卡顿发生时已连接调试器,也会标记出来。
阈值可在 Stalls 页签中选择(100、150、250、500 或 1000 ms;默认值为 150 ms)。只有当控制面板通过协议 v2 连接且某个 Activity 已启动时,看门狗才会运行。
工作来源(0.9.0)。 使用 RxJava 的 observeOn(AndroidSchedulers.mainThread()) 或在 Dispatchers.Main 上运行协程时,主线程调用栈显示的是调度器或分发器以及 Lambda,而不是调度任务的代码位置。因此,每次卡顿还会记录一个来源,Stalls 页签中的按来源视图会像引发卡顿的代码行一样对来源进行排序(次数、总阻塞时间和最长阻塞时间;点击某行可筛选事件):
- RxJava:运行任务的操作符(
observeOn、subscribeOn、timer……)和订阅调用链的代码行(HomeViewModel.load (HomeViewModel.kt:40),其调用栈会显示在卡顿详情中),以及调用链的最终消费者。订阅位置来自 SDK 的 RxJava 2 钩子(见下文);如果未启用这些钩子,来源仍会显示操作符和运行该操作符的第一个应用代码栈帧。 - Coroutine:分发器(
Dispatchers.Main、Compose 的AndroidUiDispatcher)、恢复执行的 continuation(HomeViewModel$refresh$1,位于其当前代码行),以及应用启用 kotlinx.coroutines 调试模式时的协程名称和 ID(在调试版的Application.onCreate中设置System.setProperty("kotlinx.coroutines.debug", "on"),例如CoroutineName中的refresh-cart#2)。 - Handler:已投递的
Runnable(lambda in HomeFragment、LiveData.postValue)或处理消息的Handler;Input(输入事件、点击或长按);Frame(布局和绘制、动画器、Compose 的帧);Lifecycle(Activity、服务和接收器回调);Idle(空闲处理器)。
来源取自主线程调用栈最外层的帧(Looper 的分发过程)。即使调用栈超过保留的 64 帧,SDK 0.9.0 也会发送这些帧,因此只要旧版 SDK 的调用栈能追溯到 Looper,旧版 SDK 产生的卡顿也会显示来源。Main 线程泳道的工具提示会显示每个条形对应的来源。
RxJava 跟踪(默认开启,SDK 0.9.0,RxJava 2)。 Stalls 选项卡中的开关会在会话期间安装两种
RxJavaPlugins 钩子:一个调度处理器,用于标记主线程正在运行哪个 Rx 任务;以及订阅钩子,用于保留每个 observeOn 订阅的堆栈(按 VM 捕获时的样子保留,每秒最多 100 个;仅在发生卡顿时才转换为帧,并随调用链一起丢弃)。每个钩子都会调用应用此前安装的对应钩子;会话结束或关闭开关时会移除它,除非应用此后替换了该钩子(此时它会作为直通钩子留在应用的调用链中,且绝不会重复安装)。如果 RxJavaPlugins 已锁定、当前不在性能分析会话中(只有检查链接,没有控制面板),或应用不包含 RxJava 2,就不会安装任何钩子(RxJava 仍是 SDK 的仅编译依赖项)。在 API 36 模拟器上的测量结果:调度处理器的开销无法测量;每次 observeOn 订阅的订阅钩子开销为 1–3 µs(堆栈深度为 14–58),而像 RxJava 的装配跟踪那样在订阅时物化堆栈,开销为 13–14 µs。目前尚未挂钩 RxJava 3(其卡顿仍会从堆栈获取来源)。SDK 的 consumer ProGuard 规则会保留钩子读取的 RxJava 操作符和字段的名称,以支持经过缩减的调试构建。
StrictMode(选择启用,默认关闭)。 Stalls 选项卡中有一个开关可启用它(Android 9 / API 28+)。启用后,SDK 会在应用当前的主线程和进程 StrictMode 策略上增加磁盘读写、网络、自定义慢调用和未关闭资源检测,并通过堆栈报告每次违规。关闭开关或链接关闭时,SDK 会恢复应用的策略。对于带有 death、dialog、flash 或 dropbox 处罚的策略,或看起来设置了应用自身 penaltyListener 的策略,SDK 不会进行修改;也绝不会重新启用网络操作触发的 death 处罚。此时 Stalls 选项卡会提示未添加任何检测项。每个堆栈每秒最多合并报告一次违规,全局每秒最多报告 10 次。
布局:解析 XML 的开销
使用 SDK(SDK 和服务版本为 0.4.0+)时,Layouts 标签页会列出应用通过 AppCompatActivity 的 inflater 在主线程上解析的 XML 布局:解析次数、总耗时、平均耗时和最慢耗时、最慢一次解析创建的视图,以及运行所在的屏幕。列表按总耗时排序;点击列标题即可按该列排序。来自 Android 框架或库(abc_、mtrl_、design_ 等)的布局默认隐藏,开启 Show library layouts 后即可显示。SDK 无法命名的布局会以其首个视图和 XML 行号显示(Button:12,斜体)。在另一个布局内部解析的布局(<include> 或复合视图自身的布局)会单独占一行,并标记为 nested,因为这是它自身的开销;但其耗时已计入外层布局,因此不会增加额外耗时。
打开示例应用的各个屏幕后显示的 Layouts 标签页:fragment_home 是单次解析最慢的布局,其包含的卡片(view_dashboard_card,nested)占了大部分耗时,而 item_row 在 29 个 RecyclerView 列表项中的耗时累计起来也不少。
Screens 标签页新增了 Inflate 列(该屏幕上的布局解析耗时,每次解析只计一次,不计入嵌套布局;悬停提示中显示最慢的布局)和 Views 列(来自屏幕最新视图树的视图数量和深度;悬停提示中显示最深路径)。顶层布局解析耗时达到 16 ms 或以上时,也会在 Main thread 泳道基线下方的轨道上显示为金色条;悬停可查看布局、耗时、视图数量和屏幕,点击即可打开 Layouts 标签页。
测量方式:SDK 会在每个 Activity 创建前安装 LayoutInflater.Factory2,并从布局的第一个视图开始计时,直到最后一个视图创建完毕,然后将处理过程交回原有逻辑(不影响 AppCompat 的视图替换、MaterialButton 等)。无需 Gradle 插件,也无需更改应用。请将这些数据视为下限:不包括解析器初始化时间,而且可调试构建比发布版本慢。
不在测量范围内的情况:通过应用上下文或在 Service 中解析布局、在后台线程中解析、在跟踪关闭期间创建的 Activity 中解析、在已有 LayoutInflater 工厂的 Activity 中解析,以及不属于 AppCompatActivity 的屏幕(普通 Activity 和 ComponentActivity 屏幕;仍会采集其视图树)。布局测量需要 Android 10(API 29)及以上;低于此版本时,标签页会显示相应提示,且不会安装任何内容。默认开启布局解析跟踪,Stalls 标签页中 StrictMode 旁边有对应开关。关闭后会停止记录,之后打开的屏幕不会安装工厂(重新连接后也一样,直到某个会话再次开启此功能);关闭前已打开的屏幕会保留原有工厂。
如果应用设置了自己的工厂。 只有当 Activity 的 inflater 尚未设置工厂时,SDK 才会安装自己的工厂。如果应用(或库)随后在同一个 inflater 上调用 LayoutInflater.setFactory2,例如在 super.onCreate 之后于 onCreate 中调用,Android 就会抛出 A factory has already been set on this LayoutInflater。仪表板开关无法解决这个问题,因为第一个 Activity 创建时,SDK 还未收到服务端的通知。请在应用的调试清单中关闭布局解析测量(视图树仍可正常工作):
<application>
<meta-data android:name="io.github.risyadul.lionscope.inflateTracking" android:value="false" />
</application>
也请附上堆栈跟踪提交 issue。
视图树。 每次访问屏幕后,SDK 会在首帧绘制完成且主线程空闲时遍历窗口的视图层级(迭代遍历,上限为 20,000 个视图),并报告视图和视图组数量、深度、到最深视图的路径,以及出现频率最高的视图类。这适用于 SDK 支持的所有 API 级别,包括低于 Android 10 的版本。
布局检查器:选中一个视图,查看其来源
在设备工具栏中按 Inspect(模拟器或实体设备均可,无需启动分析会话)。屏幕会冻结为快照,并在 Layouts 旁打开 Inspector 选项卡。将指针悬停在某个视图上即可高亮显示(其 Class#id 和尺寸会显示在视图框上);点击即可选中。屏幕上会显示所选视图的内边距和外边距,选项卡中则会显示其属性:类、id、文本、边界和尺寸、可见性、透明度、布局宽度和高度、内边距、外边距、标志、所属的 Fragment 和 Activity,以及其所依据的 XML 布局文件和行号(fragment_cart.xml:33)。在 Compose 屏幕上,节点是 可组合项(Button、ProductCard),并会显示调用它们的 Kotlin 文件和行号(CartScreen.kt:87)。旁边的树状视图可以折叠,会跟随所选项,支持方向键(↑↓ 移动,→ 展开,← 折叠),并可搜索类、id、文本、源文件和目标;将指针悬停在某一行上时,屏幕上的对应视图会亮起。按 Esc 或点击切换按钮即可返回实时屏幕。启用 Inspect 时,应用的顶层屏幕发生变化约 0.7 s 后,快照会刷新(不会持续刷新);点击刷新按钮则会获取新快照。
- **树的来源。** 应用中集成了 SDK(Lionscope 0.5.0 或更高版本)时,SDK 会遍历应用自身的视图,徽标会显示 `SDK`。从 SDK 0.9.0 开始,这不再需要分析会话:当仪表板为某台设备显示 Inspector(Inspector 视图或标签页)时,以及你或 Agent 最近一次 Inspect 该设备后的 5 分钟内,服务会保留一条仅用于 Inspect、连接到该设备上可调试 SDK 应用的链路。通过这条链路,SDK 只会报告当前位于最上层的屏幕并响应 Inspect;在你按下 Start 之前,不会报告帧、watchdog、StrictMode 或布局信息。链路出现后的首次 Inspect 最多可能需要等待 6 s,让应用完成连接(如果未连接,徽标会显示 `SDK connecting`)。使用 SDK 0.5.0 或 0.5.1 的应用仍然只能在会话期间连接(徽标显示:`SDK needs a session`);如果另一台 Lionscope 服务已占用该设备的链路,徽标会显示 `SDK link busy`。在 Android 10 及更高版本中,这包括应用的每个窗口,也包括对话框;在 Android 10 以下版本中,则只包括最上层的 Activity 及其对话框,而且不提供布局来源。SDK 从 0.9.0 开始也能读取 Compose(见下文)。其他情况下(应用未集成 SDK、使用较旧版本的 SDK、SDK 无法读取的 Compose、其他应用和系统 UI),服务会退回到无障碍树,由其 UI 辅助工具读取(请参阅 Agent 工具说明中有关副作用的内容),或使用 `uiautomator dump`;徽标会显示 `uiautomator` 及原因。uiautomator 提供的节点较少,也不提供来源、所有者、内边距或外边距;这些字段会显示短横线。
- **Compose。** SDK(0.9.0 或更高版本,有无会话均可)会读取每个 `ComposeView` 的组合内容(`setContent {}` Activity、XML 布局中的 ComposeView、Compose 对话框或弹出窗口),方式与 Android Studio 的 Layout Inspector 相同:启动时就会开启 Compose 的工具数据,然后才开始组合。一个节点对应的是**由你的代码调用的**可组合项,因此 `Button` 是一个节点,但其中的 `Surface`、`Row` 和 `Layout` 不是(调用位置在 androidx Compose、Material 或 Navigation 中的调用会折叠到你调用的最近一个可组合项下;第三方库内部的调用则会显示)。其来源是**调用位置**:调用所在的文件和行,而不是函数定义的位置。其 id 是 `testTag`;文本、内容描述和标志来自其语义信息;惰性列表项位于所属列表之下,`AndroidView` 的 View 位于其可组合项之下,`ComposeView` 则位于其周围的 XML 视图之下。边界是其布局的内容框(位于内边距修饰符之内);可组合项没有 alpha、内边距、外边距或布局参数。使用 `Lionscope.trackNavigation`(见下文)时,所有者会显示绘制该可组合项的导航目标。在屏幕上点选时,会跳过那些本身不绘制内容(没有背景、边框、图像或文本,例如铺满整个屏幕的空闲 toast 或 snackbar host)且没有文本、内容描述、test tag 或点击行为的可组合项;它们仍会保留在树中。在同一位置再次点击会逐层选中下方的视图。适用于 Jetpack Compose 1.6 及更高版本,以及 Android 上的 Compose Multiplatform 1.8+;编译时需包含源代码信息(调试构建默认启用)。
- **Compose 何时会回退。** 在 SDK 启动前就已完成组合的 Compose 屏幕(手动延迟安装 Lionscope,或应用通过 `` 选择退出)无法读取:徽标会说明原因(重启应用即可)。未包含 Compose 源代码信息的构建也无法读取。工具数据会让组合速度稍慢,并占用稍多内存(与 Android Studio 的 Inspector 类似),因此 Compose 屏幕的调试帧耗时会略显悲观。采集在主线程上运行:在模拟器上读取包含 30 个项目的惰性列表约需 10 ms;在慢速手机上则需数倍时间;读取组合内容会在 200 ms 后停止(此时快照会标记为已截断)。
- **行号表示起始标签结束的位置。** aapt2 记录的是结束 `>` 所在的行,因此跨越第 28 到 33 行的 `Button` 会报告第 33 行。通过代码构建的视图、ViewStub,以及在主线程之外膨胀的视图都没有来源;在膨胀跟踪关闭期间创建的 Activity 的视图(或在 SDK 中选择退出跟踪的 Activity 视图)也没有来源——开启跟踪并重新打开屏幕即可。
- **隐私。** 屏幕截图和屏幕上的文本会保留在你的设备上,快照只保存在服务的内存中(仅保留最近几份)。绝不会读取密码字段:对于 Compose,具有密码语义(`PasswordVisualTransformation`、安全文本字段)或密码内容类型的文本字段,以及曾被识别为密码字段的字段,即使关闭“显示密码”也仍会脱敏。仅设置了 `KeyboardType.Password` 的 Compose 字段会在屏幕上显示文本,读取方式与其他字段相同。AI Agent 只能看到**你**选中的元素的文本和裁剪图像(绝不会看到整个窗口或屏幕的大部分区域);Agent 自行选择的元素只会向其提供结构信息(类、id、布局来源、所有者、边界)。Agent 选中的视图会在仪表板中标记为 **AI**。请参阅“与 AI Agent 配合使用”。
#### 实时编辑视图
如果应用使用 SDK 0.9.0 或更高版本,可以在应用运行时更改所选 View 的属性,以便先尝试修复,再修改布局文件:可见性、透明度、启用状态、内边距和外边距(盒模型示意图按每侧填写一个数值)、布局宽度和高度(match_parent、wrap_content 或像素)、位移、海拔和背景色;对于 TextView,还可修改文本、文字大小(sp)和文字颜色(#RRGGBB 或 #AARRGGBB)。编辑字段后按 Enter 或移开焦点(按 Esc 可还原);服务会通过 SDK 应用修改,并立即获取新快照,同时保持视图处于选中状态。
点击颜色字段的色块会打开拾色器:拖动方块以及色相和透明度滑块;点击屏幕上已有的颜色或最近使用的颜色;或者按 Pick,再用鼠标点击冻结屏幕上的任意像素(按 Esc 取消取色)。松开鼠标时才会发送颜色,而不是每次调整时都发送。
编辑需要分析会话(按 Start):应用只接受来自欢迎消息中包含链接证明的链接的编辑(参见安全模型),而仅供检查的链接不会包含证明。没有会话时,检查器仍会以只读方式显示所有值,并提示按 Start;如果无法将链接密钥交给应用,会话也会显示相应提示。
- 临时修改。 修改会一直生效,直到你还原它、应用自行设置该属性(例如,RecyclerView 行重新绑定或数据绑定),或视图被重新创建(旋转屏幕、新建 Activity 或 Fragment)。SDK 会保留每个属性的原始值:每个已编辑字段都有一个 Revert 按钮,标签页标题会显示“
N live edits (temporary)”,并提供 Revert all。如果应用之后自行设置了某个值,则不会对其进行修改,也不会将其“还原”。 - 无法编辑的内容。 Composables(在代码中修改:标签页会显示调用位置)、uiautomator 快照、使用低于 0.9.0 版本 SDK 的应用,以及密码字段的文本(绝不会读取、显示或设置)。外边距需要
MarginLayoutParams;窗口的根视图没有可编辑的布局尺寸;自动调整大小的 TextView 没有可编辑的文字大小。 - 背景色会替换背景。 在还原之前,视图会使用纯色背景,原有的形状、波纹或图片都会被替换,背景色调也会关闭(否则会给颜色叠加色调)。还原后,原始可绘制对象及其色调会恢复,并保留此时视图的内边距。编辑后的文本在还原前会失去样式;还原后会恢复带有 spans 的样式文本;如果样式由应用自己的 spans(例如可点击链接)设置,则会恢复为纯文本。
- 修改会运行应用自身的代码。 虽然只会调用平台 setter(
setPadding、setText等),但这些调用可能触发应用的监听器:TextWatcher会收到编辑后的文本,自定义视图的setPadding重载也会运行。值有范围限制(内边距为 0–2000 px,外边距为 ±2000 px,尺寸最大为 10000 px,文本最多 500 个字符且不含控制字符或双向文本覆盖字符,文字大小为 1–200 sp),并且每台设备的编辑频率受到限制。 - Agent 在拥有设备控制权限时,可在会话期间使用
set_view_property和revert_view_properties执行相同操作(inspect_screen和get_inspector_selection会说明节点接受哪些属性,或者说明为什么不接受任何属性)。 - 还原也需要会话。 会话期间所做的编辑会在会话停止后继续保留在应用中(它们现在是视图状态的一部分);要还原这些编辑,请重新启动会话,或重启应用。
网络:请求与模拟
使用 SDK 0.9.0 或更高版本,并在应用的 OkHttpClient 中添加一行代码后,Network 选项卡会在性能分析会话期间列出应用的 HTTP 请求:方法、主机和路径、状态、耗时和大小,并标记失败、待处理和模拟的请求;可按文本、方法和状态类别(2xx … 5xx、失败)筛选。选择一项即可查看其概览、请求和响应标头、请求和响应正文(JSON 会美化显示)以及耗时(等待标头,然后读取正文)。时间线上的 Network 轨道会用不同颜色的条形显示每个请求从开始到结束的过程,颜色代表状态;点击条形即可在选项卡中打开该请求。
// Retrofit (OkHttp 4.x or 5.x): add it last, after the app's own interceptors
val client = OkHttpClient.Builder()
.addInterceptor(authInterceptor)
.addInterceptor(Lionscope.okHttpInterceptor())
.build()
val retrofit = Retrofit.Builder().baseUrl("https://api.example.com/").client(client).build()
// Ktor on Android (OkHttp engine), in androidMain
val http = HttpClient(OkHttp) { engine { addInterceptor(Lionscope.okHttpInterceptor()) } }
发布构建使用 lionscope-noop,但保留同一行代码;其拦截器只会继续传递请求。Lionscope 不会引入 OkHttp;它使用应用自己的 OkHttp。将其放在最后添加后,捕获到的就是应用拦截器(身份验证、日志记录)发送的请求,模拟响应也会经过这些拦截器返回。如果改用 addNetworkInterceptor 添加,它只会记录请求,不会模拟请求。
捕获内容。 仅在性能分析会话运行期间捕获(不包括 Inspector 不需要会话的链接,也不包括没有仪表盘时;此时它只会执行普通的 chain.proceed),并且仅限 SDK 已验证为真实服务的链接(参见安全模型;如果无法验证,仪表盘会显示提示)。文本类型的正文(JSON、XML、text/*、表单、GraphQL)最多捕获前 64 KB;其他正文(图片、多部分数据、protobuf、下载内容)只记录大小和类型。服务器发送事件和 WebSocket 升级请求会显示,但不显示其数据流。对于应用自行设置 Accept-Encoding: gzip 的正文,显示时会先解压;Brotli 和其他编码会标记出来,但不会解码。Lionscope 绝不会读取响应正文:它会复制应用读取的内容,因此应用只需读取一次,请求会在应用读完后结束(并从列表中移除)。Lionscope 内部的错误不会导致应用调用失败或改变其结果。每秒最多记录 20 个请求;其余请求只计数。
隐私。 值会在设备上替换为 ‹redacted›,并由服务端再次替换:Authorization、Proxy-Authorization、Cookie 和 Set-Cookie,以及名称包含 token、secret、api-key / api_key / apikey、auth、session、password / passwd、credential、signature、jwt 或 appcheck,是 key、code、sig、pass 或 pwd,或以 -key、_key、Key 结尾的任何标头、查询参数或片段参数、表单字段、JSON 键(任意深度)以及 XML 元素或属性(sig、pass、pwd 等也遵循同样规则)。URL 中的用户名和密码(user:pass@),以及 Location、Content-Location、Referer 和 Link 标头中 URL 的查询部分,也会被替换;错误消息中的 URL 和这类用户名密码组合也会被替换。替换依据名称判断:如果机密值使用无害的名称("data": "<token>"),或出现在 GraphQL 查询文本中,就无法检测到。请求只保存在服务端内存中(保留最近 10 分钟,最多 5000 个请求或 32 MB),绝不会写入磁盘。AI Agent 可以获取请求列表、标头和耗时信息,但只有在你通过 --allow-network-bodies 选择允许后,才能获取正文(参见与 AI Agent 配合使用)。
模拟。 在请求上选择模拟此响应(或在模拟列表中选择新建模拟)会打开模拟编辑器:指定方法
(或任意方法)、URL 模式(精确、前缀或 glob,其中 * 匹配任意内容,包括 /;除非模式中包含 ?,否则查询参数会被忽略),以及响应内容:响应(状态码、标头、正文,最多 256 KB)或故障
(超时或连接错误,应用会将其视为 IOException),还可设置最长 30 s 的延迟。模拟的请求不会发送到网络。如果多条规则匹配,优先级依次为精确匹配、前缀匹配、glob 匹配;然后是模式更长的规则;再然后是指定了方法而非任意方法的规则;最后是最新规则。规则属于当前运行会话:会话停止时会清除。只要有模拟规则启用,所有视图的顶部栏都会显示已启用 N 条模拟(并显示应用是否已收到这些规则);每个模拟请求都会标记为已模拟,应用的 logcat 中会为每个模拟请求添加一条 Lionscope 日志。最多可设置 32 条规则。
Inspector 和 Device 视图中不会显示 Network 标签页,但请求仍会被记录,模拟指示器也会保留;点击该指示器即可返回模拟列表。要试用此功能,示例应用的更多 → 打开网络演示会调用一个小型本地 API:node android-sdk/sample/sample-api.mjs,模拟器通过 http://10.0.2.2:8745 访问该 API。
在仪表板中操作模拟器
设备选择器会列出所有 AVD,包括已关机的设备。启动会使用主机 GPU 在无窗口模式下启动设备(-no-window -gpu host),并可选择快速启动(加载 Quick Boot 快照,并在关机时保存快照)或冷启动(-no-snapshot-load)。随后,设备屏幕会显示在 Device 列中,你可以像使用模拟器窗口一样操作:点击并拖动以触摸,按住 Alt 并拖动以缩放(两个手指围绕中心镜像移动),滚动鼠标滚轮,屏幕获得焦点时输入文字(Esc 键为返回),并使用工具栏上的按钮执行返回、主页、最近任务、电源、旋转、全分辨率截图,以及设置帧尺寸(短边为 360、540 或 720 px)。无论是否有性能分析会话,都可以使用镜像;只有在该列打开且标签页可见时才会传输画面。
共享剪贴板(工具栏中,默认开启)会保持两端剪贴板同步:你在电脑上复制的文本,会在点击屏幕或使屏幕获得焦点时写入 Android 剪贴板,因此可以通过 Android 自带的长按粘贴来粘贴;在 Android 中复制的文本则会出现在电脑剪贴板中(显示“已从模拟器复制”)。在屏幕上按 Cmd/Ctrl+V 可直接粘贴到当前聚焦的字段。支持最多 32 KB 的任意 Unicode 文本。读取剪贴板需要浏览器的剪贴板权限(Chrome 会询问一次);基于 Firefox 的浏览器(Firefox、Zen)和 Safari 不提供此权限,因此请在这些浏览器中按 Cmd/Ctrl+V,将剪贴板内容发送到屏幕(屏幕会显示相应提示);从 Android 复制内容则在所有浏览器中都可用。关闭此开关后,不会读取或写入任一剪贴板(Cmd/Ctrl+V 仍可作为显式操作进行粘贴)。
关闭模拟器(位于 Device 列和设备选择器旁)会关闭任何正在运行的模拟器,但会先停止其上的性能分析会话。如果模拟器不是由 Lionscope 启动的(例如由 Android Studio 启动),或当前有会话正在运行,Lionscope 会先询问。Lionscope 启动的模拟器也会在服务器停止时关闭;你自行启动的模拟器则不会关闭。
Lionscope 通过模拟器自身的 gRPC 端点与其通信,并从模拟器的发现文件
(avd/running/pid_<pid>.ini,其中包含端口和访问令牌)中查找该端点。画面由主机端的模拟器捕获,并且仅在屏幕发生变化时以 PNG 格式发送,因此模拟器内的 Android 不会增加额外负载,也不会影响应用的测量结果。在 M 系列 Mac 上,以 540 px 显示时,帧率约为 58 fps,每帧约 140 KB,从输入到显示画面约需 55 ms。
如果模拟器使用软件渲染(SwiftShader,例如使用 -no-window 但不加 -gpu host),仪表板会发出警告:此时的帧耗时无法代表真实设备。
在仪表板中操作实体设备
选择一部通过 USB 连接的手机,其屏幕就会显示在“设备”列中,且提供与模拟器相同的控件(触控、多点触控、滚轮、按键、共享剪贴板、返回 / 主页 / 最近任务 / 电源、应用支持时可旋转、截图和帧大小)。手机上不会安装任何内容:Lionscope 会将捆绑的 scrcpy 服务端(v5.0.1,Apache-2.0,已根据其公布的 SHA-256 校验)推送到 /data/local/tmp,在“设备”列打开时通过 adb 运行,并在停止镜像时自行移除。镜像期间手机屏幕会保持亮起(scrcpy 的 stay_awake);之后会恢复之前的设置。手机的硬件编码器会发送 H.264 视频,由浏览器通过 WebCodecs 解码(Chrome、Firefox 130+、Safari 16.4+;必须在 localhost 上打开仪表板)。
镜像期间,手机的硬件编码器会增加少量负载,因此关闭“设备”列可获得最准确的测量结果。
在 Xiaomi / HyperOS(以及某些其他 OEM 版本)上,除非在开发者选项中开启USB 调试(安全设置),否则手机会拒绝注入的输入;屏幕仍可镜像,仪表板也会提示你需要开启该设置。
更改设备的位置
位置(“设备”列工具栏中的图钉)可以将设备放到其他位置,类似于 Android Studio 模拟器的“位置”控件:点击地图(或拖动图钉)、输入 -6.1754, 106.8272,或者粘贴包含坐标的 Google Maps 或 OpenStreetMap 链接,然后点击设置位置。常用地点可以保存(保存在此浏览器中)。Lionscope 移动设备位置时,图钉会变成紫色;点击重置即可撤销。
- 模拟器会将该点作为 GPS 定位(通过模拟器的 gRPC 端点;没有端点时则使用
adb emu geo fix):应用会看到普通的位置。重置会恢复模拟器之前的定位。 - **实体设备(Android 12 及更高版本)**通过 Android 的测试定位提供程序接收位置:Lionscope 允许 adb shell 模拟位置(
appops set com.android.shell android:mock_location allow),并将gps、network和fused提供程序替换为测试提供程序,每秒向其发送该位置(cmd location providers …)。Google Play 服务的融合定位也会跟随这些提供程序。应用看到的Location.isMock()为 true,因此拒绝模拟位置的应用不会采用该位置。手机上不会安装任何内容。 - 点击重置、服务端停止时,以及服务端被终止或手机先断开连接(或短暂离线)时,手机会恢复真实位置;若手机先断开连接(或短暂离线),则会在 Lionscope 服务端下次检测到手机时恢复:
/data/local/tmp中的标记文件会记录设置位置的服务端以及要恢复的内容。其他正在运行的 Lionscope 服务端移动的手机不会受影响(会拒绝设置),但点击重置会恢复任何被 Lionscope 移动过的手机。重置会移除所有gps、network和fused测试提供程序,包括模拟位置应用安装的提供程序。 - 如果设备的位置开关已关闭,面板会显示提示:此时应用将看不到任何位置信息。
地图图块来自 OpenStreetMap(tile.openstreetmap.org),仅在面板打开时加载;这是仪表板唯一从互联网加载的内容。输入或粘贴的坐标不会离开你的设备,但你查看的地图图块除外。
要求
- Node.js ≥ 22.12
- Android SDK platform-tools(
adb)。Lionscope 会依次在LIONSCOPE_ADB、$ANDROID_HOME、$ANDROID_SDK_ROOT、~/Library/Android/sdk和PATH中查找。 - 已启用 USB 调试、运行 Android 7.0(API 24)或更高版本的设备或模拟器(Lionscope 依赖
pidof、toyboxdate +%s%3N和非 PTY 的adb shell)。
快速开始
npx lionscope@latest
# open http://localhost:8701
@latest 会让 npx 检查是否有新版本;直接使用 npx lionscope 可能会继续运行先前缓存的版本。
也可以直接从源码运行(npm 包可能落后于最新的 git 标签):
git clone https://github.com/risyadul/Lionscope.git && cd Lionscope && npm install && npm run lionscope。
选择设备,选择应用,然后按下 Start。在仅使用 ADB 的模式下,无需在设备上安装任何内容。npx lionscope --help 会列出选项(--port、--device-port、--adb、--allow-device-control、--allow-network-bodies),以及供 AI Agent 使用的 mcp 和 query 命令;lionscope 包由一个打包文件和构建好的仪表板组成,没有运行时依赖。
添加 SDK(可选,仅限调试构建)
SDK 发布在 JitPack 上。这是一个公共制品:无需凭据或选择启用属性。
// settings.gradle (dependencyResolutionManagement) or app/build.gradle repositories
maven { url "https://jitpack.io"; content { includeGroup "com.github.risyadul.Lionscope" } }
// app/build.gradle
debugImplementation "com.github.risyadul.Lionscope:lionscope:0.9.0"
如果项目使用 dependencyResolutionManagement,则应将 maven 块放在 settings.gradle 中;否则放在模块的 repositories 中。content { includeGroup … } 过滤器可确保 JitPack 只提供 Lionscope。
无需修改代码:SDK 会通过 androidx.startup 自动启动,且只会在可调试构建和应用的主进程中执行操作。会话启动时,服务会为所选设备运行 adb reverse tcp:8700 tcp:<gateway port>,并在会话停止时移除该映射。如果应用移除了 androidx.startup 的 InitializationProvider,请从 Application.onCreate 调用 Lionscope.install(context)。
屏幕名称。 默认情况下,屏幕名称取自其类名(CheckoutActivity、HomeFragment)。如果 Activity 或 Fragment 有一个公开的无参 getScreen(),且它返回的对象有一个非空白的 String value(getScreen()?.value,例如实现 ScreenName 接口的枚举),则会使用该别名。示例应用的 ScreenName.kt 展示了此约定;SDK 通过反射读取它,因此无需共享接口。
Navigation Compose。 对 SDK 来说,单 Activity Compose 应用只有一个 Activity。告知 SDK 你的 NavController,每个目标位置就会成为一个屏幕(Screens 轨道、每个屏幕的卡顿情况、停顿情况,以及 Inspector 中的屏幕和所有者):
val navController = rememberNavController()
Lionscope.trackNavigation(navController) // once per controller; repeated calls are free
NavHost(navController, startDestination = "catalog") { /* ... */ }
屏幕名称是目标位置的路由模式,绝不会包含参数:product/{id}(查询占位符会从名称中移除);对于类型安全路由,则使用其类名(ProductDetail);dialog() 目标位置属于对话框。当应用顶部没有 Fragment 时,此设置生效;对于嵌套的 NavHost,使用最内层显示的 NavHost(请分别跟踪)。lionscope-noop 使用相同的调用,因此在发布构建中也能编译通过。在 Kotlin Multiplatform(使用 org.jetbrains.androidx.navigation:navigation-compose 的 Compose Multiplatform)中,SDK 是 Android 依赖项,因此请从 androidMain 调用,例如通过 expect/actual 函数:
// commonMain
@Composable expect fun TrackNavigation(navController: NavHostController)
// androidMain
@Composable actual fun TrackNavigation(navController: NavHostController) = Lionscope.trackNavigation(navController)
// iosMain (and other targets)
@Composable actual fun TrackNavigation(navController: NavHostController) = Unit
目前尚未跟踪 Voyager 和 Navigation 3。
SDK 详情:minSdk 23(帧指标需要 API 24)、Java 8 字节码、Kotlin 元数据 2.0;除 androidx.startup 外没有运行时依赖项(androidx.fragment、Compose UI、Navigation 和 Kotlin stdlib 均由宿主应用提供,且仅在应用包含它们时使用)。
演示应用:android-sdk/sample。
网络(可选)。 SDK 中唯一需要修改代码的功能:将 Lionscope.okHttpInterceptor() 添加到应用的 OkHttpClient(Retrofit 或 Ktor 的 OkHttp 引擎),即可使用网络选项卡和模拟。OkHttp 4.x 或 5.x 由应用提供。
避免进入发布构建
debugImplementation 意味着发布构建中不包含 Lionscope 的任何内容:没有类、没有
InitializationProvider 条目,也不会添加 SDK 引入的 INTERNET 权限。作为第二道保障,除非应用可调试(FLAG_DEBUGGABLE),否则 SDK 不会执行任何操作。
显式调用 API 的应用(Lionscope.install()、Lionscope.isActive()、Lionscope.trackNavigation()、
Lionscope.okHttpInterceptor())也需要它在发布构建中可编译;对于这些应用,有一个 API 相同的空操作构件(其拦截器只会放行请求):
releaseImplementation "com.github.risyadul.Lionscope:lionscope-noop:0.9.0"
未连接仪表板时的开销
- 生命周期回调只记录当前的顶层屏幕;没有连接时不会发送任何内容。
- 对于使用 Compose UI 的应用,Compose 的工具数据从一开始就会启用(这样 Inspector 之后才能读取这些数据):组合速度会略慢一些,并占用稍多内存,效果类似 Android Studio 的 Inspector。使用
io.github.risyadulul.lionscope.composeInspection元数据可选择退出(此时 Inspector 会改用 uiautomator)。 - 只有在仪表板接受了应用后(该软件包有活跃会话),才会注册
FrameMetrics监听器,因此其他时候不会逐帧执行任何工作。 - 应用在前台时,SDK 会尝试连接
127.0.0.1:8700,间隔依次为 1 → 2 → 5 s(无人监听时,本地连接会被拒绝);前台运行 10 分钟仍未连接仪表板后,间隔会延长至每 15 s 一次;应用转入后台 30 s 后会停止尝试,直到应用重新回到前台。 - 只有在仪表板通过协议 v2 接受了应用且有 Activity 已启动时,主线程看门狗线程才会存在;StrictMode 在启用前处于关闭状态,其报告线程在首次启用后会保持空闲(不执行任何工作)。
- 在性能分析会话之外,
Lionscope.okHttpInterceptor()只执行一次 volatile 读取和chain.proceed。 - RxJava 钩子只在性能分析会话期间安装(参见 RxJava 跟踪);从未开启过会话的进程不会安装这些钩子。不会使用 Looper 的消息日志(
Printer会让每条主线程消息增加约 3 µs 的开销,让每次协程分发增加约 17 µs 的开销)。 - 除了在应用繁忙时不能被饿死的帧指标和看门狗线程外,其他线程都以后台优先级运行;有界的出站队列会丢弃最旧的消息,而不是不断增长;SDK 错误会被控制住,不会传递给应用的崩溃处理程序。
与 AI Agent 搭配使用
AI 编码 Agent(Claude Code、Cursor、VS Code、Codex、Gemini CLI、Windsurf、Claude Desktop 等)可以读取 Lionscope 记录的数据并运行性能分析会话:“在模拟器上分析购物应用,然后告诉我哪个屏幕卡顿最严重,以及哪一行代码阻塞了主线程”。它们获取的数字与仪表板显示的相同(Screens、Stalls、Layouts 和 Logcat 的数据均来自同一个共享模块),仪表板也会在它们旁边保持打开状态。
三种接入方式,一套工具:
- 通过 stdio 使用 MCP:
npx -y lionscope mcp。它会使用已在运行的 Lionscope 服务(由--port指定,默认值为 8701);如果没有服务在运行,则会在自己的进程中启动一个:此时仪表板位于 http://localhost:8701,直到 Agent 断开连接。如果它使用的服务停止了,下次调用工具时会启动一个服务 (或者连接到其他客户端已启动的服务)。日志会输出到 stderr。 - 通过 HTTP 使用 MCP(Streamable HTTP):对于非网页客户端,可通过运行中的服务(
npx lionscope)访问http://localhost:8701/mcp:如果请求的Origin与仪表板的不同,请求就会被拒绝。桌面端宿主程序会从 Node 进程建立连接,且不会发送Origin(VS Code 和 Cursor 的 MCP 客户端运行在 Node 扩展宿主中);基于浏览器的客户端(VS Code for the Web、MCP 检查器页面)无法使用此方式,因此应为它们提供 stdio 命令。 - Shell:
npx lionscope query <tool>会打印工具的 JSON 结果,适用于只能运行命令的 Agent。 | 工具 | 功能说明 | | -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 读取(始终启用) |status(设备、模拟器、会话、SDK 链接)、list_apps、get_location(Lionscope 在设备上设置的位置、模拟器的 GPS 定位)、get_summary(最近 1/5/10 分钟的 CPU、内存、FPS、卡顿、停顿、热门屏幕和布局)、get_screens、get_stalls(按问题行和来源分类:Rx 链的订阅位置、协程、已投递的 Runnable)、get_strictmode、get_layouts、get_view_tree、search_logcat| | 会话(始终启用) |start_session、stop_session、set_sdk_options(停顿阈值、StrictMode、布局膨胀跟踪、RxJava 跟踪) | | 检查器(始终启用) |get_inspector_selection(你在仪表板的 Layout Inspector 中选中的元素:其属性、视图的 XML 布局及行号或组合项的 Kotlin 调用位置、所属的 Fragment、Activity 和目标、祖先和子项,以及截图裁剪图;仅针对 Agent 选中的节点提供结构信息)、select_inspector_node(在仪表板中突出显示一个节点,并标记为 AI) | | 网络(始终启用) |get_network(最近 1/5/10 分钟内应用的 HTTP 请求:方法、URL、状态、耗时、大小、是否模拟;可按方法、状态类别、主机、文本筛选)、get_network_request(单个请求的标头和耗时;只有使用--allow-network-bodies时才包含请求正文) | | 设备(可选启用,见下文) |get_ui_tree(屏幕上的元素:文本、描述、ID、边界、标志)、inspect_screen(Layout Inspector 快照,也会显示在仪表板中)、set_view_property/revert_view_properties(实时临时更改快照中的 View:可见性、透明度、内边距、外边距、大小、平移、仰角、颜色、TextView 文本和大小;SDK 0.9.0+,在会话期间使用)、tap_element、wait_for、scroll_to(按文本、ID 或描述查找元素)、screenshot(短边为 720 px)、tap、swipe、type_text、press_key(导航和编辑按键;绝不会按电源、睡眠或锁定键)、launch_app、boot_emulator、shutdown_emulator、set_network_mock、clear_network_mocks(模拟响应或故障,见网络请求和模拟)、set_location、clear_location(移动设备位置,见更改设备位置) | “为什么这个按钮没有对齐?” 在仪表板的布局检查器中选中该元素(点击 Inspect,然后点击元素),再询问“这个元素”:Agent 会调用get_inspector_selection,获取检查器选项卡中显示的信息(类、id、文本、边界、内边距、外边距、布局宽度和高度、view_dashboard_actions.xml第 15 行、所属 Fragment),以及一张元素的裁剪截图,最多 512 px(如果是窗口根节点,或元素覆盖了屏幕一半以上,则不提供截图)。该行是开始标签结束的位置,工具也会说明这一点。启用设备控制后,inspect_screen会自行截取快照(并通过查询查找节点;它会替换你看到的快照),而select_inspector_node会将你定位到它指的节点,并标记为 AI。对于 Agent 自行选中的节点,get_inspector_selection只会返回其结构(类、id、布局源、所属对象、边界),不会返回文本或图像:没有设备控制权限的 Agent 无法通过这种方式读取屏幕。若应用中没有 SDK,快照将由 uiautomator 提供,不含布局源或所属对象。启用设备控制、使用 SDK 0.9.0+ 并启动性能分析会话后,Agent 还可以尝试在运行中的应用里应用修复:set_view_property { nodeId, property: "padding", value: [16, 8, 16, 8] }会返回下一张快照中该元素的显示效果,而revert_view_properties会撤销修改(无论哪种情况,修改都是临时的;请参阅实时编辑视图)。
“为什么结账失败?” 在应用中启用拦截器后,get_network 会列出相关请求(status: "5xx" 或 "failed"),而 get_network_request 会提供单个请求的标头、时间信息和错误。看起来像凭据的标头值已自动脱敏。除非服务或 MCP 客户端使用 --allow-network-bodies 运行(例如 npx -y lionscope mcp --allow-network-bodies,或 lionscope query network-request --id 12 --allow-network-bodies),否则工具结果中不会包含请求正文;启用该选项后,请求正文会像其他应用字符串一样标记为不可信数据。启用设备控制后,set_network_mock 可以为某个 URL 返回预设响应、错误状态或超时(在仪表板中显示为 AI 规则),而 clear_network_mocks 会将其移除:请让 Agent 在完成后清除模拟规则,或停止会话。
设置
每个执行环境都会自行启动 npx -y lionscope mcp;在参数中添加 --allow-device-control,即可让它
操作设备。HTTP 变体会连接到你通过 npx lionscope 启动的服务。
格式依据各工具在 2026 年 10 月发布的文档。
Claude Code(文档),或安装插件,即可同时添加 服务和技能:
claude mcp add lionscope -- npx -y lionscope mcp
# or, against a running server:
claude mcp add --transport http lionscope http://localhost:8701/mcp
添加 --scope project,通过项目的 .mcp.json 共享;或添加 --scope user,以供所有项目使用。
Cursor(文档):在项目中使用 .cursor/mcp.json,或使用
~/.cursor/mcp.json 以供所有项目使用。
{
"mcpServers": {
"lionscope": { "type": "stdio", "command": "npx", "args": ["-y", "lionscope", "mcp"] }
}
}
HTTP:"lionscope": { "url": "http://localhost:8701/mcp" }。
VS Code(GitHub Copilot Agent 模式,文档):
在工作区中使用 .vscode/mcp.json;注意顶层键为 servers。
{
"servers": {
"lionscope": { "type": "stdio", "command": "npx", "args": ["-y", "lionscope", "mcp"] }
}
}
HTTP:"lionscope": { "type": "http", "url": "http://localhost:8701/mcp" }。也可以在终端中运行:
code --add-mcp '{"name":"lionscope","type":"stdio","command":"npx","args":["-y","lionscope","mcp"]}'。
Codex(文档):使用 ~/.codex/config.toml,或运行
codex mcp add lionscope -- npx -y lionscope mcp。
[mcp_servers.lionscope]
command = "npx"
args = ["-y", "lionscope", "mcp"]
HTTP:用 url = "http://localhost:8701/mcp" 替代 command 和 args。
Gemini CLI(文档):在项目中使用 .gemini/settings.json,
或使用 ~/.gemini/settings.json。
{
"mcpServers": {
"lionscope": { "command": "npx", "args": ["-y", "lionscope", "mcp"] }
}
}
HTTP:按文档说明使用 "lionscope": { "httpUrl": "http://localhost:8701/mcp" }(较新版本也接受
"url": "http://localhost:8701/mcp", "type": "http",这是 gemini mcp add --transport http 写入的内容)。若要
从终端添加:gemini mcp add -s user lionscope npx -- -y lionscope mcp(-- 可确保 -y 传递给 npx)。
Windsurf(于 2026 年 6 月更名为 Devin Desktop,文档):
macOS 和 Linux 上使用 ~/.config/devin/mcp_config.json,Windows 上使用 %APPDATA%\devin\mcp_config.json(项目
也可以使用 .devin/mcp_config.json),并添加与上述 Gemini CLI 相同的 mcpServers 条目。HTTP:
Cascade Agent 使用 "lionscope": { "serverUrl": "http://localhost:8701/mcp" },
Devin Local Agent 使用 "lionscope": { "url": "http://localhost:8701/mcp" }。
Claude Desktop(文档):依次选择设置 →
开发者 → 编辑配置,打开 claude_desktop_config.json(macOS 上的路径为 ~/Library/Application Support/Claude/,
Windows 上为 %APPDATA%\Claude\);添加与上述 Gemini CLI 相同的 mcpServers 条目,然后重启 Claude Desktop。
使用 stdio 条目:Claude Desktop 的自定义连接器会从 Anthropic 一侧连接远程服务,因此无法连接到
localhost。
任何其他 MCP 客户端:将 npx -y lionscope mcp 作为 stdio 服务运行,或连接到
http://localhost:8701/mcp(Streamable HTTP,无身份验证,仅限环回地址)。
Agent 技能
MCP 工具会说明每次调用的作用;Lionscope 技能(skills/lionscope/SKILL.md)则会告诉 Agent 如何针对具体任务使用这些工具:从哪里开始、如何处理卡顿的屏幕、如何按行和来源排查主线程阻塞、如何处理用户在 Inspector 中选中的元素(并尝试使用实时编辑修复)、如何检查应用的请求和模拟的错误状态,以及如何在修复前后进行检查。无论是否使用 MCP 服务,它都能正常工作:没有 MCP 服务时,它会从 shell 使用 npx lionscope query。它是一个 Agent Skills 文件夹,因此任何能够读取 SKILL.md 的工具环境都可以使用;不同之处只在于它的存放位置。
Claude Code:插件(一次安装 MCP 服务和技能)。选择以下两个插件之一:
/plugin marketplace add risyadul/Lionscope
/plugin install lionscope@lionscope # read tools and sessions
/plugin install lionscope-device-control@lionscope # also the device tools (taps, edits, mocks, emulators)
在终端中运行 claude plugin install lionscope --marketplace risyadul/Lionscope 即可一次完成这两个步骤。插件会自行运行 npx -y lionscope@<version> mcp:请移除你手动添加的 lionscope MCP 服务,否则 Agent 会看到重复的工具。可通过 /plugin marketplace update lionscope 获取新版本(也可以在 /plugin 中开启 Marketplace 自动更新)。
适用于任何工具环境,使用 skills CLI(vercel-labs/skills,它知道各个 Agent 将技能存放在哪里):运行 npx skills add risyadul/Lionscope(添加 -a codex、-a opencode 等以选择 Agent;添加 -g 可安装到你的用户目录,而非项目目录)。
手动安装:将 skills/lionscope 文件夹复制到以下位置(也可以将其中的 SKILL.md 下载到名为 lionscope 的文件夹中):
| 工具环境 | 项目 | 你的用户 |
|---|---|---|
| Claude Code | .claude/skills/lionscope/ | ~/.claude/skills/lionscope/ |
| Codex | .agents/skills/lionscope/ | ~/.agents/skills/lionscope/ |
| OpenCode | .opencode/skills/lionscope/(也会读取 .agents/skills/ 和 .claude/skills/) | ~/.config/opencode/skills/lionscope/ |
| Hermes Agent | ~/.hermes/skills/lionscope/,或运行 hermes skills install https://raw.githubusercontent.com/risyadul/Lionscope/main/skills/lionscope/SKILL.md | |
| Cursor、Gemini CLI、GitHub Copilot、… | 各自的技能文件夹(参见对应文档) |
复制的技能版本会保持不变:Lionscope 更新后,请重新复制。通过 skills CLI 安装的版本可使用 npx skills update 更新。该技能固定使用编写时对应的 npm 版本(npx -y lionscope@<version> query),因此其中的 shell 命令与所介绍的工具相匹配。
让 Agent 操作设备
设备工具默认关闭:Agent 可以读取数据、启动或停止会话,但不能操作屏幕。
使用 --allow-device-control 开启,可仅为一个 MCP 客户端启用(npx -y lionscope mcp --allow-device-control,例如 claude mcp add lionscope -- npx -y lionscope mcp --allow-device-control),也可为
整个服务启用(npx lionscope --allow-device-control,同时也会在 /mcp 上启用)。这些工具通过
adb(screencap、uiautomator、input、am start -W)在模拟器和实体设备上运行,因此能执行你手持设备时
能做的任何操作:连接了含有个人数据的手机时,请勿启用。对于并非由此服务启动的模拟器,只有在设置
confirm: true 时才会关闭。
两种定位方式:
- 按元素定位(精确):
get_ui_tree会读取屏幕的无障碍树(见下文),而tap_element { text: "Checkout" }、wait_for { text: "Order placed" }和scroll_to { id: "row_title", text: "Row 40" }可按可见文本或内容描述、资源 ID(login_button)或 描述查找元素,并点击元素的正中心。如果选择器匹配多个元素,则不会点击任何元素,而是返回候选项;使用nth选择其中一个。type_text { text, field: { id: "email" } }会先点击输入框。 密码输入框不会返回其文本。 - 按坐标定位:先调用
screenshot,再依据截图中的像素使用tap/swipe。当界面树 无法识别元素时可采用这种方式:Jetpack Compose 元素只有使用Modifier.testTag和testTagsAsResourceId = true才会获得资源 ID(否则请使用其文本或内容描述);WebView、画布、游戏 或视频内容也可能不会出现在界面树中。
界面树由 Lionscope 的 UI 助手提供(适用于 Android 7.0 及更高版本):服务通过 adb 在设备上运行一个小程序,
该程序会保持一个无障碍(UiAutomation)连接打开,在几百毫秒内读取屏幕,且不会等待动画停止。连接期间
(直到上次读取后的 20 s),应用会检测到无障碍功能已启用,这可能会影响其行为或性能;其他 UI 自动化工具
(Appium、uiautomator 测试)也无法连接。无法使用该助手时,会回退到 uiautomator dump,该操作需要一两秒,
并且要求屏幕处于静止状态;如果屏幕持续动画,则会在重试后失败,并提示使用截图方式。坐标遵循设备当前的
旋转方向(依据 wm size 和显示屏旋转角度,而非对话框窗口):如果屏幕旋转后仍使用旋转前截取的截图,系统会拒绝操作,
以免点击错误位置,请重新截图。type_text 支持可打印的 ASCII 字符,且无法输入 %s(这是 adb 用于表示空格的
转义序列)。launch_app 会通过 am start -W 启动启动器 Activity,并返回 Android 的启动时间。每台设备
最多同时运行两个设备操作;更多操作会收到设备忙的响应。
从 Shell 调用
npx lionscope query --list # every tool, its description and input schema
npx lionscope query status
npx lionscope query summary --window 5m
npx lionscope query stalls --include-stacks
npx lionscope query logcat --level E --limit 50
npx lionscope query start-session --serial emulator-5554 --package-name com.example.shop
npx lionscope query screenshot --allow-device-control --out screen.png
npx lionscope query inspector-selection --out element.png # what you selected in the Inspector
npx lionscope query network --status 5xx --window 10m
npx lionscope query network-request --id 12 --allow-network-bodies
参数使用 --name value 标志(可用 kebab 或 camel 大小写),或 --json '{"window":"5m"}';短名称可
省略 get_ / search_ 前缀。结果以 JSON 格式输出到 stdout;失败时退出码为 1,stdout 中会包含
{"ok": false, "error": "…"}。query 需要服务器正在运行(使用 --port 选择端口)。
安全说明
- 所有内容都留在这台机器上:端点监听 127.0.0.1,并拒绝网页可能发出的请求(参见安全模型)。
- 日志行、标签、屏幕、布局和类名以及堆栈帧都来自正在分析的应用,Inspector 结果中的视图文本、内容描述、资源 ID 和图像裁剪也来自该应用。工具结果会将它们标记为不可信数据,Agent 应据此处理:日志行或标签绝不是指令。
- 屏幕上的文本只有在以下情况下才会通过 Inspector 传到 Agent:通过
get_inspector_selection获取你选中的单个元素(附带该元素的裁剪图,绝不会是整个窗口或屏幕的大部分),以及通过inspect_screen获取(设备控制;不含图像)。Agent 自行选择的元素(select_inspector_node,在仪表板中标记为 AI)只会提供结构信息:类、ID、布局来源、所属方和边界。绝不会读取密码字段,浏览器也绝不会缓存屏幕截图。 - HTTP 请求的 URL、标头和正文都来自应用及其服务器,也会被标记为不可信数据。凭据样式的值会在离开设备前从标头、URL、表单、JSON 和 XML 中删除;只有使用
--allow-network-bodies时,正文才会传到 Agent。 --allow-device-control可防止 AI 在未经请求的情况下操作设备;它不是安全边界,因为任何本地进程都已能够使用 adb。--allow-network-bodies也是如此(任何本地进程都能读取仪表板套接字);它会将请求和响应正文排除在 AI 的上下文之外,除非你希望包含这些内容。
架构
device laptop (everything listens on 127.0.0.1)
app (debug) + :lionscope SDK ── TCP :8700 ── adb reverse ──▶ server/ SDK gateway (NDJSON)
adbd ◀───────────── adb shell: /proc, dumpsys, logcat ─────── server/ collectors
│
▼
session store (10 min)
│ HTTP + WebSocket :8701
▼
dashboard/ in the browser
emulator ◀─────────── gRPC (EmulatorController, token) ─────── server/ emulator manager + /ws/screen
phone ◀── adb forward ── scrcpy-server (H.264 + control) ───── server/ scrcpy session + /ws/screen
| 目录 | 说明 |
|---|---|
protocol/ | 服务端和仪表板共用的线路协议:类型(wire.ts)、zod 验证(index.ts)、SDK 黄金样例行(fixtures/),以及 insights/:仪表板的各项指标(屏幕、卡顿、布局、logcat 过滤器),以纯函数形式提供,供 Agent 工具复用 |
server/ | Node.js + TypeScript:adb 采集器和解析器、会话存储(10 分钟环形缓冲区)、WebSocket 网关、SDK 网关,以及 agent/:位于 /mcp、/api/agent 和 lionscope mcp / query 后的 AI Agent 工具 |
dashboard/ | Vite + React + uPlot 单页应用,由服务端提供 |
android-sdk/ | Gradle 构建::lionscope(调试 SDK)、:lionscope-noop(发布版空操作)、:sample(演示应用) |
cli/ | lionscope npm 包:使用 esbuild 将服务端打包到 dist/lionscope.mjs,并包含构建后的仪表板 |
SDK 使用普通 TCP 与服务端通信,每行传输一个 JSON 对象,而不是使用 WebSocket:设置了
usesCleartextTraffic=false(或未允许 localhost 的网络安全配置)的应用会阻止 ws://localhost,
而普通套接字不受此策略限制。Kotlin 编码器和服务端验证器均使用同一个黄金样例文件
protocol/fixtures/device-messages.ndjson 进行测试。
SDK 的 hello 消息携带其协议版本(自 0.2.0 起为 2:包含 stall、StrictMode 和 config 消息);服务端以 welcome 回复,并采用该版本与自身版本中的较低者
(协商版本,接受范围为 1 到 1000)。对于服务端不认识的消息类型,其对应行会被跳过并计数,而不会关闭连接;已知类型仍会经过严格验证。从 0.1.1 起,
不同版本的 SDK 和服务端因此可以继续协同工作:0.1.1 服务端或 SDK 会协商协议版本 1,卡顿和 StrictMode 功能则不可用。服务端会丢弃无效的卡顿或
StrictMode 消息,而不会关闭连接。0.1.0 服务端只接受
协议版本 1,并会在遇到未知消息类型时关闭连接。
网络检查器要求设备两端(SDK 和 0.9.0 或更高版本的服务端)均使用设备协议 7:使用较旧的服务端时,拦截器会保持直通状态;较旧的 SDK 则不会发送任何请求。HTTP 请求和响应行的长度 最多可达 256 KB(其他 SDK 行仍限制为 64 KB),服务端的模拟规则会以扁平行的形式发送给 SDK,每行 最多 512 KB。
卡顿来源使用设备协议 9(SDK 和服务端 0.9.0):卡顿消息可携带堆栈中最外层的帧、RxJava 钩子的发现结果和协程名称,config 则携带 RxJava 开关。使用较旧的服务端时,SDK 仍会像之前一样发送卡顿消息,并保持 RxJava 钩子关闭;较旧 SDK 发送的卡顿消息仍会根据其堆栈确定来源。
安全模型
- 服务仅侦听
127.0.0.1。仅接受来自回环 Host/Origin 的 WebSocket 升级请求 (可阻止跨站 WebSocket 劫持和 DNS 重绑定)。每条仪表板消息都会经过验证。 - AI Agent 端点(
/mcp、/api/agent/<tool>)会拒绝 Host 不是服务自身端口上的localhost/127.0.0.1的请求 (防止 DNS 重绑定)。/mcp会拒绝任何 Origin 与仪表板不同的请求;JSON 工具 API 则拒绝所有 Origin (防止 CSRF);它还要求Content-Type: application/json和X-Lionscope-Agent: 1,而网页只有在服务器从未批准的 CORS 预检后才能发送这些内容。 错误和日志中不会出现输入的文本。仅在启用--allow-device-control时才提供设备控制工具;该标志用于防止 AI 未经请求操作你的手机,并非安全 边界(任何本地进程都可以运行 adb)。工具结果中的应用数据会标记为不可信。 - 设备序列号和软件包名称都会按严格模式检查,并作为参数数组传给
adb, 绝不会经过主机 shell。 - SDK 网关(TCP,仅限
127.0.0.1)要求在 1 秒内将有效的hello作为首行发送,否则会关闭连接;它会关闭 看起来像 HTTP 的请求(浏览器可以向 localhost 发送 POST),限制 hello 和行的速率,限制行大小和连接数; 在通过设备上的pidof验证 hello 中的 PID 与其来源设备相符之前,不会回复任何内容(甚至不会发送welcome);所有拒绝响应都相同,因此探测者无法获知正在分析哪个应用。每台带有仅检查链接(0.9.0)的 设备都会有自己的网关侦听器,因此链接始终与其设备绑定; 只有可调试应用会以这种方式建立链接,并且 Inspect 只会询问 Android 报告为前台的应用。 用于 Inspect 链接的adb reverse tcp:8700映射只会在有人检查该设备时存在,且绝不会占用(或移除) 另一台正在运行的 Lionscope 服务所用的映射。 - 仪表板和屏幕套接字没有自己的密码:同一台机器上任何用户的任何进程,只要能访问
127.0.0.1, 就能使用它们,包括操作镜像中的模拟器。这并不比本地进程通过127.0.0.1:5037上的 adb 服务 已经拥有的访问权限更多;请勿在与不信任的人共用的机器上运行 Lionscope。 - 模拟器访问令牌会从模拟器的发现文件中读取,且仅由服务使用;绝不会记录日志或发送到浏览器。屏幕套接字
(
/ws/screen)具有相同的 Host/Origin 检查、大小上限和输入速率限制。只有在 AVD 名称与emulator -list-avds的输出匹配后,才会启动任何进程;对于并非由此服务启动的模拟器,只有在用户于仪表板 确认后才会关闭,且只会通过其自身的 gRPC 端点或针对其序列号运行adb emu kill来关闭 (绝不发送进程信号)。 - 对于实体设备,每次推送前都会检查 scrcpy 服务端 jar 的 SHA-256;手机上的命令行由固定预设构建
(仪表板的值不会传入设备 shell),并且在所有退出路径上都会移除手机进程和 adb 转发。视频和设备消息
都会进行大小检查。启动镜像时,adb 会短暂地将本地端口转发到手机;Lionscope 一连接上就会移除该转发。
如果你使用
adb -a运行 adb 服务(侦听所有网络接口),这段时间内该端口可从你的网络访问,因此请让 adb 保持默认的回环绑定。 - SDK 只会发送屏幕类名/别名、帧耗时,以及在启用卡顿检测时发送主线程的堆栈帧(类、方法、文件、行号;
启用 RxJava 跟踪时,还会发送 Rx 任务和消费者的类名,以及订阅该链的调用帧;并会在调试模式下发送
协程名称)和 StrictMode 消息,而且只会从可调试版本发送。当你
执行 Inspect 时,它还会发送视图树,包括屏幕上的文本(绝不包括密码字段的文本)。如果没有
adb reverse映射,设备上另一个侦听127.0.0.1:8700的应用可能会充当服务并接收该数据流, 包括在它请求时发送的视图树:请勿在通过 Lionscope 检查的设备上保留不可信的应用。 - 在应用中使用
Lionscope.okHttpInterceptor()后,SDK 还会仅在分析会话期间发送每个 HTTP 请求的方法、URL、标头、耗时,以及每个文本请求体最多 64 KB 的内容,并对看起来像凭据的值进行脱敏 (请参阅网络请求和模拟)。服务会检查每个字段的大小,并再次进行脱敏。 与其他 SDK 数据不同,网络捕获和模拟需要证明127.0.0.1:8700上的侦听器确实是 adb 信任的 Lionscope 服务:在欢迎会话链接之前,服务会为该应用进程生成一个随机密钥,并通过adb shell am broadcast将其交给 SDK 的LinkKeyReceiver;该接收器受android.permission.DUMP保护(只有 shell 和系统持有此权限,因此其他应用无法设置密钥)。 然后,welcome 会携带 SDK 的 hello 中一个新鲜随机数在该密钥下计算的 HMAC-SHA256;SDK 只有在验证通过后 才会记录请求并接受模拟规则。因此,替代服务侦听该端口的应用不会收到网络数据、无法注入模拟内容, 也无法编辑视图(0.9.0);但它仍可能收到上文所述的屏幕、帧、卡顿和 Inspector 视图树。 请求仅保存在内存中(10 分钟、32 MB),绝不会写入磁盘;只有启用--allow-network-bodies时, 请求体才会发送给 AI Agent。模拟规则只会通过应用的会话链接发送给应用,会话停止时会清除; Agent 设置这些规则时需要--allow-device-control(这些规则会改变应用所看到的内容)。 - 位置(0.10.0):坐标会经过检查(WGS 84 范围),并以定点数形式通过固定命令发送到设备。在实体设备上,
只有在 Lionscope 移动设备期间,才会设置 shell 的模拟位置权限,之后会恢复原来的模式。仪表板的 CSP
允许地图从
https://tile.openstreetmap.org加载图片;浏览器只会在“位置”面板打开期间获取这些图片 (并按 OSM 图块策略的要求,以仪表板源作为 Referer)。 - 实时视图编辑(0.9.0)只会通过捕获当前 Inspector 快照的 SDK 链接,发送到该快照中当前应用的视图,
并且只针对该应用最新一次捕获中的视图(SDK 会为每个 View 对象分配一个键;重新创建的视图会获得新键)。
编辑会改变应用的行为(例如启用已禁用的按钮、改写标签),因此 SDK 只会在具备上述链接证明的链接
(分析会话)上应用编辑,并对其他请求返回
unauthenticated。仪表板只会在你提交字段时发送编辑; Agent 需要--allow-device-control才能执行编辑。服务端和 SDK 都会根据固定边界检查值;每台设备的 编辑操作会受到速率限制(先允许突发 10 次,然后每秒 5 次),SDK 自身也会再次限速:最多保留 256 项, 最多允许 8 项等待主线程处理,并丢弃连接结束后尚未执行的编辑。SDK 只会调用平台 setter(以及 AppCompat 的背景色调 setter),不会调用其他应用代码,不过这些 setter 可能会触发应用的监听器; SDK 绝不会读取或设置密码字段的文本,也不会在某个已编辑字段变为密码字段后继续保留该字段的文本。
Known limitations
- 仅主进程。 只会分析包名对应的进程。次要进程(
:remote服务、WebView 沙盒渲染器)不会纳入 CPU、内存或 logcat 数据,SDK 也不会在这些进程中启动。 gfxinfo只能看到 UI 工具包帧。 在仅使用 ADB 的模式下,帧数据来自dumpsys gfxinfo,它只能看到 Android UI 工具包(HWUI)绘制的帧。渲染到SurfaceView或TextureView中的内容(视频、相机、地图、游戏、Flutter)不会被计数,因此 0 FPS 可能表示“没有绘制 UI 帧”,而不是应用卡死。- 可调试构建速度较慢。 在可调试构建上测得的数值比发布版本慢。使用 Lionscope 查找问题_所在位置_;使用 Macrobenchmark 或生产环境监控获取绝对数值。
- 屏幕归属按 500 ms 计算。 SDK 帧会聚合到 500 ms 的时间桶中,每个时间桶会归属于其结束时显示在最上层的屏幕。屏幕切换最后时刻的帧可能会计入下一个屏幕。
- 卡顿检测采用采样方式。 看门狗以约 50 ms 的步长工作,因此持续时间是近似值,略微超过阈值的阻塞可能会被漏掉。一个 tick 在主队列中等待其他工作所花的时间也会计为阻塞时间。
- 调用来源存在盲区。 无法得知协程的启动位置(Android 没有协程调试探针):调用来源会标明恢复执行的 continuation,且只有在 kotlinx.coroutines 调试模式下才会显示其名称。会话开始前已订阅的 Rx 链,或其
observeOn在与调用subscribe不同的线程上订阅的 Rx 链(observeOn位于subscribeOn上方),没有订阅位置;只有observeOn链会记录订阅位置。由Dispatchers.Main.immediate或 trampoline 执行的工作会在正在分发的消息中运行,并归属于该消息。 - 混淆过的 SDK 名称较短。 当卡顿的罪魁祸首是使用 R8 或 ProGuard 打包的第三方 SDK 时,其帧信息会显示为
com.vendor.internal.b.a (SourceFile:22):包名能说明哪个 SDK 阻塞了主线程,但行号无法指向可读的源代码。 - StrictMode 与应用共享策略。 Lionscope 添加的检测会遵循应用自身基础策略中的惩罚设置,因此可能会向 logcat 添加日志行(例如
penaltyLog)。应用自带penaltyListener时,会通过启发式方法检测,但无法识别所有监听器。只有当应用策略允许主线程进行网络操作时,才会报告网络违规。 - Inspector 中的 Compose 需要使用未经过缩减的调试构建,且该构建需包含 Compose 源代码信息(默认设置),并且必须在 SDK 启动后进行组合。可组合项仅包括从你的代码(以及 androidx Compose、Material 和 Navigation 之外的库)调用的项;不执行布局的可组合项(如 effect、
Dialog调用、provider)没有自己的节点,而Dialog的内容会显示在对话框窗口中。 - 实时编辑仅适用于 Views。 可组合项是只读的(每次重新组合时,其值都来自代码);编辑会一直生效,直到撤销编辑、应用自行设置该属性或视图被重新创建。
- 布局耗时是下限。 不会测量解析器的初始化;嵌套的
<include>会同时计入其父级和自身;只会检测通过 Activity 的 inflater 在主线程上进行的膨胀:在后台线程、通过 application-context inflater 或由 Compose 执行的布局膨胀不会被检测。 - 屏幕镜像。 模拟器需要包含 gRPC 端口和令牌的发现文件(已在 emulator 36.6 上测试)。AVD 没有鼠标,因此滚轮会通过短暂的触摸拖动来滚动;输入的按键在可打印 ASCII 字符范围内较为可靠(模拟器可能会丢弃其他字符);其他内容请使用粘贴。实体设备需要支持 WebCodecs H.264 解码的浏览器;只有在应用允许旋转的情况下,才能从仪表板旋转屏幕。镜像视频是手机自身编码器的输出,因此画质和延迟取决于手机。Linux 和 Windows 的发现位置与 Android Studio 的位置一致,但尚未在这些系统上试用。
- 内存数据使用
dumpsys meminfo --local获取:这会提供相同的 App Summary 数值,但不会要求应用转储其运行时统计数据(否则每次轮询都会触发 GC)。该操作仍需约 0.3–2 s,因此每 3 s 采样一次内存。 - 卡顿检测遵循 Android 自身的定义:在 API 31 及更高版本中,帧在截止时间之后完成即视为卡顿;超过一个 vsync 间隔的帧视为“慢”帧。在 API 31 以下版本中,会根据帧时间戳推断 vsync 周期,因此可以正确测量 90/120 Hz 设备。某些 OEM 构建版本(例如 Xiaomi 的 HyperOS、Android 15)会在正常帧上设置厂商标志位,并交换
FrameInterval/FrameStartTime标头标签;解析器可处理这两种情况。 - 网络捕获需要 OkHttp。 只有通过带有
Lionscope.okHttpInterceptor()的OkHttpClient发出的请求才会被捕获(Retrofit、Ktor 的 OkHttp 引擎、Coil/Glide 的 OkHttp 集成);HttpURLConnection、Ktor 的 CIO 引擎、WebView、gRPC 自有传输和原生代码都不会被捕获。支持 OkHttp 4.x 和 5.x(已通过 4.12 和 5.4 测试),不支持 3.x。只有在请求体可以安全地写入两次时才会捕获(非单次传输或双向传输,且长度已知并且不超过 1 MB);如果应用在响应结束前将其关闭,响应会显示为已截断。仅捕获不超过 64 KB 的文本类型请求体和响应体;不会解码 Brotli 和 zstd 请求体或响应体。如果应用从未将响应体读取完毕或关闭响应体,该响应会一直处于待处理状态。模拟由拦截器本身应用,因此只会响应经过该拦截器的请求;如果拦截器是通过addNetworkInterceptor添加的,则不会应用模拟。
配置
| 标志 | 环境变量 | 默认值 | 用途 |
|---|---|---|---|
--port <port> | LIONSCOPE_PORT | 8701 | 仪表板 HTTP + WebSocket 端口 |
--device-port <port> | LIONSCOPE_DEVICE_PORT | 8700 | SDK 网关(TCP、NDJSON);通过 adb reverse 访问 |
--adb <path> | LIONSCOPE_ADB | 自动 | adb 二进制文件的路径 |
--allow-device-control | 关闭 | AI Agent 可以截屏、点击、输入、启动应用和启动/关闭模拟器,并设置网络模拟 | |
--allow-network-bodies | 关闭 | AI Agent 可以在 get_network_request 中获取 HTTP 请求体和响应体 | |
LIONSCOPE_DASHBOARD_DIR | 自动 | 要提供服务的已构建仪表板 |
标志优先于环境变量。未设置 LIONSCOPE_DASHBOARD_DIR 时,服务端会提供 npm 包中随附的仪表板,或克隆仓库中的 dashboard/dist。无法选择在 127.0.0.1 以外的地址监听。
开发
npm install
npm run dev # server (tsx watch, --dev allows the Vite origin) + Vite on http://localhost:5173
npm test # all unit tests (protocol, server, dashboard)
npm run test:coverage
npm run typecheck
npm run lint
npm run format:check # or `npm run format` to fix
npm run build # production dashboard build
npm run build:cli # the npm package in cli/ (dashboard + bundled server); `cd cli && npm pack` to try it
Android SDK(需要 JDK 17 和 Android SDK):
cd android-sdk
./gradlew :lionscope:test :lionscope-noop:assemble :sample:assembleDebug
./gradlew :lionscope:publishToMavenLocal :lionscope-noop:publishToMavenLocal # same coordinates as JitPack
发布版本取自 Gradle 属性 lionscopeVersion(默认值为 0.9.0);在 JitPack 上则取自 git 标签。有关如何贡献,请参阅 CONTRIBUTING.md。
许可证
根据 Apache License, Version 2.0 授权。请参阅 NOTICE。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。