鴻蒙開源第三方組件—ANR異常監(jiān)測組件 ANR-WatchDog-ohos
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)
前言
基于安卓平臺(tái)的消息彈框組件ANR-WatchDog(https://github.com/SalomonBrys/ANR-WatchDog),實(shí)現(xiàn)鴻蒙化遷移和重構(gòu)。代碼已經(jīng)開源到(https://gitee.com/isrc_ohos/anr-watch-dog-ohos),歡迎各位下載使用并提出寶貴意見!
背景
ANR-WatchDog-ohos是一個(gè)監(jiān)測組件,可以監(jiān)測鴻蒙應(yīng)用的ANR(Application Not Response-應(yīng)用程序無響應(yīng))錯(cuò)誤,并能及時(shí)拋出異常。在此組件被移植成功之前,鴻蒙應(yīng)用程序是無法捕獲和報(bào)告ANR錯(cuò)誤的,調(diào)查ANR的唯一方法是查看/data/anr/traces.txt文件。因此ANR-WatchDog-ohos為ANR捕獲過程提供了更好的交互性、便捷性以及可視化的效果,同時(shí)也提升了程序的健壯性。
組件效果展示
1、組件應(yīng)用的界面介紹
為了更好的向開發(fā)者展示組件的運(yùn)行效果,先來了解一下組件應(yīng)用中各按鈕的含義。在圖1中,藍(lán)色框內(nèi)是ANR的監(jiān)測模式設(shè)置按鈕,紅色框內(nèi)的是ANR模擬按鈕。下面具體解釋各按鈕的含義:
- Min ANR duration:阻塞響應(yīng)時(shí)間按鈕。開發(fā)者通過點(diǎn)擊按鈕設(shè)置阻塞響應(yīng)時(shí)間為2秒、4秒或6秒,即應(yīng)用阻塞2秒、4秒或6秒后,執(zhí)行特定的響應(yīng)行為。
- Report mode:報(bào)告模式按鈕。開發(fā)者通過點(diǎn)擊按鈕設(shè)置ANR發(fā)生時(shí),HiLog中輸出錯(cuò)誤報(bào)告的模式:All Threads表示輸出每個(gè)線程的錯(cuò)誤日志;Main thread only 表示只輸出主線程的錯(cuò)誤日志;Filtered表示只輸出符合特定過濾條件的線程的錯(cuò)誤日志。
- Behaviour:響應(yīng)行為按鈕。開發(fā)者通過點(diǎn)擊按鈕設(shè)置ANR發(fā)生時(shí)應(yīng)用的響應(yīng)行為:Crash表示應(yīng)用閃退;Silent表示開發(fā)者自定義應(yīng)用的響應(yīng)行為。
- Thread Sleep:可以模擬主線程休眠。
- Infinite loop:可以模擬主線程無限循環(huán)。
- Dead lock:可以模擬主線程死鎖。

圖1 ANR-WatchDog-ohos組件應(yīng)用的界面介紹
2、組件運(yùn)行效果展示
通過點(diǎn)擊圖1紅色框內(nèi)三個(gè)不同的按鈕,可以看到三種不同的ANR發(fā)生時(shí)組件的運(yùn)行效果。為了更清楚的展現(xiàn)檢測模式的作用,我們給每個(gè)ANR模擬按鈕設(shè)置不同的檢測模式。下面對(duì)組件的運(yùn)行效果進(jìn)行詳細(xì)描述:
1、線程休眠
ANR監(jiān)測模式:阻塞響應(yīng)時(shí)間為2秒,報(bào)告模式為All Threads、響應(yīng)行為Crash。
點(diǎn)擊Thread Sleep按鈕,啟動(dòng)主線程休眠后,ANR-WatchDog-ohos組件監(jiān)測到程序在2秒內(nèi)一直無響應(yīng),于是觸發(fā)應(yīng)用閃退,并通過HiLog報(bào)告所有線程的ANR詳情,其模式設(shè)置和執(zhí)行效果如圖2所示。
在報(bào)告中,可以根據(jù)“Caused by”后面的堆棧信息追蹤查看線程休眠的具體原因,如圖3所示。

圖2 線程休眠設(shè)置流程和執(zhí)行效果

圖3 監(jiān)測線程休眠后閃退輸出的HiLog信息
2、線程無限循環(huán)
ANR監(jiān)測模式:阻塞響應(yīng)時(shí)間為4秒,報(bào)告模式為All Threads、響應(yīng)行為Crash。
點(diǎn)擊Infinite loop按鈕,啟動(dòng)線程無限循環(huán)后,ANR-WatchDog-ohos組件監(jiān)測到程序在4秒內(nèi)一直無響應(yīng),于是觸發(fā)應(yīng)用閃退,并通過HiLog報(bào)告主線程的ANR錯(cuò)誤詳情,其監(jiān)測模式設(shè)置和執(zhí)行效果如圖4所示,HiLog報(bào)告主線程的ANR詳情如圖5所示。

圖4 線程無限循環(huán)設(shè)置流程和執(zhí)行效果

圖5 監(jiān)測線程無限循環(huán)后閃退輸出的HiLog信息
3、線程死鎖
ANR監(jiān)測模式:阻塞響應(yīng)時(shí)間為6秒,報(bào)告模式為Filtered(只報(bào)告以“APP:”為前綴的線程)、響應(yīng)行為Crash。
點(diǎn)擊Dead lock按鈕,啟動(dòng)線程死鎖后,ANR-WatchDog-ohos組件監(jiān)測到程序在6秒內(nèi)一直無響應(yīng),于是觸發(fā)應(yīng)用閃退,并通過HiLog報(bào)告以“APP:”為前綴線程的ANR錯(cuò)誤詳情,其監(jiān)測模式設(shè)置和執(zhí)行效果如圖6所示,HiLog報(bào)告主線程的ANR詳情如圖7所示。

圖6 線程死鎖設(shè)置流程和執(zhí)行效果

圖7 監(jiān)測線程死鎖后閃退輸出的HiLog信息
值得注意的是:無論在哪種ANR類型下,只要將Behaviour設(shè)置為Silent,應(yīng)用遇到ANR時(shí)的響應(yīng)行為都需要開發(fā)者自定義。例如此處我們定義:應(yīng)用遇到ANR的情況時(shí),通過HiLog打印出ANR-Watchdog-Demo的tag,如圖7所示:

圖8 Silent行為下不閃退只輸出HiLog信息
Sample解析
ANR-WatchDog-ohos組件能夠監(jiān)測多種類型的ANR錯(cuò)誤,及時(shí)捕捉并觸發(fā)相應(yīng)的響應(yīng)行為。下面將具體講解ANR-WatchDog-ohos組件的使用方法,共分為7個(gè)步驟,其中步驟1至步驟2在MyApplication文件中進(jìn)行,步驟3至步驟7在MainAbility文件中進(jìn)行:
步驟1. 導(dǎo)入相關(guān)類并實(shí)例化類對(duì)象。
步驟2. 設(shè)置ANRListener監(jiān)聽。
步驟3. 模擬主線程休眠、無限循環(huán)和死鎖。
步驟4. 創(chuàng)建xml文件。
步驟5. 設(shè)置整體布局,并實(shí)例化MyApplication對(duì)象。
步驟6. 設(shè)置ANR檢測模式Button的點(diǎn)擊事件。
步驟7. 設(shè)置ANR模擬Button的點(diǎn)擊事件。
(1)導(dǎo)入相關(guān)類并實(shí)例化類對(duì)象
在MyApplication文件中,導(dǎo)入ANRError類和ANRWatchDog類并實(shí)例化ANRWatchDog類的對(duì)象,設(shè)置默認(rèn)的阻塞響應(yīng)時(shí)間Min ANR duration為2000毫秒(2秒)。其中,ANRWatchDog類的作用是檢測ANR的情況是否出現(xiàn),ANRError類的作用是拋出錯(cuò)誤信息,即正在運(yùn)行線程的堆棧追蹤信息。
- //導(dǎo)入ANRError類和ANRWatchDog類
- import com.github.anrwatchdog.ANRError;
- import com.github.anrwatchdog.ANRWatchDog;
- //實(shí)例化ANRWatchDog類對(duì)象
- ANRWatchDog anrWatchDog = new ANRWatchDog(2000);//設(shè)置阻塞響應(yīng)時(shí)間為2000毫秒(2秒)
(2)設(shè)置ANRListener監(jiān)聽
當(dāng)響應(yīng)行為按鈕設(shè)置為Crash:
由于MyApplication類繼承了AbilityPackage類,因此需要重寫onInitialize()方法。在onInitialize()方法中,需要調(diào)用ANRWatchDog類的setANRListener()方法,為應(yīng)用設(shè)置ANR監(jiān)聽,其中onAppNotResponding()方法用于在上述監(jiān)聽中設(shè)置應(yīng)用的ANR響應(yīng)行為,此處設(shè)置ANR情況發(fā)生時(shí),應(yīng)用crash并拋出異常。當(dāng)需要提前或推遲報(bào)告ANR錯(cuò)誤或者執(zhí)行響應(yīng)行為時(shí),在onInitialize()方法中,可以通過調(diào)用ANRWatchDog類的setANRInterceptor()方法設(shè)置攔截器,實(shí)現(xiàn)在給定的響應(yīng)時(shí)間內(nèi)對(duì)異?;蚱渌远x的響應(yīng)行為進(jìn)行攔截。
- //重寫onInitialize()方法
- @Override
- public void onInitialize() {
- super.onInitialize();
- //設(shè)置ANRListener監(jiān)聽
- anrWatchDog.setANRListener(new ANRWatchDog.ANRListener() {
- @Override//設(shè)置監(jiān)測到ANR錯(cuò)誤后的具體響應(yīng)行為
- public void onAppNotResponding(ANRError error) {
- ...
- throw error;//直接拋出錯(cuò)誤異常,程序閃退 }
- })
- .setANRInterceptor(new ANRWatchDog.ANRInterceptor() {
- @Override//定義攔截器來決定是否提前或推遲
- public long intercept(long duration) {...}
- });
- anrWatchDog.setIgnoreDebugger(true).start();//在debug的情況下也能拋出ANR異常
- }
當(dāng)響應(yīng)行為按鈕設(shè)置為Silent:
此時(shí)需要設(shè)置ANRListener 類的對(duì)象為final 對(duì)象,對(duì)象內(nèi)部的內(nèi)容可變,但是引用不會(huì)變。我們定義:線程阻塞后程序不閃退,而是打印ANR-Watchdog-Demo的tag,因此在重寫ANRWatchDog類的onAppNotResponding()方法時(shí),只需要自定義相應(yīng)的Hilog報(bào)告即可,不需要拋出異常。
- final ANRWatchDog.ANRListener silentListener = new ANRWatchDog.ANRListener() {
- @Override//重寫setANRListner()方法
- public void onAppNotResponding(ANRError error) {//自定義ANRListener回調(diào)
- HiLog.error(new HiLogLabel(HiLog.LOG_APP,0,"ANR-Watchdog-Demo"), "", error);
- }
- };
(3)模擬線程休眠、線程無限循環(huán)和線程死鎖
為了使ANR-WatchDog-ohos能監(jiān)測到如線程休眠、線程無限循環(huán)和線程死鎖不同情況下的ANR,需要分別設(shè)置函數(shù),模擬這三種情況。
線程休眠
- private static void Sleep() {//模擬線程休眠的情況
- try {
- Thread.sleep(8 * 1000);//線程休眠8秒后釋放鎖
- }
- catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
線程無限循環(huán)
- private static void InfiniteLoop() {//模擬線程無限循環(huán)的情況
- int i = 0;
- while (true) {//判斷條件恒為true,則無限循環(huán)
- i++;
- }
- }
線程死鎖
- private void lock(){//模擬線程死鎖的情況
- new Thread(){
- @Override
- public void run(){
- synchronized (MainAbility.this){//線程占用鎖
- try{
- Thread.sleep(60000);//休眠60秒后釋放鎖
- }
- ...}
- }.start();
- synchronized (MainAbility.this){//主線程也同時(shí)占用鎖
- HiLog.info(new HiLogLabel(HiLog.LOG_APP,0,"ANR-Failed"),"主線程也申請(qǐng)鎖");
(4)創(chuàng)建xml文件
在ability_main.xml中創(chuàng)建顯示文件,最主要的部分是圖1藍(lán)框中3個(gè)模式設(shè)置按鈕和紅框中3個(gè)ANR類型的按鈕。
- <DirectionalLayout//創(chuàng)建整體布局
- xmlns:ohos="http://schemas.huawei.com/res/ohos"
- ohos:height="match_parent"
- ohos:width="match_parent"
- ohos:orientation="vertical">
- ...
- //圖1紅框中的按鈕
- <Button //阻塞響應(yīng)時(shí)間按鈕
- ohos:id="$+id:minAnrDuration"
- ohos:width="match_content"
- ohos:height="match_content"
- ohos:text="2s"
- ohos:text_size="150"/>
- ... //報(bào)告模式按鈕和響應(yīng)行為按鈕同上
- //圖1紅框中的按鈕
- <Button //線程休眠按鈕
- ohos:id="$+id:threadSleep"
- ohos:left_margin="24"
- ohos:width="match_content"
- ohos:height="match_content"
- ohos:text="$string:threadsleep"
- .../>
- ... //線程無限循環(huán)按鈕和死鎖按鈕同上
- </DirectionalLayout>
(5)設(shè)置整體布局,并實(shí)例化MyApplication對(duì)象
通過setUIContent()方法加載上一步設(shè)置好的xml文件作為整體顯示布局,實(shí)例化MyApplication對(duì)象為后續(xù)設(shè)置各按鈕的點(diǎn)擊事件做準(zhǔn)備。
- setUIContent(ResourceTable.Layout_ability_main);//加載UI布局
- final MyApplication application = (MyApplication) getAbilityPackage();//實(shí)例化
(6)設(shè)置ANR檢測模式Button的點(diǎn)擊事件
本步驟需要阻塞響應(yīng)時(shí)間、報(bào)告模式和響應(yīng)行按鈕的點(diǎn)擊事件。
阻塞響應(yīng)時(shí)間Button
為實(shí)現(xiàn)每點(diǎn)擊按鈕一次就切換一種阻塞響應(yīng)時(shí)間,需要用公式將變量application.duration控制在2秒、4秒和6秒之間。application.duration的初始值為4,每點(diǎn)擊一次按鈕,將application.duration整除6的余數(shù)加上2的值重新復(fù)制給application.duration,可以實(shí)現(xiàn)上述切換效果。
- minAnrDurationButton.setClickedListener(new Component.ClickedListener() {
- @Override//重寫onClick()方法
- public void onClick(Component component) {
- application.duration = application.duration % 6 + 2;//得到整除6的余數(shù)加2
- minAnrDurationButton.setText(application.duration + " seconds");
- }
- });
報(bào)告模式Button
為實(shí)現(xiàn)每點(diǎn)擊按鈕一次就切換一種報(bào)告模式,需要用公式將變量mode控制在0、1、2這三個(gè)值中。0表示All Threads;1表示Main thread only;2表示Filtered。mode初始值為0,所以第一次點(diǎn)擊后mode值變?yōu)?,通過setReportMainThreadOnly()方法設(shè)置為只報(bào)告主線程,其他情況與上述類似。
- reportModeButton.setClickedListener(new Component.ClickedListener() {
- @Override//重寫onClick()方法
- public void onClick(Component component) {
- mode = (mode + 1) % 3;//得到mode加1并整除3后的余數(shù)
- switch (mode) {
- case 0:
- ...//所有線程
- application.anrWatchDog.setReportAllThreads();break ;
- case 1:
- ...//只有主線程
- application.anrWatchDog.setReportMainThreadOnly();break ;
- case 2:
- ...//過濾以“APP:”為前綴的線程
- application.anrWatchDog.setReportThreadNamePrefix("APP:");break ;
- }
- }
- });
響應(yīng)行為Button
crash變量是ANR響應(yīng)行為的標(biāo)志位,為實(shí)現(xiàn)每點(diǎn)擊按鈕一次就切換一種響應(yīng)行為,需要判斷crash變量是否為true。如果crash變量為true,則說明在監(jiān)測到ANR錯(cuò)誤后應(yīng)用直接閃退,需要通過setANRListener()方法調(diào)用步驟(2)中響應(yīng)行為為Crash時(shí)的onAppNotResponding()方法;反之,則說明開發(fā)者自定義了監(jiān)測到ANR錯(cuò)誤后應(yīng)用的響應(yīng)行為,需要通過setANRListener()方法調(diào)用步驟(2)中的響應(yīng)行為為Silent時(shí)的onAppNotResponding()方法。
- behaviourButton.setClickedListener(new Component.ClickedListener() {
- @Override//重寫onClick()方法
- public void onClick(Component component) {
- crash = !crash;每次點(diǎn)擊更改crash的布爾類型
- if (crash) {//crash為true
- behaviourButton.setText("Crash");
- application.anrWatchDog.setANRListener(null);//無需設(shè)置回調(diào)
- } else {//crash不為true
- behaviourButton.setText("Silent");//自定義ANRListener回調(diào)
- application.anrWatchDog.setANRListener(application.silentListener);
- }
- }
(7)設(shè)置ANR模擬Button的點(diǎn)擊事件
最后需要設(shè)置線程休眠、線程無限循環(huán)和線程死鎖按鈕的點(diǎn)擊事件。此處以線程休眠按鈕為例,只需在對(duì)應(yīng)的onClick()方法中調(diào)用各自的模擬函數(shù)即可,其他兩種情況同理。
- findComponentById(ResourceTable.Id_threadSleep).setClickedListener(new Component.ClickedListener() {//線程休眠Button的click點(diǎn)擊事件
- @Override
- public void onClick(Component component) {//重寫onClick()方法
- Sleep();//調(diào)用模擬線程休眠的函數(shù)
- }
- });
Library解析
Library包含兩個(gè)重要的類,即ANRWatchDog和ANRError,它們向開發(fā)者提供使用ANR-WatchDog-ohos組件監(jiān)測并處理ANR錯(cuò)誤的具體執(zhí)行方法,本節(jié)將分別講解ANRWatchDog類和ANRError類的內(nèi)部邏輯。
1、ANRWatchDog類
(1)構(gòu)造方法阻塞響應(yīng)時(shí)間
ANRWatchDog類繼承自Thread類,其實(shí)質(zhì)是一個(gè)線程,因此根據(jù)線程的特性,我們可以隨時(shí)將其中斷。ANRWatchDog類提供了兩個(gè)構(gòu)造方法,使用第二個(gè)帶參的構(gòu)造方法,開發(fā)者能夠?qū)ψ枞憫?yīng)時(shí)間進(jìn)行設(shè)置,使用第一個(gè)不帶參的構(gòu)造方法,阻塞響應(yīng)時(shí)間默認(rèn)配置為5000ms。
- //構(gòu)造方法一
- public ANRWatchDog() {
- this(DEFAULT_ANR_TIMEOUT);//使用默認(rèn)的阻塞響應(yīng)時(shí)間5000ms
- }
- //構(gòu)造方法二
- public ANRWatchDog(int timeoutInterval) {
- super();
- _timeoutInterval = timeoutInterval;//自定義阻塞響應(yīng)時(shí)間timeoutInterval
- }
(2)任務(wù)單元_ticker判斷主線程是否阻塞

圖9 監(jiān)測主線程是否阻塞的原理
在ANRWatchDog類中,監(jiān)測主線程是否阻塞的具體原理流程如圖9,其核心是向主線程拋出一個(gè)Runnable類型的任務(wù)單元_ticker,然后判斷其在特定時(shí)間內(nèi)是否被主線程處理,若_ticker被處理,說明主線程未阻塞,需要進(jìn)行循環(huán)判斷。若未被處理,說明主線程阻塞,需要向開發(fā)者發(fā)送ANR錯(cuò)誤信息。
變量_tick標(biāo)志著_ticker是否被處理,其初始值為0,并且是volatile類型的,這個(gè)類型的好處是能夠保證此變量在被不同線程操作時(shí)的可見性,即如果某線程修改了此變量的值,那么新值對(duì)其他線程來說是立即可見的。在未執(zhí)行在_ticker之前,_tick的值為阻塞響應(yīng)時(shí)間,執(zhí)行了_ticker后,_tick的值會(huì)被重置為0,因此只需要判斷_tick值是否被重置為0即可獲知_ticker是否被處理。
- private volatile long _tick = 0; //用于標(biāo)志_ticker是否被處理
- private volatile boolean _reported = false;
- private final Runnable _ticker = new Runnable() {
- @Override public void run() {//_ticker處理線程
- _tick = 0;//重置為初始值0,表示_ticker被處理
- _reported = false;
- }
- };
復(fù)制 在ANRWatchDog類的run()方法中,先通過_tick值判斷_ticker是否被發(fā)送給主線程,如果_tick的值為0則有兩種情況,一種是_tick的初始值為0,_ticker從未被發(fā)送給主線程;另一種是_ticker完成了一次或多次發(fā)送周期,且均被主線程處理,_tick被重置為0。在上述兩種情況下,需要將_tick值加上一段阻塞響應(yīng)時(shí)間后重新發(fā)送給主線程。
- @Override
- public void run() {//ANRWatchDog類的執(zhí)行過程
- setName("|ANR-WatchDog|");
- long interval = _timeoutInterval;
- while (!isInterrupted()) {
- boolean needPost = _tick == 0;//將“_tick是初始值0”賦給needPost
- _tick += interval;//_tick值加一個(gè)阻塞響應(yīng)時(shí)間
- if (needPost) {//判斷_tick是否為0
- _uiHandler.postTask(_ticker);//發(fā)送_ticker給主線程
- }
- ...}
如果_tick的值不為0,此時(shí)ANRWatchDog線程需要休眠一個(gè)阻塞響應(yīng)時(shí)間(對(duì)應(yīng)圖的1藍(lán)框中的Min ANR duration)。休眠結(jié)束后,繼續(xù)根據(jù)_tick的值可以判斷_ticker是否被處理,如果_tick被重置為0,則說明主線程處理了_ticker,主線程未阻塞;反之則說明主線程沒有處理_ticker,主線程阻塞,需要通過ANRError類拋出錯(cuò)誤信息(具體操作間ANRError類的介紹),并返回一個(gè)ANRError類的實(shí)例。
- try {
- Thread.sleep(interval);//ANRWatchDog線程休眠一個(gè)阻塞響應(yīng)時(shí)間
- } catch (InterruptedException e) {
- _interruptionListener.onInterrupted(e);
- return ;
- }
- if (_tick != 0 && !_reported) {//如果主線程沒有處理_ticker,則主線程阻塞
- ...
- final ANRError error;//聲明ANRError類
- if (_namePrefix != null) {//調(diào)用ANRError類的New()方法
- error = ANRError.New(_tick, _namePrefix, _logThreadsWithoutStackTrace);
- } else {//調(diào)用ANRError類NewMainOnly()方法
- error = ANRError.NewMainOnly(_tick);
- }
- }
隨后,調(diào)用ANRListener類onAppNotResponding()方法設(shè)置主線程阻塞后的響應(yīng)行為(對(duì)應(yīng)圖1藍(lán)框中的Behaviour)。
- _anrListener.onAppNotResponding(error); //響應(yīng)行為設(shè)置
2、 ANRError類
ANRError類繼承自Error類,主要用于拋出錯(cuò)誤信息,其有兩個(gè)重要的方法,分別是New()方法和NewMainOnly()方法。以下兩段代碼分別展示了兩個(gè)方法的具體邏輯。通過對(duì)比可發(fā)現(xiàn)這兩個(gè)方法的處理過程其實(shí)是類似的,核心都是先通過getMainEventRunner()方法獲取主線程mainThread ;再通過主線程得到堆棧信息mainStackTrace ,最后以mainThread和mainStackTrace作為參數(shù)實(shí)例化ANRError對(duì)象,并將該對(duì)象作為函數(shù)返回值。
NewMainOnly()方法
- static ANRError NewMainOnly(long duration) {
- final Thread mainThread = //通過getMainEventRunner()方法獲取到主線程findThread(EventRunner.getMainEventRunner().getThreadId());
- //獲取堆棧信息
- final StackTraceElement[] mainStackTrace = mainThread.getStackTrace();
- return new ANRError(new $(getThreadTitle(mainThread), mainStackTrace).new _Thread(null), duration);//返回重新構(gòu)造的ANRError實(shí)例
- }
New()方法
- static ANRError New(long duration, String prefix, boolean logThreadsWithoutStackTrace) {
- final Thread mainThread = //通過getMainEventRunner()方法獲取到主線程findThread(EventRunner.getMainEventRunner().getThreadId());
- final Map<Thread, StackTraceElement[]> stackTraces = new TreeMap<Thread, StackTraceElement[]>(new Comparator<Thread>() {@Override...});//獲取堆棧信息
- ...
- for (Map.Entry<Thread, StackTraceElement[]> entry : stackTraces.entrySet())
- tst = new $(getThreadTitle(entry.getKey()), entry.getValue()).new _Thread(tst);//重新構(gòu)造ANRError實(shí)例
- return new ANRError(tst, duration);//返回重新構(gòu)造的ANRError實(shí)例
- }
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)