概述:
當(dāng)Android應(yīng)用的UI線程被阻塞太久時(shí),就會(huì)觸發(fā)一個(gè)”Application Not Responding“(ANR)錯(cuò)誤院塞。如果APP運(yùn)行在前臺(tái)遮晚,系統(tǒng)就會(huì)彈出一個(gè)提示框,告知用戶拦止,用戶可以選擇繼續(xù)等待或者強(qiáng)制關(guān)掉县遣。如果app處于后臺(tái)則會(huì)直接被系統(tǒng)kill掉糜颠。
ANR的原因
ANR是因?yàn)樨?fù)責(zé)更新UI的主線程無(wú)法處理用戶輸入事件或繪制操作,而導(dǎo)致的糟糕體驗(yàn)萧求。
在Android中民傻,程序的響應(yīng)性是由Activity Manager與Window Manager系統(tǒng)服務(wù)來(lái)負(fù)責(zé)監(jiān)控的伸但,當(dāng)系統(tǒng)檢測(cè)到下面的條件之一時(shí)會(huì)顯示ANR的對(duì)話框:
(1) 對(duì)輸入事件(例如硬件點(diǎn)擊或者屏幕觸摸事件)害幅,5秒內(nèi)都無(wú)響應(yīng)赃绊。
(2) 前臺(tái)BroadcastReceiver不能在10秒內(nèi)結(jié)束接收到的任務(wù)。
ANR的觸發(fā)場(chǎng)景
(1) 在主線程執(zhí)行耗時(shí)的IO操作秒梳。
(2) 在主線程執(zhí)行耗時(shí)的計(jì)算法绵。
(3) 在主線程與其他進(jìn)程進(jìn)行同步的binder調(diào)用箕速,并且另一個(gè)進(jìn)程需要很長(zhǎng)時(shí)間才能返回酪碘。
(4) 主線程因等待其他線程的同步鎖( synchronized)而被長(zhǎng)時(shí)間阻塞。
(5) 主線程與另一個(gè)線程處于死鎖狀態(tài)盐茎。
檢測(cè)ANR
(1) Strict mode,使用 StrictMode可以幫助你在開(kāi)發(fā)的過(guò)程中發(fā)現(xiàn)在主線程意外的IO操作兴垦。
可以在Application、Activity或者其他應(yīng)用組件進(jìn)行配置:
public void onCreate() {
if (DEVELOPER_MODE) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .detectDiskReads()
.detectDiskWrites()
.detectNetwork() // or .detectAll() for all detectable problems .penaltyLog()
.build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() .detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects()
.penaltyLog()
.penaltyDeath().build());
}
super.onCreate();}
(2) 允許后臺(tái)ANR彈窗
默認(rèn)情況下字柠,Android只顯示前臺(tái)ANR彈窗探越,如果需要允許顯示后臺(tái)ANR彈窗,就要到開(kāi)發(fā)者選項(xiàng)窑业,開(kāi)啟”Show all ANRs“钦幔。
(3)TraceView
使用Traceview去跟蹤正在運(yùn)行的應(yīng)用,并定位主線程忙碌的位置
(4)分析traces日志文件
當(dāng)發(fā)生ANR常柄,Android系統(tǒng)會(huì)存儲(chǔ)日志文件鲤氢。
日志路徑:
舊版系統(tǒng): /data/anr/traces.txt
新版系統(tǒng): /data/anr/anr_*
Android新系統(tǒng)因?yàn)樾枰猺oot權(quán)限所以會(huì)報(bào)錯(cuò)
remote open failed: Permission denied
如果沒(méi)有root可以使用一下方法導(dǎo)出全量文件:
adb bugreport
按照對(duì)應(yīng)路徑找到anr文件即可.
(5)如何避免ANR
(1)在工作線程中,執(zhí)行耗時(shí)操作西潘,如網(wǎng)絡(luò)卷玉、DB操作或者Bitmap大小調(diào)整的操作。
(2)使用AsyncTask來(lái)執(zhí)行耗時(shí)操作喷市。
(3)使用線程或者HandlerThread相种,要通過(guò) Process.setThreadPriority()并傳遞 THREAD_PRIORITY_BACKGROUND來(lái)設(shè)置線程的優(yōu)先級(jí)為”background“,不然這個(gè)線程仍然會(huì)使得你的應(yīng)用顯得卡頓品姓,因?yàn)楣ぷ骶€程默認(rèn)與UI線程有著同樣的優(yōu)先級(jí)寝并。
(4)避免在BroadcastReceiver中執(zhí)行耗時(shí)操作,如保存數(shù)據(jù)或者注冊(cè)一個(gè)Notification腹备。不能通過(guò)工作線程來(lái)執(zhí)行復(fù)雜的任務(wù)操作衬潦,而應(yīng)該啟動(dòng)一個(gè) IntentService來(lái)執(zhí)行耗時(shí)任務(wù)來(lái)響應(yīng)BroadcastReceiver中的長(zhǎng)時(shí)間任務(wù)。
1.traces文件
traces.txt系統(tǒng)自動(dòng)生成的記錄anr等異常的文件馏谨,只記錄java代碼產(chǎn)生的異常别渔。
2.traces文件的獲取
2.1 如果手機(jī)已經(jīng)是完全root了的或者是模擬器,可以直接通過(guò)DDMS的File Explorer直接導(dǎo)出來(lái),目錄在data/anr/traces.txt下。
2.2 如果不是root的手機(jī)哎媚,可以通過(guò)如下adb命令查看ANR日志文件位于哪里喇伯。
adb shell ls /data/anr/
當(dāng)發(fā)生ANR,Android系統(tǒng)會(huì)存儲(chǔ)日志文件拨与。
日志路徑:
舊版系統(tǒng): /data/anr/traces.txt
新版系統(tǒng): /data/anr/anr_*
然后通過(guò)adb的pull將日志文件拉取到指定的路徑稻据。
adb pull /data/anr/traces.txt d:/
但是如果手機(jī)沒(méi)有進(jìn)行root,執(zhí)行adb pull命令就會(huì)出現(xiàn)如下提示:
remote object ‘/data/anr/traces.txt’ does not exist
這時(shí)候我們可以使用adb將文件copy一份到sdcard买喧,然后再拉取出來(lái):
adb shell
cat /data/anr/traces.txt >/mnt/sdcard/traces.txt
exit
然后可以再?gòu)膕d卡考到項(xiàng)目目錄:
D:\demos\X5WebDemo>adb pull /mnt/sdcard/traces.txt ./traces.txt
3.線上用戶ANR信息捕獲方案
- Bugly監(jiān)控ANR異常方案捻悯,是通過(guò)FileObserver監(jiān)聽(tīng)trace文件寫(xiě)實(shí)現(xiàn)的。
2.當(dāng)ANR發(fā)生的時(shí)候淤毛,是通過(guò)監(jiān)聽(tīng)文件夾“data/anr/”的寫(xiě)入情況,來(lái)判斷是否發(fā)生了ANR今缚,如果監(jiān)聽(tīng)
到data/anr/traces.txt文件寫(xiě)入。說(shuō)明有此時(shí)有ANR異常發(fā)生然后將traces.txt文件上傳到服務(wù)器即可低淡。注意由于系統(tǒng)不同data/anr目錄下的文件可能有多個(gè)姓言,每個(gè)app發(fā)生anr信息時(shí)都會(huì)保存到該目錄,所以最終上傳的anr信息需要根據(jù)當(dāng)前進(jìn)程信息過(guò)濾掉非本應(yīng)用下的anr異常蔗蹋,具體可參考Bugly實(shí)現(xiàn)細(xì)節(jié)何荚。
3.FileObserver捕獲ANR異常,缺點(diǎn)是Android5.0低權(quán)限應(yīng)用不能監(jiān)聽(tīng)變化“、data/anr/traces.txt”猪杭,只能在root之后才可以餐塘。
// 偽代碼實(shí)現(xiàn)anr監(jiān)聽(tīng),上傳
private void startWatching() {
final String anrFile = "data/anr/traces.txt"
observer = new RecursiveFileObserver(anrFile, FileObserver.CLOSE_WRITE| FileObserver.MOVED_TO) { // set up a file observer to watch this directory on sd card
@Override
public void onEvent(int event, final String anrFile) {
//1.開(kāi)啟工作線程上傳anrFile路徑文件到服務(wù)端皂吮,供問(wèn)題排查
·······
//2.更新ui顯示
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
//這里更新ui信息
}
});
}
}
};
observer.startWatching();
}