黑科技!讓Native Crash 與ANR無處發(fā)泄导帝!

1.前言

高產(chǎn)似母豬的我守谓,又帶來了干貨記錄,本次是對signal的一個總結(jié)與回顧您单。不知道你們開發(fā)中斋荞,是否會遇到小部分的nativecrash 或者 anr,這部分往往是由第三方庫導(dǎo)致的或者當(dāng)前版本沒辦法修復(fù)的bug導(dǎo)致的虐秦,往往這些難啃的crash平酿,對現(xiàn)有的crash數(shù)據(jù)指標(biāo)造成一定影響,同時也對這小部分crash用戶不友好悦陋,那么我們有沒有辦法實現(xiàn)一套crash or anr重啟機(jī)制呢蜈彼?其實是有的,相信在各個大廠都有一套“安全氣囊”裝置叨恨,比如crash一定次數(shù)就啟用輕量版本或者自動重新啟動等等柳刮,下面我們來動手搞一個這樣的裝置!這也是我第三個s開頭的開源庫Signal。

https://github.com/TestPlanB/Signal

注意:前方高能秉颗!閱讀本文最好有一點ndk開發(fā)的知識噢痢毒!沒有也沒關(guān)系,沖吧蚕甥!

2.Native Crash

native crash不同于java/kotlin層的crash哪替,在java環(huán)境中,如果程序出現(xiàn)了不可預(yù)期的crash(即沒有捕獲)菇怀,就會往上拋出給最終的線程uncaghtexceptionhandler凭舶,在這里我們可以再次處理,比如屏蔽某個exception即可保持app的穩(wěn)定爱沟,然后native層的crash不一樣帅霜,native 層的crash大多數(shù)是“不可恢復(fù)”的,比如某個內(nèi)存方面的錯誤呼伸,這些往往是不可處理的身冀,需要中斷當(dāng)前進(jìn)程,所以如果發(fā)生了native crash括享,我們轉(zhuǎn)移到自定義的安全處理搂根,比如自動重啟后提示用戶等等,就會提高用戶很大的體驗感(比起閃退)铃辖。

信號量機(jī)制

當(dāng)native 層發(fā)生異常的時候剩愧,往往是通過信號的方式發(fā)送,給相對應(yīng)的信號處理器處理娇斩。

截屏2022-08-05 16.12.41.png

我們可以從signal.h看到搓扯,大概已經(jīng)定義的信號量有:

/**
 * #define SIGHUP 1
#define SIGINT 2
#define SIGQUIT 3
#define SIGILL 4
#define SIGTRAP 5
#define SIGABRT 6
#define SIGIOT 6
#define SIGBUS 7
#define SIGFPE 8
#define SIGKILL 9
#define SIGUSR1 10
#define SIGSEGV 11
#define SIGUSR2 12
## define SIGPIPE 13
#define SIGALRM 14
#define SIGTERM 15
#define SIGSTKFLT 16
#define SIGCHLD 17
#define SIGCONT 18
#define SIGSTOP 19
#define SIGTSTP 20
#define SIGTTIN 21
#define SIGTTOU 22
#define SIGURG 23
#define SIGXCPU 24
#define SIGXFSZ 25
#define SIGVTALRM 26
#define SIGPROF 27
#define SIGWINCH 28
#define SIGIO 29
#define SIGPOLL SIGIO
#define SIGPWR 30
#define SIGSYS 31
 */

具體的含義可自定百度或者google襟己,相信如果開發(fā)者都能在bugly等bug平臺上看到绳军。

信號量處理函數(shù)sigaction

一般的我們有很多種方式定義信號量處理函數(shù)彰导,這里介紹sigaction 頭文件:#include<signal.h>

定義函數(shù):int sigaction(int signum,const struct sigaction *act ,struct sigaction *oldact)

函數(shù)說明:sigaction會依參數(shù)signum指定的信號編號來設(shè)置該信號的處理函數(shù)。參數(shù)signum可以指定SIGKILL和SIGSTOP以外的所有信號瓶殃。如參數(shù)結(jié)構(gòu)sigaction定義如下:

struct sigaction {
    void (*sa_handler)(int);
    void (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t sa_mask;
    int sa_flags;
    void (*sa_restorer)(void);
};

信號處理函數(shù)可以采用void (sa_handler)(int)或void (sa_sigaction)(int, siginfo_t *, void )充包。到底采用哪個要看sa_flags中是否設(shè)置了SA_SIGINFO位,如果設(shè)置了就采用void (sa_sigaction)(int, siginfo_t *, void )遥椿,此時可以向處理函數(shù)發(fā)送附加信息基矮;默認(rèn)情況下采用void (sa_handler)(int),此時只能向處理函數(shù)發(fā)送信號的數(shù)值冠场。

sa_handler:此參數(shù)和signal()的參數(shù)handler相同家浇,代表新的信號處理函數(shù),其他意義請參考signal()碴裙;

sa_mask:用來設(shè)置在處理該信號時暫時將sa_mask指定的信號集擱置钢悲;

sa_restorer:此參數(shù)沒有使用点额;

**sa_flags **:用來設(shè)置信號處理的其他相關(guān)操作,下列的數(shù)值可用莺琳。

sa_flags還可以設(shè)置其他標(biāo)志:SA_RESETHAND:當(dāng)調(diào)用信號處理函數(shù)時还棱,將信號的處理函數(shù)重置為缺省值SIG_DFL SA_RESTART:如果信號中斷了進(jìn)程的某個系統(tǒng)調(diào)用,則系統(tǒng)自動啟動該系統(tǒng)調(diào)用 SA_NODEFER :一般情況下惭等, 當(dāng)信號處理函數(shù)運行時珍手,內(nèi)核將阻塞該給定信號。但是如果設(shè)置SA_NODEFER標(biāo)記辞做, 那么在該信號處理函數(shù)運行時琳要,內(nèi)核將不會阻塞該信號。參考

https://blog.csdn.net/qq_32198115/article/details/120720820

即我們可以通過這個函數(shù)秤茅,注冊我們想要的信號處理稚补,如果當(dāng)SIGABRT信號到來時,我們希望將其引到自我們自定義信號處理嫂伞,即可采用以下方式:

sigaction(SIGABRT, &sigc, nullptr);

其中sigc為sigaction結(jié)構(gòu)體的變量孔厉。

struct sigaction sigc;
//sigc.sa_handler = SigFunc;
sigc.sa_sigaction = SigFunc;
sigemptyset(&sigc.sa_mask);
sigc.sa_flags = SA_SIGINFO;

SigFunc為我們定義處理函數(shù)的指針,我們可以設(shè)定這樣一個函數(shù)帖努,去處理我們想要攔截的信號。

void SigFunc(int sig_num, siginfo *info, void *ptr) {
   自定義處理
}

native crash攔截

有了前面這些基礎(chǔ)知識粪般,我們就開始封裝我們的crash攔截吧拼余,作為庫開發(fā)者,我們希望把攔截的信號量交給上層去處理亩歹,所以我們的層次是這樣的匙监。

截屏2022-08-05 16.16.31.png

所以我們可以有以下代碼,具體細(xì)節(jié)可以看Signal 我們給出函數(shù)處理器小作。

https://github.com/TestPlanB/Signal

jobject currentObj;
JNIEnv *currentEnv = nullptr;
void SigFunc(int sig_num, siginfo *info, void *ptr) {
    // 這里判空并不代表這個對象就是安全的亭姥,因為有可能是臟內(nèi)存

    if (currentEnv == nullptr || currentObj == nullptr) {
        return;
    }
    __android_log_print(ANDROID_LOG_INFO, TAG, "%d catch", sig_num);
    __android_log_print(ANDROID_LOG_INFO, TAG, "crash info pid:%d ", info->si_pid);
    jclass main = currentEnv->FindClass("com/example/lib_signal/SignalController");
    jmethodID id = currentEnv->GetMethodID(main, "callNativeException", "(I)V");
    if (!id) {
        return;
    }
    currentEnv->CallVoidMethod(currentObj, id, sig_num);
    currentEnv->DeleteGlobalRef(currentObj);


}

被加載的時候由系統(tǒng)自動調(diào)用JNI_OnLoad。

extern "C" jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    jint result = -1;
    // 直接用vm進(jìn)行賦值顾稀,不然不可靠
    if (vm->GetEnv((void **) &currentEnv, JNI_VERSION_1_4) != JNI_OK) {
        return result;
    }
    return JNI_VERSION_1_4;
}

其中currentEnv代表著當(dāng)前jni環(huán)境达罗,我們在JNI_OnLoad階段進(jìn)行初始化即可,currentObj即代表我們要調(diào)用的方法對象静秆,因為我們要回調(diào)到j(luò)ava層粮揉,所以native肯定需要一個java對象,具體可以看到Signal里面的處理抚笔,值得注意的是扶认,我們在native想要在其他函數(shù)使用java對象的話,在初始函數(shù)賦值的時候殊橙,就必須采用env->NewGlobalRef方式分配一個全局變量辐宾,不然在該函數(shù)結(jié)束的時候狱从,對象的內(nèi)存就會變成臟變量(注意不是NULL)。

Spi機(jī)制的運用

如果還不明白spi機(jī)制的話叠纹,可以查看我之前寫的這篇spi機(jī)制季研,因為我們最終會將信號信息傳遞給java層,所以最終會在java最后執(zhí)行我們的重啟處理吊洼,但是重啟前我們可能會使用各種自定義的處理方案训貌,比如彈出toast或者各種自定義操作,那么這種自定義的處理就很合適用spi接口暴露給具體的使用者即可冒窍,所以我們Signal定義了一個接口递沪。

https://juejin.cn/post/7109666495566708766

interface CallOnCatchSignal {
    fun onCatchSignal(signal: Int,context: Context)
}

外部庫的調(diào)用者實現(xiàn)這個接口,將實現(xiàn)類配置在META-INF.services目錄即可综液,如圖:

截屏2022-08-05 16.19.34.png

如此一來款慨,我們就可以在自定義的MyHandler實現(xiàn)自己的重啟邏輯,比如重啟/自定義上報crash等等谬莹,demo可以看Signal的處理檩奠。

3.ANR

關(guān)于anr也是一個很有趣的話題,我們可以看到anr也會導(dǎo)致閃退附帽,主要是國內(nèi)各個廠商都有自己的自定義化處理埠戳,比如常規(guī)的彈出anr框或者主動閃退,無論是哪一種蕉扮,對于用戶來說都不是一個好的體驗整胃。

ANR傳遞過程

以android 11為例子,最終anr被檢測發(fā)生后喳钟,會調(diào)用ProcessErrorStateRecord類的appNotResponding方法屁使,去進(jìn)行dump 墓碑文件的操作,這個時候就會調(diào)用發(fā)送一個信號為Signal_Quit的信號奔则,對應(yīng)的常量為3蛮寂,所以如果我們想檢測到anr后去進(jìn)行自定義處理的話,按照上面所說直接用sigaction可以嗎易茬?

截屏2022-08-05 16.20.18.png

然而如果直接用sigaction去注冊Signal_Quit信號進(jìn)行處理的話酬蹋,會發(fā)現(xiàn)居然什么都沒有回調(diào)!那么這發(fā)生了什么疾呻!

原因就是我們進(jìn)程繼承Zygote進(jìn)行的時候就把主線程信號的掩碼也繼承了除嘹,Zygote進(jìn)程把這三個信號量加入了掩碼,該方法被調(diào)用在init方法中岸蜗。

截屏2022-08-05 16.20.59.png

掩碼的作用就是使得當(dāng)前的線程不響應(yīng)這三個信號量尉咕,交給其他線程處理。

那么其他線程這里指的是什么璃岳?其實就是SignalCatcher線程年缎,通常我們發(fā)生anr的時候也能看到log輸出悔捶,最終在run方法注冊處理函數(shù)。

截屏2022-08-05 16.21.31.png

最終調(diào)用WaitForSignal:

截屏2022-08-05 16.22.06.png

調(diào)用wait方法:

截屏2022-08-05 16.22.13.png

這個sigwait方法也是一個注冊信號處理函數(shù)的方法单芜,跟sigaction的區(qū)別可參考蜕该。

https://blog.csdn.net/finuxz/article/details/54969582

取消block

經(jīng)過上面的分析,相信能了解到為什么Signal_Quit監(jiān)聽不了了洲鸠,我們也知道堂淡,zygote通過掩碼把信號進(jìn)行了屏蔽,那么我們有辦法把這個屏蔽給打開嗎扒腕?答案是有的绢淀。

pthread_sigmask(SIG_UNBLOCK, &mask, &old))
sigemptyset(&mask);sigaddset(&mask, SIGQUIT);

我們可以通過pthread_sigmask設(shè)置為非block,即參數(shù)1的標(biāo)志瘾腰,把要取消屏蔽的信號放入即可皆的,如圖就是把SIGQUIT取消了,這樣一來我們再使用sigaction去注冊SIGQUIT就可以在信號出發(fā)時執(zhí)行我們的anr處理邏輯了蹋盆。值得注意的是费薄,SIGQUIT觸發(fā)也不一定由anr發(fā)生,這是一個必要但不充分的條件栖雾,所以我們還要添加其他的判斷楞抡,比如我們可以判斷一個queue里面的當(dāng)前message的when參數(shù)來判斷這個消息在隊列待了多久,又或者是我們自定義一個異步消息去查看這個消息什么時候回調(diào)了handler等等方法析藕,最終判斷是否是anr拌倍,當(dāng)然這個不是百分百準(zhǔn)確,目前我也沒想到百分百準(zhǔn)確的方法噪径,因為FileObserve監(jiān)聽traces文件已經(jīng)在android5以上不能用了,所以Signal里面沒有給出具體的判斷数初,只給了一個參考例子找爱。

4最后

上述所講的都在Signal這個庫里面有源碼與注釋,用起來吧泡孩!自定義處理可以用作檢測crash车摄,anr,也可以用作一個安全裝置仑鸥,發(fā)生crash重啟等等吮播,只要有腦洞,都可以實現(xiàn)眼俊!最后記得點個贊啦意狠!

文章引用

https://blog.csdn.net/qq_32198115/article/details/120720820

Android Native Crash 收集

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市疮胖,隨后出現(xiàn)的幾起案子环戈,更是在濱河造成了極大的恐慌闷板,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件院塞,死亡現(xiàn)場離奇詭異遮晚,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)拦止,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進(jìn)店門县遣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人汹族,你說我怎么就攤上這事萧求。” “怎么了鞠抑?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵饭聚,是天一觀的道長。 經(jīng)常有香客問我搁拙,道長秒梳,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任箕速,我火速辦了婚禮酪碘,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘盐茎。我一直安慰自己兴垦,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布字柠。 她就那樣靜靜地躺著探越,像睡著了一般。 火紅的嫁衣襯著肌膚如雪窑业。 梳的紋絲不亂的頭發(fā)上钦幔,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天,我揣著相機(jī)與錄音常柄,去河邊找鬼鲤氢。 笑死,一個胖子當(dāng)著我的面吹牛西潘,可吹牛的內(nèi)容都是我干的卷玉。 我是一名探鬼主播,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼喷市,長吁一口氣:“原來是場噩夢啊……” “哼相种!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起东抹,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蚂子,失蹤者是張志新(化名)和其女友劉穎沃测,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體食茎,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡蒂破,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了别渔。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片附迷。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖哎媚,靈堂內(nèi)的尸體忽然破棺而出喇伯,到底是詐尸還是另有隱情,我是刑警寧澤拨与,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布稻据,位于F島的核電站,受9級特大地震影響买喧,放射性物質(zhì)發(fā)生泄漏捻悯。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一淤毛、第九天 我趴在偏房一處隱蔽的房頂上張望今缚。 院中可真熱鬧,春花似錦低淡、人聲如沸姓言。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽何荚。三九已至,卻和暖如春猪杭,著一層夾襖步出監(jiān)牢的瞬間兽泣,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工胁孙, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人称鳞。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓涮较,卻偏偏與公主長得像,于是被迫代替她去往敵國和親冈止。 傳聞我的和親對象是個殘疾皇子狂票,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,077評論 2 355

推薦閱讀更多精彩內(nèi)容