Android ANR 原理与 `signal_catcher` 源码深度解析:从信号处理到日志生成

一、ANR 触发机制回顾

Android 应用无响应(ANR)的本质是 主线程未能按时完成关键任务,系统通过以下超时阈值触发 ANR:

  • Input 事件处理:5 秒未完成。
  • 前台广播处理:10 秒(有序广播)。
  • Service 启动:前台 20 秒,后台 200 秒。
  • ContentProvider 发布:10 秒(首次获取时)。

触发 ANR 后,系统会向目标进程发送 SIGQUIT 信号,由 signal_catcher 线程生成堆栈快照(traces.txt)。
核心问题:为何开发者无法通过常规方式(如 sigaction)监听 SIGQUIT?这与 signal_catcher信号独占处理机制 密切相关。


二、signal_catcher 源码深度解析

1. 线程初始化:同步与信号监听

signal_catcher 在 ART 运行时启动时创建,通过多线程协作确保初始化顺序:

// 构造函数
SignalCatcher::SignalCatcher() {
  pthread_create(&pthread_, nullptr, &Run, this); // 创建子线程
  MutexLock mu(lock_);
  while (thread_ == nullptr) {
    cond_.Wait(); // 父线程等待子线程就绪
  }
}

// 子线程入口函数
void* SignalCatcher::Run(void* arg) {
  Runtime::AttachCurrentThread("Signal Catcher"); // 绑定 ART 运行时
  {
    MutexLock mu(lock_);
    thread_ = self;          // 标记子线程就绪
    cond_.Broadcast();       // 唤醒父线程
  }
  SignalSet signals(SIGQUIT, SIGUSR1);
  while (!halt_) {
    int sig = signals.Wait(); // 阻塞等待信号
    ProcessSignal(sig);       // 处理信号
  }
}

关键设计

  • 条件变量同步:父线程通过 cond_.Wait() 等待子线程完成初始化。
  • 信号集配置:明确监听 SIGQUITSIGUSR1,其他信号被忽略。
2. 信号处理核心逻辑

SIGQUIT 到达时,signal_catcher 调用 HandleSigQuit() 生成堆栈信息:

void HandleSigQuit() {
  std::ostringstream os;
  os << "----- pid " << getpid() << " -----\n";
  DumpCmdLine(os); // 输出进程名(如 com.example.app)
  
  Runtime::DumpForSigQuit(os); // 核心:生成所有线程堆栈
  Output(os.str());            // 写入 tombstoned
}

void Runtime::DumpForSigQuit(std::ostream& os) {
  thread_list_->SuspendAll(); // 暂停所有线程
  thread_list_->Dump(os);     // 遍历线程并转储堆栈
  thread_list_->ResumeAll();  // 恢复线程执行
}

关键步骤

  • 线程暂停:通过 SuspendAll() 暂停所有线程,确保堆栈一致性。
  • 堆栈解析:对每个线程调用 Thread::DumpState(),解析 Java/Native 调用链。
  • 日志写入:通过 tombstoned 服务生成 /data/anr/traces.txt
3. SIGUSR1 的调试功能

SIGUSR1 用于手动触发 GC 和性能分析:

void HandleSigUsr1() {
  Runtime::GetHeap()->CollectGarbage(false); // 强制 Full GC
  ProfileSaver::ForceProcessProfiles();      // 保存 JIT Profile 数据
}

应用场景

  • 内存泄漏调试:通过 adb shell kill -10 <pid> 手动触发 GC。
  • 性能优化:保存 JIT 编译的热点方法数据,用于 AOT 优化。

三、sigaction vs. sigwait:为何开发者无法捕获 SIGQUIT

1. sigaction:异步信号处理的局限性

开发者通常通过 sigaction 注册信号处理函数:

#include <signal.h>
void handler(int sig) { /* 处理信号 */ }

struct sigaction sa;
sa.sa_handler = handler;
sigaction(SIGQUIT, &sa, nullptr); // 注册 SIGQUIT 回调

问题:在 Android 中,此回调 不会触发
原因signal_catcher 通过 sigwait 独占处理 SIGQUIT,导致信号被阻塞,无法传递到应用层。

2. sigwait:同步信号处理的独占性

signal_catcher 使用 sigwait 实现同步信号处理:

sigset_t set;
sigaddset(&set, SIGQUIT);
pthread_sigmask(SIG_BLOCK, &set, nullptr); // 阻塞 SIGQUIT

// 在独立线程中等待信号
int sig;
sigwait(&set, &sig); // 阻塞直到信号到达
ProcessSignal(sig);

关键机制

  • 信号阻塞:调用 pthread_sigmask 阻塞目标信号,阻止其触发默认行为或异步回调。
  • 同步处理:信号被转换为同步事件,由独立线程处理,避免竞态条件。
3. 对比总结
特性 sigaction sigwait
处理方式 异步(中断当前线程,跳转到回调函数) 同步(线程主动等待信号)
线程安全 需自行保证(回调可能在任意线程执行) 天然线程安全(信号处理在独立线程完成)
信号覆盖风险 高(后注册的回调会覆盖前者) 无(信号被阻塞,其他模块无法接收)
适用场景 简单逻辑(如 SIGINT 退出) 复杂逻辑(如 ANR 日志生成)
性能影响 可能中断关键代码路径 可控(独立线程处理,无中断)

四、开发者注意事项:为何不要监听 SIGQUIT

  1. 信号冲突
    signal_catcher 已通过 sigwait 独占处理 SIGQUIT,开发者注册的回调无法生效。

  2. 系统调试依赖
    SIGQUIT 是生成 ANR 日志的关键信号,拦截会破坏系统调试能力。


五、实战:绕过限制监听 SIGQUIT

sigset_t set;
sigaddset(&set, SIGQUIT);
pthread_sigmask(SIG_UNBLOCK, &set, nullptr); // 解除阻塞

// 注册PLT Hook 拦截trace文件的write函数病将anr内容写入自定义anr文件
handleANR()
.............
// 向signal_catcher 线程发送SIGQUIT 信号
tgkill(getPid(),getSignalCatcherThreadId(),SIGQUIT)

  • 注册PLT Hook 拦截trace文件的write函数病将anr内容写入自定义anr文件
  • 向signal_catcher 线程发送SIGQUIT 信号,生成trace.txt,保持系统默认行为。

六、总结

signal_catcher 是 Android 系统调试能力的核心模块,其通过 同步信号处理多线程协作,确保 ANR 日志的高效生成。理解其源码后,开发者应注意:

  1. 避免信号冲突:尊重系统对 SIGQUIT 的独占使用。
  2. 选择高层 API:通过 Android 标准接口获取 ANR 信息。
  3. 掌握底层机制:在系统级开发中谨慎处理信号,确保兼容性。

通过 sigactionsigwait 的对比,我们更深入理解了 Android 信号处理的底层逻辑,也为高性能应用开发提供了理论基础。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容