源起
从Console程序到GUI程序
大学时代,主修专业是测控技术与仪器,和硬件打交道比较多,主要编程语言是汇编和C。后来学习Java时,Get到一个新技能:Java Swing(Java GUI框架),这之后,仿佛打开了新世界的大门,兴奋地动手做了一系列GUI程序:例如某音乐网站音乐下载器程序、物理实验报告程序等。
那时候,Java Swing早已诞生,但限于专业性质,之前写的多是汇编或者C语言控制台程序,而通过GUI框架构建的有完善图形界面的程序对我来说是全新的,之所以兴奋,是因为意识到,这种技术更具潜力,我有了利用这种技术做更多事情(实现想法和完成需求)的可能性!。
DRY(Don't Repeat Yourself)
回到当下。我是一名有多年经验的Android应用开发老狗🐶,负责开发的应用App,UI图标多采用SVG格式。当实现某个界面时,用到的图标要从Figma(设计资源网站)获取,有两种工作流:
- 从Figma导出SVG文件,再通过一些方式(例如在线转换网站、LLM)转换为Android支持的格式(Vector Drawable),再手动复制到Android Studio(Android开发IDE)中
- 从Figma导出SVG文件,再通过Android Studio的Asset导入功能逐个 导入(自动转换Vector Drawable,但不支持批量!)。 无论哪种,都需要在多个工具间切换,当你日复一日地重复这样的工作流时,多少会觉得厌倦,不禁会问:有没有更好的方式
后来,基于诉求,我实现了一个Figma CLI工具,核心能力如下:
- 和Figma打通,能批量导出指定界面的SVG
- 支持在本地将SVG批量转为Vector Drawable
只需一个导出命令,即可实现设计图标的批量导出和转换,不用在工具间来回切换,简单而直接!:
bash
export <figma_url> -o path/to/drawable
DRY(Don't Repeat Yourself) Again
1、问题筛查和识别
作为客户端App开发,我们开发的功能直接面向用户,是可见、可交互部分。很多Bug,总是先反馈到我们。当提测需求量比较大时,我们会看到一个很长的Bug List,然后逐个去看,比较容易识别的,转给其他端(后端、前端、SDK等),不容易识别的,则要分析Log,进一步确定。事实证明有相当比例的Bug,属于其他端,我们在问题识别上就要花费不少时间,而真正属于我们的Bug,则还需要花更多时间去分析和解决。
2、持续性问题排查 除了上面提到的问题识别问题,还有些问题归属是明确的,但需要持续性处理:例如自动化测试、Monkey测试问题,会在整个产品迭代周期持续产生,需要持续跟进
3、如何优化
一次会议上,同事反馈了上述问题,大家都有同感,但一致认为上述问题是无法避免的问题,这就是我们的工作,我们能做的就是降低识别和排查成本。初步提议:写脚本 ,根据日志关键字,例如错误码,进行初步判定。但脚本的判定依赖于预定义规则 ,且对于多个业务交叉性的问题,这个规则很难通过代码实现。事实证明后面确实没人去做这件事
观察和行动
观察
AI如火如荼,自年初开始,我就一直在持续了解和学习AI相关知识:看了李沐的《动手学深度学习》、毛玉仁的《大模型原理与技术》等教程,对一些经典论文:如《Attention Is All You Need 》、《ReAct: Synergizing Reasoning and Acting in Language Models 》、《SWE-agent: Agent-Computer Interfaces Enable Automated Software Engineering》等进行了拜读,不求百分百理解,了解其核心思想为止。此外,在X(原Twitter)上关注相关领域人士,阅读相关博客,了解相关动态...
行动
后面动手用Dify搭建AI工作流,使用Coding Agent Vibe了一个AI翻译应用项目:译读(由于电脑损坏,这个项目永久丢失,还是得做好数据备份!)
我逐渐找到了上述问题解决的新思路!
思路
我需要从确定性基于规则 的编程思路切换到基于神经网络 寻找解空间 的思路,用AI专家AK的理念来说,就是要从[Software 1.0 ]过渡到Software 2.0)
结合先前的观察和行动结果,我的目标已经比较明确:我要做的是一个基于LLM的日志分析系统:
text
Func = 识别用户问题并自动分析,输出诊断结论
Input = 用户问题描述、日志路径
Output = 诊断结论
规划
制定计划
一开始我采用了经典的ReAct架构 ,和ReAct论文的实现完全对齐:让Agent严格按Thought、Act、Observation的格式输出。
行动和观察
结果是我把论文中提到的问题都体验了一遍(规划路径单一、循环卡住等),另一个重要的问题:模型在思考中花费了大量Token去确保Thought-Act-Observation循环的进行(扮演好角色),导致Token效率低下。我期望模型把思考Token主要用在任务上!
调整计划
我抛弃了显式的Thought-Act-Observation的格式输出,直接利用模型的Function Calling(Tool Calling)能力实现工具调用,并配合适当的提示指令让Agent完成多轮次的隐式的Thought-Act-Observation,架构还是ReAct架构,只是实现层利用了现代模型本身的能力。
项目介绍
项目名称
SWEDeepDiver 是一个复合词,SWE指软件工程,DeepDiver字面意思是深潜者 ,这里比喻深入钻研和深入探索 ,SWEDeepDiver即软件工程领域问题的深入钻研者
项目用途
项目的初始目的是解决重复性 的Bug识别、分析和归因工作,主要面向Android端,主要支持的输入形式是问题描述和问题日志(文本),后来在实现的过程中,意识到它可以更通用:
- 多技术栈、多需求处理:可处理各种平台、技术栈的各类软件相关问题(Bug分析、性能分析、代码分析、数据统计等)
- 工作在本地文件系统:可自由探索目录、文件,可处理各类问题附件(日志、Trace、HeapDump File、截图、录屏等)
- 可扩展 :可利用
ProcessFile工具提供的口子,接入各个平台的工具链,覆盖更多需求场景
text
Func = 识别用户问题并自动分析,输出诊断结论
Input = 用户问题描述、问题附件包
Output = 诊断结论
SWEDeepDiver具有文件系统的常用操作能力,如Glob、Read、Grep等,它的工作空间是文件系统的一个目录
未来规划
- 完善文件处理工具:支持更多平台工具链,支持更多SWE场景(如移动端OOM分析、卡顿分析等)
- 完善代码分析工具:支持分析工具实现替换,支持远端仓库Clone,支持本地代码路径配置等
- 优化Agent架构和代码:提升安全性、提升性能等
- 提升Agent性能:提高分析质量(可回溯性、可复现性、结论确定性等)
一个例子作为本文结尾
我遇到了一个ANR(应用卡死无响应)问题,通过SWEDeepDiver进行问题分析
输入:
ini
iuuse_bugreport = r"""
App发生ANR,请分析原因
发生时间:12-02
问题目录:workspace/case/bugreport/bugreport-dada-AQ3A.240812.002-2025-12-15-10-08-51
"""
输出:
markdown
# 🔍 诊断报告
## 🎯 诊断结论
**基本情况**
- 进程:`co.ex`
- 主要界面:`FRPlayerPlayerActivity` / `FRPlayerListActivity`
- ANR 类型:**Input dispatching timed out(输入分发超时)**
- 发生时间:**2025-12-02 18:07:47 左右**
**更精确的结论(带不确定性说明):**
> 在用户从记录相关页面(`FRPlayerPlayerActivity` / `FRPlayerListActivity`)退到桌面时,应用在主线程上处理"焦点丢失 / 退出"相关回调时,执行了耗时操作(如大计算、同步磁盘/数据库 IO、大量资源释放等),导致约 5 秒内无法完成焦点事件处理,触发了 `Input dispatching timed out` ANR。
> 由于 ANR 抓取堆栈时机稍晚,堆栈中已经看不到具体业务方法,只能根据时序和机制进行高置信度推断,而非 100% 确证。
**置信度**:⭐⭐⭐⭐ 中高(约 75--80%)
(高于一般猜测,但低于"栈直接指到你代码"的确证级别)
---
## 📊 核心证据
### 1️⃣ ANR 事件与类型
**events 日志**
- `12-02 18:07:47.198`:
```text
I am_anr : [0,26801,co.ex,955792966,
Input dispatching timed out
(26d71a co.ex/co.ex.FRPlayerListActivity (server) is not responding.
Waited 5000ms for FocusEvent(hasFocus=false)).]
.```
含义:
- 系统向窗口 `co.ex/co.ex.FRPlayerListActivity` 发送了一个 `FocusEvent(hasFocus=false)`(即失去焦点)。
- 等待 **5 秒** 没收到"事件处理完成"的反馈,于是触发 Input ANR。
根据 Android ANR 机制([技术常识]):
- 这种 ANR 是**输入系统视角**下的 ANR:
- InputDispatcher 给某窗口发事件 → 5s 内收不到处理完成信号 → 认为该窗口/进程"未响应"。
---
### 2️⃣ 焦点切换与 5 秒"空窗期"
**焦点切换到桌面**
- `12-02 18:07:43.331`:
```text
I input_focus: [At display 0,focus update to ...
com.miui.home/com.miui.home.launcher.Launcher,from setFocusedWindow.]
.```
说明:
- 此时焦点已经切换到了桌面 Launcher,用户从你的 App 退到了桌面。
**ANR 后主线程才开始处理生命周期**
紧接在 `18:07:47.198 am_anr` 之后,你的进程立刻开始处理一系列与"退出/失焦"相关的事件(都在主线程 TID=26801):
```text
18:07:47.363 I wm_on_top_resumed_lost_called: [ ..., co.ex.FRPlayerListActivity, topStateChangedWhenResumed]
18:07:47.385 I input_focus: DIE in: PopupWindow ... ViewRootImpl.die ...
18:07:47.394 I input_focus: doDie:PopupWindow ...
18:07:47.395 I wm_on_paused_called: [ ..., co.ex.FRPlayerListActivity, performPause, ...]
18:07:47.399 I wm_on_stop_called: [ ..., co.ex.FRPlayerListActivity, STOP_ACTIVITY_ITEM, ...]
18:07:47.405 I input_focus: handleWindowFocusChanged: co.ex/co.ex.FRPlayerListActivity ...
18:07:47.412 I viewroot_draw_event: [VRI[FRPlayerListActivity], Not drawing due to not visible. Reason=View.INVISIBLE]
.```
时序上:
- 18:07:43.3 → 焦点给了桌面,应用应尽快处理失焦/暂停/停止。
- 18:07:47.2 → **刚好 5 秒后**,系统判定为 ANR。
- 18:07:47.36~47.41 → 你这边主线程才开始密集执行 `onTopResumedLost` / `onPause` / `onStop` / `handleWindowFocusChanged(false)` 等。
这说明:
- 这 5 秒里,主线程**没有及时处理应该在那时刻执行的消息/回调**(焦点事件、生命周期事件)。
- 满足 Input dispatch ANR 的经典模式:[技术常识] 主线程被耗时任务占用 → 输入事件无法在 5s 内走完流程 → ANR。
---
### 3️⃣ InputDispatcher 连接状态:真正"卡住"的是 Player Activity 窗口
从 bugreport 中的 InputDispatcher dump(你提到的那一段):
```text
Connections:
1022: channelName='7027620 co.ex/co.ex.FRPlayerPlayerActivity (server)',
status=NORMAL, monitor=false, responsive=false
WaitQueue: length=1
FocusEvent(hasFocus=true), seq=..., age=5002ms, wait=5002ms
703: channelName='4fe2a4d co.ex/co.ex.FRPlayerListActivity (server)',
status=NORMAL, monitor=false, responsive=true
.```
说明:
- `FRPlayerPlayerActivity` 的输入连接上,有一个 `FocusEvent(hasFocus=true)` 已经在队列里等了 **5002 ms** 且 `responsive=false`。
- `FRPlayerListActivity` 当前被标记为 `responsive=true`。
结合 `am_anr` 的 Subject 提示 `FRPlayerListActivity` 不响应:
- 进程内实际涉及两个窗口(List 和 Player),共享同一个主线程。
- Input 系统计数时,是针对**连接维度**(某个 InputChannel),`FRPlayerPlayerActivity` 那个连接迟迟没完成焦点事件;系统在整体评估时,Subject 中标记到的是 `FRPlayerListActivity`,但本质问题还是**这个进程的主线程 5 秒内未响应输入事件**。
这进一步加强了"主线程在这段时间内被卡住"的判断。
---
### 4️⃣ ANR 堆栈:主线程已"回到空闲状态"
`/data/anr/anr_2025-12-02-18-07-47-342` 中:
```text
Subject: Input dispatching timed out (26d71a co.ex/co.ex.FRPlayerListActivity ...)
----- pid 26801 at 2025-12-02 18:07:57.3185+0800 -----
Cmd line: co.ex
".co.ex" sysTid=26801
#00 __epoll_pwait
#01 android::Looper::pollInner
#02 android::Looper::pollOnce
#03 android.os.MessageQueue_nativePollOnce
#05 android.os.MessageQueue.next
#06 android.os.Looper.loopOnce
#07 android.os.Looper.loop
...
#18 android.app.ActivityThread.main
...
.```
特征:
- 栈顶只是在 `Looper.loop` / `epoll_pwait` 上等待消息,这是一种"主线程空闲"的状态。
- 说明:**真正导致 5 秒卡顿的耗时代码,在这个采样时刻已经结束**,所以堆栈没有直接指向你的某个 Activity 方法或业务逻辑。
这也是 Android 中常见的一种情况:
卡顿已经结束 → 抓到的 trace 只能证明"ANR 发生过",但**看不到具体卡在哪个方法**。
---
### 5️⃣ 系统资源情况:不像系统级"整体卡死"
同一 ANR 文件中的压力(pressure)指标:
```text
/proc/pressure/cpu:
some avg10=5.47 avg60=6.60 avg300=8.27
full avg10=0.00 ...
/proc/pressure/memory:
full avg10=0.00 ...
/proc/pressure/io:
full avg10=0.00 ...
.```
含义:
- CPU 有一定 `some` 压力,但 `full=0`,意味着**并不是整个系统长时间所有任务都抢不到 CPU**。
- 内存和 IO full 压力也为 0。
再看 MIUI 的 `abnormal_cpuinfo`(17:56~17:59):
```text
CPU usage ...:
22% 26215/co.ex: 16% user + 5.9% kernel
15% 26215/usap64
3.7% RenderThread
0.1% .co.ex (主线程)
.```
说明:
- 你的进程在当晚总体上属于比较重的 CPU 消费者之一,但没有看到系统级"所有东西都挂掉"的情况。
综上,更符合**应用自己在主线程做了重活**,而不是系统整体卡死。
---
## 🧠 根因推断(带置信度标注)
**假设 X:主线程在"焦点 / 生命周期回调"里执行耗时操作**(置信度:中高)
- 触发点:用户从 `FRPlayer*` 界面退出到桌面。
- 此时系统向窗口发送:
- `FocusEvent(hasFocus=false)`(当前前台 Activity 失去焦点)
- 生命周期回调 `onPause()` / `onStop()` / 相关 Window 回调 `onWindowFocusChanged(false)` 等。
- 在这些回调里,如果你做了:
- 同步写入大文件 / 数据库操作;
- 大量遍历/计算(比如处理记录数据);
- 大量 Bitmap/资源的释放并同步 GC 整理;
- 任何会阻塞 UI 线程数秒的操作;
- 那么主线程在 **5 秒内不能处理完输入焦点事件** → InputDispatcher 视角下就是"Input dispatching timed out" → 触发这类 ANR。
**依据:**
- `[日志]` `am_anr` 明确是 `Input dispatching timed out ... FocusEvent(hasFocus=false)`。
- `[日志]` 焦点在 18:07:43 切到了桌面,刚好约 5 秒后 ANR,在 ANR 之后数毫秒内才执行 `onTopResumedLost`、`onPause`、`onStop` 等。
- `[日志]` InputDispatcher dump 显示 `FRPlayerPlayerActivity` 的焦点事件在 WaitQueue 里等待 5002ms、`responsive=false`。
- `[日志]` ANR traces 只看到主线程在 Looper.loop 上空闲,可解释为:**耗时任务已经做完了,才抓的栈**。
- `[技术常识]` Input ANR 的典型机制就是"主线程长时间未处理输入/焦点事件"。
- `[技术常识]` 焦点丢失/退出回调中做重活,是最常见的这类 ANR 根因之一。
---
## ⚠️ 不确定性与边界
需要明确告诉你,目前无法做到 100% 确证的点有:
1. **缺少直接的应用方法栈**
- trace 抓到时主线程已经空闲,因此看不到"具体卡在 `onPause`/`onStop`/`onWindowFocusChanged` 或某个你自己的方法里"。
2. **这 5 秒到底在做什么?**
- 只能推断"主线程被阻塞 / 干重活"。
- 无法从现有日志中确认是写文件、访问数据库、网络请求、死锁,还是多个中等耗时步骤叠加。
3. **List vs Player 窗口的责任划分**
- InputDispatcher dump 显示 `FRPlayerPlayerActivity` 连接上的焦点事件滞留 5002ms;
- ANR Subject 中写的是 `FRPlayerListActivity` 不响应;
- 实际上它们同属一个进程、共用主线程,很可能是两个界面交替切换或重叠的场景,对最终结论影响不大,但没法精确到"是哪一个 Activity 内的哪一段代码"。
---
## 🔧 建议的改进与排查方向
结合你业务中的"FRPlayerList / FRPlayerPlayer"场景,可以重点自查:
1. **检查以下回调中是否有重活在主线程执行:**
- `FRPlayerListActivity` / `FRPlayerPlayerActivity` 及其父类里的:
- `onPause()`
- `onStop()`
- `onDestroy()`
- `onWindowFocusChanged(boolean hasFocus)`
- 以及这些回调直接或间接调用的逻辑,比如:
- 保存记录;
- 关闭/导出播放数据;
- 同步上传 / 写本地文件;
- 加载/释放大批资源等。
2. **通用优化建议**
- 所有**超过 50--100ms 的操作**,一律移出 UI 线程:
- 文件读写、数据库操作、网络请求;
- 大循环、复杂计算;
- 大对象的批量创建 / 销毁。
- 对在"退出页面/失焦"时触发的逻辑:
- 尽量简化为轻量标记(如改状态、post 到后台线程)。
- 真正耗时工作交给线程池 / 协程 / WorkManager 等后台执行。
3. **如果你能改包再复现 ANR**,建议:
- 打开 StrictMode / 主线程耗时监控(比如对 main thread 的 Looper 打桩统计 >200ms 的 message)。
- 在这些关键生命周期/焦点回调前后加带时间戳的日志,确认是哪一步拉长到了几秒。
---
## 🧾 小结(给你的直观结论)
- 这次 ANR 是一个**输入分发超时(Input dispatch ANR)**,不是普通的"后台服务卡住"那种。
- 触发点是:用户离开记录界面 → 系统发出焦点/生命周期事件 → 你的主线程 5 秒内没处理完。
- 日志显示:**5 秒后 ANR 触发,紧接着你的 Activity 才开始执行暂停/停止/失焦等回调**,非常符合"在这些回调里做了重活"的模式。
- 堆栈抓取时已经空闲,所以看不到具体方法名;这是唯一主要的不确定性来源。
- 建议你重点检查 `FRPlayerListActivity` / `FRPlayerPlayerActivity` 在退出/失焦时主线程做了哪些事,把任何偏重的工作都搬到后台线程去。