mcpField
L
lionscope

lionscope

本地服务
github.com/risyadul开发工具04 天前更新

关于 lionscope

lionscope 是在浏览器中运行的 Android 性能分析器和布局检查器,面向开发者及其 AI Agent。它可将性能、布局、网络和日志信息放在统一时间轴中,并可通过可选 SDK 提供更深入的代码级数据。

主要功能

  1. 01统一时间轴查看 CPU、内存、帧与日志
  2. 02检查视图属性及 Compose 界面
  3. 03定位主线程卡顿及其来源代码
  4. 04查看 HTTP 流量并模拟响应
  5. 05操作模拟器与 USB 设备

适用场景

  1. 01排查应用卡顿及高耗时帧
  2. 02检查布局层级并编辑视图属性
  3. 03分析请求耗时或测试模拟响应
  4. 04让 AI Agent 查询设备和性能数据

MCP 相关

通过 MCP 或 CLI 将设备和分析数据提供给 AI Agent 使用。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"risyadul-lionscope": {
"command": "npx",
"args": [
"-y",
"lionscope"
]
}
}
}
启动命令npx -y lionscope

本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
npmlionscope0.9.0

工具

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 流量和模拟响应。
Lionscope 仪表板正在分析示例应用:左侧 Device 列显示模拟器屏幕,中间是屏幕轨道、CPU、内存和渲染图表,共用一条时间轴,下方是 logcat,右侧是 Screens 标签页

无头模拟器上运行的内置示例应用,由 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>/stat1 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 标签页:每个屏幕的帧、卡顿率、卡顿情况和主要帧阶段,按卡顿率排序

Screens 标签页按卡顿率对屏幕排序,并标出每个屏幕的主要帧阶段(面板更宽时,还会显示 p90 和慢帧)。如果应用为屏幕设置了别名(home_tab、legacy_page_1),则使用该别名;否则使用类名。 卡顿页签:看门狗阈值、StrictMode、Inflation 和 RxJava 跟踪控件;违规项按来源分组(一次 Input 点击、一条 RxJava observeOn 调用链、一个协程),以及包含行号(SlowWork.kt:16、AsyncWork.kt:42 和 :37)和来源的卡顿事件

从 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 排在第一,其包含的 dashboard 卡片标记为 nested,item_row 在列表屏幕中解析了 29 次

打开示例应用的各个屏幕后显示的 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 后,快照会刷新(不会持续刷新);点击刷新按钮则会获取新快照。

示例中 Detail 屏幕的检查器视图:冻结的屏幕上选中了价格标签,并标注为 MaterialTextView#price_tag;视图树及其属性包括祖先路径、来源 activity_detail.xml:28(开始标签末尾)、所属 DetailActivity、边界和尺寸,以及可实时编辑的文本、可见性、透明度、宽度和高度、平移、仰角、背景和文本颜色 - **树的来源。** 应用中集成了 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 取消取色)。松开鼠标时才会发送颜色,而不是每次调整时都发送。

示例中价格标签的背景色拾色器:饱和度和亮度方块、色相和透明度滑块、颜色值 #FFFFF3E0、Pick(吸管)按钮以及此屏幕上的颜色

编辑需要分析会话(按 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 轨道会用不同颜色的条形显示每个请求从开始到结束的过程,颜色代表状态;点击条形即可在选项卡中打开该请求。

Network 选项卡:示例应用的请求,包含方法、路径、主机、状态和耗时;一个失败的请求以红色显示,两个请求由模拟规则响应并标记为 Mocked;所选请求的概览:URL、状态以及模拟该请求的规则
// 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、toybox date +%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_PORT8701仪表板 HTTP + WebSocket 端口
--device-port <port>LIONSCOPE_DEVICE_PORT8700SDK 网关(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。

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。