shizukurunner 中文文档 下载 App

命令卡死在执行中 - 哪些命令会让 Runner 停不下来

现象

Runner 里执行命令后界面显示"执行中"但一直等不到结果,通知栏有 Runner 的执行通知在转,强制关闭 Runner 通知没反应,甚至整个 Runner APP 卡住无响应——这就是命令卡死了。

根本原因

Runner 通过 Shizuku Binder 通道 fork shell 子进程执行命令,但有一个盲区:Runner 无法给正在执行的 shell 子进程发送终止信号(比如 SIGTERM、SIGKILL)。原因是 Shizuku Binder 通道不支持终端控制码,Runner 拿不到子进程的 stdin/stdout/stderr 的完整控制权。

所以一旦 shell 子进程进入以下状态,Runner 就管不了它了:

常见卡死场景

场景一:无限输出的命令

logcat 2&1 > /sdcard/log.txt
# 或
dumpsys activity activities --watch
# 或
top -b -d 1

这些命令没有自然结束条件,会一直跑。Runner 不知道它们什么时候该停,也没法告诉它们"你停一下"。

场景二:等待用户输入的命令

vi /sdcard/file.txt
# 或
read -p "Enter something: " var

命令停在等待输入上,但 Runner 不会给它喂空输入(或喂了也没用),子进程就一直阻塞在 read/select 调用上。

场景三:等待网络的命令

curl --connect-timeout 30 https://slow-server.com
# 如果目标服务器很慢,curl 会一直等

shell 命令自己的 timeout 参数(比如 --connect-timeout)在 Runner 环境下可能生效,但如果命令没有设置超时就会一直等。

怎么自救

方法一:Runner 返回值 141(SIGPIPE)

如果卡住的命令输出被 Runner 主动截断了(超过 1000 字符),Runner 会关闭输出管道,子进程收到 SIGPIPE 后会主动终止。此时你看到的返回值应该是 141。

所以如果命令跑了一会儿之后自动停下了、返回值 141——其实是 Runner 的保护机制生效了。不是真正卡死。

方法二:杀掉子进程

Runner 本身无法终止它 fork 的 shell 子进程,但可以用 adb 手动杀:

# 电脑端
adb shell ps | grep sh
# 找到 Runner fork 的 shell 进程 PID
adb shell kill -9 <PID>

方法三:重启手机

终极手段。因为卡住的 shell 子进程是后台进程,不会因为你杀 Runner APP 就跟着死——Android 的应用进程和它 fork 的 shell 子进程是两个独立实体。

怎么预防

预防一:不要跑无限输出命令

把 logcat 改成带时间限制的:

timeout 10 logcat -d -v time > /sdcard/log.txt

把 dumpsys --watch 改成单次执行:

dumpsys activity activities > /sdcard/activity.txt

预防二:脚本里加超时

给需要时间的命令加 timeout 或设置连接超时:

timeout 5 curl https://example.com

如果 Android 不带 timeout 命令,可以自己写一个:

# 简单超时函数
(function(){ local pid=$!; sleep 5 && kill $pid 2>/dev/null & wait $pid }) &

预防三:批量脚本里每条独立执行

不要把太多命令串在一条里,Runner 的输出截断逻辑对长命令链很敏感。分多条执行可以定位到底哪条命令出了问题。