前言
?ANR是Application Not Responding的縮寫很洋,即應(yīng)用程序無響應(yīng)喉磁。簡(jiǎn)單來說,就是應(yīng)用的界面突然卡住了娜谊,無法響應(yīng)用戶的操作如觸摸事件等斤讥。
優(yōu)化思路
1 ANR執(zhí)行流程
- 發(fā)生ANR
- 進(jìn)程接收異常終止信號(hào)芭商,開始寫入進(jìn)程ANR信息
- 彈出ANR提示框(Rom表現(xiàn)不一,有可能不彈)
2 如何解決ANR
?解決ANR問題近迁,首先要做的是找到問題鉴竭,線下我們可以通過ADB命令導(dǎo)出ANR文件進(jìn)行分析搏存,線上我們可以使用FileObserver或ANR-WatchDog保存ANR堆棧信息矢洲,然后上傳到服務(wù)器。
2.1導(dǎo)出ANR文件
?ANR發(fā)生之后我們可以使用以下命令導(dǎo)出ANR文件:
adb pull data/anr/traces.txt 存儲(chǔ)路徑
或者
//生成
adb bugreport
//導(dǎo)出
adb pull 文件路徑 存儲(chǔ)路徑
2.2 ANR文件分析
?使用命令會(huì)導(dǎo)出一個(gè)zip壓縮包,ANR文件在FS/data/anr目錄下灾螃。
?我的實(shí)例是在MainActivity的第41行做了個(gè)Thread.sleep(20*1000)的操作腰鬼。
2.3 線上ANR監(jiān)控方案
?線上監(jiān)控方案我們可以實(shí)現(xiàn)
FileObserver
去監(jiān)聽ANR目錄的變化和使用ANR-WatchDod
去監(jiān)聽并打印ANR堆棧信息。FileObserver
注意事項(xiàng):
- FileOberver需要三種權(quán)限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<!--在sdcard中創(chuàng)建/刪除文件的權(quán)限 -->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"></uses-permission>
- FileOberver的startWatching本谜、stopWatching都加鎖偎窘,因此我們要避免在對(duì)象鎖中實(shí)現(xiàn)陌知,以免造成死鎖。
public int[] startWatching(List<File> files,
@NotifyEventType int mask, FileObserver observer) {
final int count = files.size();
final String[] paths = new String[count];
for (int i = 0; i < count; ++i) {
paths[i] = files.get(i).getAbsolutePath();
}
final int[] wfds = new int[count];
Arrays.fill(wfds, -1);
startWatching(m_fd, paths, mask, wfds);
final WeakReference<FileObserver> fileObserverWeakReference = new WeakReference<>(observer);
synchronized (m_observers) {
for (int wfd : wfds) {
if (wfd >= 0) {
m_observers.put(wfd, fileObserverWeakReference);
}
}
}
return wfds;
}
?使用方法:
@SuppressLint("NewApi")
fun startANRListener(){
val fileObserver = object :FileObserver(File("/data/anr/"), CLOSE_WRITE){
override fun onEvent(event: Int, path: String?) {
Log.d("ANRWatching", "/data/anr/$path")
}
}
fileObserver.startWatching()
}
ANR-WatchDog
?Git地址:ANR-WatchDog
?ANR-WatchDog是一個(gè)非侵入式的ANR監(jiān)控組件。
使用步驟:
- 在app/build.gradle添加依賴
implementation 'com.github.anrwatchdog:anrwatchdog:1.4.0'
- 在application類的onCreate中使用腰涧,即可實(shí)現(xiàn)自動(dòng)監(jiān)測(cè)ANR。
class MyApplication: Application() {
override fun onCreate() {
super.onCreate()
ANRWatchDog().start();
}
}
ANR發(fā)生之后可直接在日志中查看堆棧信息:
?也可以在Application中監(jiān)聽ANR-WatchDog返回的錯(cuò)誤日志疗锐。
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
BlockCanary.install(this, AppBlockCanaryContext()).start()
val anrWatchDog = ANRWatchDog()
anrWatchDog.setANRListener {
}
anrWatchDog.start()
}
}
原理
?ANRWatchDog繼承子Thread滑臊,所以它最重要的就是run方法简珠。核心內(nèi)容可以分為以下幾點(diǎn):
- 在run方法中實(shí)現(xiàn)無限循環(huán)
-
_tick自動(dòng)加上5000毫秒
并往Handler發(fā)消息 - 睡眠5000毫秒
- 如果5000毫秒之內(nèi)主線程還沒有處理Runnable聋庵,將_tick置0,則進(jìn)入ANR異常氧映。
public class ANRWatchDog extends Thread {
private static final int DEFAULT_ANR_TIMEOUT = 5000;
private final Handler _uiHandler = new Handler(Looper.getMainLooper());
private final int _timeoutInterval;
private volatile long _tick = 0;
private volatile boolean _reported = false;
private final Runnable _ticker = new Runnable() {
@Override public void run() {
// _tick = 0用于 第5步判斷
_tick = 0;
_reported = false;
}
};
/**
* Constructs a watchdog that checks the ui thread every {@value #DEFAULT_ANR_TIMEOUT} milliseconds
*/
public ANRWatchDog() {
this(DEFAULT_ANR_TIMEOUT);
}
/**
* Constructs a watchdog that checks the ui thread every given interval
*
* @param timeoutInterval The interval, in milliseconds, between to checks of the UI thread.
* It is therefore the maximum time the UI may freeze before being reported as ANR.
*/
public ANRWatchDog(int timeoutInterval) {
super();
_timeoutInterval = timeoutInterval;
}
@SuppressWarnings("NonAtomicOperationOnVolatileField")
@Override
public void run() {
setName("|ANR-WatchDog|");
long interval = _timeoutInterval;
//1 無限循環(huán)
while (!isInterrupted()) {
boolean needPost = _tick == 0;
//2 _tick自增
_tick += interval;
if (needPost) {
//3 發(fā)送消息
_uiHandler.post(_ticker);
}
try {
//4 睡眠
Thread.sleep(interval);
} catch (InterruptedException e) {
_interruptionListener.onInterrupted(e);
return ;
}
//5 如果UI線程沒有處理_ticker, 走下面代碼塊,進(jìn)入ANR異常臼疫。
if (_tick != 0 && !_reported) {
//noinspection ConstantConditions
if (!_ignoreDebugger && (Debug.isDebuggerConnected() || Debug.waitingForDebugger())) {
Log.w("ANRWatchdog", "An ANR was detected but ignored because the debugger is connected (you can prevent this with setIgnoreDebugger(true))");
_reported = true;
continue ;
}
interval = _anrInterceptor.intercept(_tick);
if (interval > 0) {
continue;
}
final ANRError error;
if (_namePrefix != null) {
error = ANRError.New(_tick, _namePrefix, _logThreadsWithoutStackTrace);
} else {
error = ANRError.NewMainOnly(_tick);
}
_anrListener.onAppNotResponding(error);
interval = _timeoutInterval;
_reported = true;
}
}
}
}
總結(jié)
?ANR異常我們可分為線上監(jiān)測(cè)和線下監(jiān)測(cè)兩個(gè)方向
- 線上監(jiān)測(cè)主要是利用FileObserver進(jìn)行ANR目錄文件變化監(jiān)聽烫堤,以ANR-WatchDog進(jìn)行補(bǔ)充凤价。
- FileObserver在使用過程中應(yīng)注意高版本程序不可用以及預(yù)防死鎖出現(xiàn)利诺。
- 線下監(jiān)測(cè)主要是在報(bào)錯(cuò)之后利用ADB命令將錯(cuò)誤的日志導(dǎo)出并找到錯(cuò)誤的類進(jìn)行分析慢逾。