一、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)
SIGQUIT和SIGUSR1,其他信號(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?
信號(hào)沖突:
signal_catcher已通過(guò)sigwait獨(dú)占處理SIGQUIT,開(kāi)發(fā)者注冊(cè)的回調(diào)無(wú)法生效。系統(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)注意:
-
避免信號(hào)沖突:尊重系統(tǒng)對(duì)
SIGQUIT的獨(dú)占使用。 - 選擇高層 API:通過(guò) Android 標(biāo)準(zhǔn)接口獲取 ANR 信息。
- 掌握底層機(jī)制:在系統(tǒng)級(jí)開(kāi)發(fā)中謹(jǐn)慎處理信號(hào),確保兼容性。
通過(guò) sigaction 與 sigwait 的對(duì)比,我們更深入理解了 Android 信號(hào)處理的底層邏輯,也為高性能應(yīng)用開(kāi)發(fā)提供了理論基礎(chǔ)。