Android ANR 原理與 `signal_catcher` 源碼深度解析:從信號(hào)處理到日志生成

一、ANR 觸發(fā)機(jī)制回顧

Android 應(yīng)用無(wú)響應(yīng)(ANR)的本質(zhì)是 主線程未能按時(shí)完成關(guān)鍵任務(wù),系統(tǒng)通過(guò)以下超時(shí)閾值觸發(fā) ANR:

  • Input 事件處理:5 秒未完成。
  • 前臺(tái)廣播處理:10 秒(有序廣播)。
  • Service 啟動(dòng):前臺(tái) 20 秒,后臺(tái) 200 秒。
  • ContentProvider 發(fā)布:10 秒(首次獲取時(shí))。

觸發(fā) ANR 后,系統(tǒng)會(huì)向目標(biāo)進(jìn)程發(fā)送 SIGQUIT 信號(hào),由 signal_catcher 線程生成堆??煺眨?code>traces.txt)。
核心問(wèn)題:為何開(kāi)發(fā)者無(wú)法通過(guò)常規(guī)方式(如 sigaction)監(jiān)聽(tīng) SIGQUIT?這與 signal_catcher信號(hào)獨(dú)占處理機(jī)制 密切相關(guān)。


二、signal_catcher 源碼深度解析

1. 線程初始化:同步與信號(hào)監(jiān)聽(tīng)

signal_catcher 在 ART 運(yùn)行時(shí)啟動(dòng)時(shí)創(chuàng)建,通過(guò)多線程協(xié)作確保初始化順序:

// 構(gòu)造函數(shù)
SignalCatcher::SignalCatcher() {
  pthread_create(&pthread_, nullptr, &Run, this); // 創(chuàng)建子線程
  MutexLock mu(lock_);
  while (thread_ == nullptr) {
    cond_.Wait(); // 父線程等待子線程就緒
  }
}

// 子線程入口函數(shù)
void* SignalCatcher::Run(void* arg) {
  Runtime::AttachCurrentThread("Signal Catcher"); // 綁定 ART 運(yùn)行時(shí)
  {
    MutexLock mu(lock_);
    thread_ = self;          // 標(biāo)記子線程就緒
    cond_.Broadcast();       // 喚醒父線程
  }
  SignalSet signals(SIGQUIT, SIGUSR1);
  while (!halt_) {
    int sig = signals.Wait(); // 阻塞等待信號(hào)
    ProcessSignal(sig);       // 處理信號(hào)
  }
}

關(guān)鍵設(shè)計(jì)

  • 條件變量同步:父線程通過(guò) cond_.Wait() 等待子線程完成初始化。
  • 信號(hào)集配置:明確監(jiān)聽(tīng) SIGQUITSIGUSR1,其他信號(hào)被忽略。
2. 信號(hào)處理核心邏輯

當(dāng) SIGQUIT 到達(dá)時(shí),signal_catcher 調(diào)用 HandleSigQuit() 生成堆棧信息:

void HandleSigQuit() {
  std::ostringstream os;
  os << "----- pid " << getpid() << " -----\n";
  DumpCmdLine(os); // 輸出進(jìn)程名(如 com.example.app)
  
  Runtime::DumpForSigQuit(os); // 核心:生成所有線程堆棧
  Output(os.str());            // 寫入 tombstoned
}

void Runtime::DumpForSigQuit(std::ostream& os) {
  thread_list_->SuspendAll(); // 暫停所有線程
  thread_list_->Dump(os);     // 遍歷線程并轉(zhuǎn)儲(chǔ)堆棧
  thread_list_->ResumeAll();  // 恢復(fù)線程執(zhí)行
}

關(guān)鍵步驟

  • 線程暫停:通過(guò) SuspendAll() 暫停所有線程,確保堆棧一致性。
  • 堆棧解析:對(duì)每個(gè)線程調(diào)用 Thread::DumpState(),解析 Java/Native 調(diào)用鏈。
  • 日志寫入:通過(guò) tombstoned 服務(wù)生成 /data/anr/traces.txt。
3. SIGUSR1 的調(diào)試功能

SIGUSR1 用于手動(dòng)觸發(fā) GC 和性能分析:

void HandleSigUsr1() {
  Runtime::GetHeap()->CollectGarbage(false); // 強(qiáng)制 Full GC
  ProfileSaver::ForceProcessProfiles();      // 保存 JIT Profile 數(shù)據(jù)
}

應(yīng)用場(chǎng)景

  • 內(nèi)存泄漏調(diào)試:通過(guò) adb shell kill -10 <pid> 手動(dòng)觸發(fā) GC。
  • 性能優(yōu)化:保存 JIT 編譯的熱點(diǎn)方法數(shù)據(jù),用于 AOT 優(yōu)化。

三、sigaction vs. sigwait:為何開(kāi)發(fā)者無(wú)法捕獲 SIGQUIT

1. sigaction:異步信號(hào)處理的局限性

開(kāi)發(fā)者通常通過(guò) sigaction 注冊(cè)信號(hào)處理函數(shù):

#include <signal.h>
void handler(int sig) { /* 處理信號(hào) */ }

struct sigaction sa;
sa.sa_handler = handler;
sigaction(SIGQUIT, &sa, nullptr); // 注冊(cè) SIGQUIT 回調(diào)

問(wèn)題:在 Android 中,此回調(diào) 不會(huì)觸發(fā)!
原因signal_catcher 通過(guò) sigwait 獨(dú)占處理 SIGQUIT,導(dǎo)致信號(hào)被阻塞,無(wú)法傳遞到應(yīng)用層。

2. sigwait:同步信號(hào)處理的獨(dú)占性

signal_catcher 使用 sigwait 實(shí)現(xiàn)同步信號(hào)處理:

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

// 在獨(dú)立線程中等待信號(hào)
int sig;
sigwait(&set, &sig); // 阻塞直到信號(hào)到達(dá)
ProcessSignal(sig);

關(guān)鍵機(jī)制

  • 信號(hào)阻塞:調(diào)用 pthread_sigmask 阻塞目標(biāo)信號(hào),阻止其觸發(fā)默認(rèn)行為或異步回調(diào)。
  • 同步處理:信號(hào)被轉(zhuǎn)換為同步事件,由獨(dú)立線程處理,避免競(jìng)態(tài)條件。
3. 對(duì)比總結(jié)
特性 sigaction sigwait
處理方式 異步(中斷當(dāng)前線程,跳轉(zhuǎn)到回調(diào)函數(shù)) 同步(線程主動(dòng)等待信號(hào))
線程安全 需自行保證(回調(diào)可能在任意線程執(zhí)行) 天然線程安全(信號(hào)處理在獨(dú)立線程完成)
信號(hào)覆蓋風(fēng)險(xiǎn) 高(后注冊(cè)的回調(diào)會(huì)覆蓋前者) 無(wú)(信號(hào)被阻塞,其他模塊無(wú)法接收)
適用場(chǎng)景 簡(jiǎn)單邏輯(如 SIGINT 退出) 復(fù)雜邏輯(如 ANR 日志生成)
性能影響 可能中斷關(guān)鍵代碼路徑 可控(獨(dú)立線程處理,無(wú)中斷)

四、開(kāi)發(fā)者注意事項(xiàng):為何不要監(jiān)聽(tīng) SIGQUIT?

  1. 信號(hào)沖突
    signal_catcher 已通過(guò) sigwait 獨(dú)占處理 SIGQUIT,開(kāi)發(fā)者注冊(cè)的回調(diào)無(wú)法生效。

  2. 系統(tǒng)調(diào)試依賴
    SIGQUIT 是生成 ANR 日志的關(guān)鍵信號(hào),攔截會(huì)破壞系統(tǒng)調(diào)試能力。


五、實(shí)戰(zhàn):繞過(guò)限制監(jiān)聽(tīng) SIGQUIT

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

// 注冊(cè)PLT Hook 攔截trace文件的write函數(shù)病將anr內(nèi)容寫入自定義anr文件
handleANR()
.............
// 向signal_catcher 線程發(fā)送SIGQUIT 信號(hào)
tgkill(getPid(),getSignalCatcherThreadId(),SIGQUIT)

  • 注冊(cè)PLT Hook 攔截trace文件的write函數(shù)病將anr內(nèi)容寫入自定義anr文件
  • 向signal_catcher 線程發(fā)送SIGQUIT 信號(hào),生成trace.txt,保持系統(tǒng)默認(rèn)行為。

六、總結(jié)

signal_catcher 是 Android 系統(tǒng)調(diào)試能力的核心模塊,其通過(guò) 同步信號(hào)處理多線程協(xié)作,確保 ANR 日志的高效生成。理解其源碼后,開(kāi)發(fā)者應(yīng)注意:

  1. 避免信號(hào)沖突:尊重系統(tǒng)對(duì) SIGQUIT 的獨(dú)占使用。
  2. 選擇高層 API:通過(guò) Android 標(biāo)準(zhǔn)接口獲取 ANR 信息。
  3. 掌握底層機(jī)制:在系統(tǒng)級(jí)開(kāi)發(fā)中謹(jǐn)慎處理信號(hào),確保兼容性。

通過(guò) sigactionsigwait 的對(duì)比,我們更深入理解了 Android 信號(hào)處理的底層邏輯,也為高性能應(yīng)用開(kāi)發(fā)提供了理論基礎(chǔ)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容