前言
?ANR是Application Not Responding的縮寫,即應用程序無響應。簡單來說,就是應用的界面突然卡住了,無法響應用戶的操作如觸摸事件等。
優化思路
1 ANR執行流程
- 發生ANR
- 進程接收異常終止信號,開始寫入進程ANR信息
- 彈出ANR提示框(Rom表現不一,有可能不彈)
2 如何解決ANR
?解決ANR問題,首先要做的是找到問題,線下我們可以通過ADB命令導出ANR文件進行分析,線上我們可以使用FileObserver或ANR-WatchDog保存ANR堆棧信息,然后上傳到服務器。
2.1導出ANR文件
?ANR發生之后我們可以使用以下命令導出ANR文件:
adb pull data/anr/traces.txt 存儲路徑
或者
//生成
adb bugreport
//導出
adb pull 文件路徑 存儲路徑
生成bugreport
導出bugreport
2.2 ANR文件分析
?使用命令會導出一個zip壓縮包,ANR文件在FS/data/anr目錄下。
ANR文件
?我的實例是在MainActivity的第41行做了個Thread.sleep(20*1000)的操作。
ANR文件分析
2.3 線上ANR監控方案
?線上監控方案我們可以實現
FileObserver
去監聽ANR目錄的變化和使用ANR-WatchDod
去監聽并打印ANR堆棧信息。FileObserver
注意事項:
- FileOberver需要三種權限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<!--在sdcard中創建/刪除文件的權限 -->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"></uses-permission>
- FileOberver的startWatching、stopWatching都加鎖,因此我們要避免在對象鎖中實現,以免造成死鎖。
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是一個非侵入式的ANR監控組件。
使用步驟:
- 在app/build.gradle添加依賴
implementation 'com.github.anrwatchdog:anrwatchdog:1.4.0'
- 在application類的onCreate中使用,即可實現自動監測ANR。
class MyApplication: Application() {
override fun onCreate() {
super.onCreate()
ANRWatchDog().start();
}
}
ANR發生之后可直接在日志中查看堆棧信息:
?也可以在Application中監聽ANR-WatchDog返回的錯誤日志。
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
BlockCanary.install(this, AppBlockCanaryContext()).start()
val anrWatchDog = ANRWatchDog()
anrWatchDog.setANRListener {
}
anrWatchDog.start()
}
}
原理
?ANRWatchDog繼承子Thread,所以它最重要的就是run方法。核心內容可以分為以下幾點:
- 在run方法中實現無限循環
-
_tick自動加上5000毫秒
并往Handler發消息 - 睡眠5000毫秒
- 如果5000毫秒之內主線程還沒有處理Runnable,將_tick置0,則進入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 無限循環
while (!isInterrupted()) {
boolean needPost = _tick == 0;
//2 _tick自增
_tick += interval;
if (needPost) {
//3 發送消息
_uiHandler.post(_ticker);
}
try {
//4 睡眠
Thread.sleep(interval);
} catch (InterruptedException e) {
_interruptionListener.onInterrupted(e);
return ;
}
//5 如果UI線程沒有處理_ticker, 走下面代碼塊,進入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;
}
}
}
}
總結
?ANR異常我們可分為線上監測和線下監測兩個方向
- 線上監測主要是利用FileObserver進行ANR目錄文件變化監聽,以ANR-WatchDog進行補充。
- FileObserver在使用過程中應注意高版本程序不可用以及預防死鎖出現。
- 線下監測主要是在報錯之后利用ADB命令將錯誤的日志導出并找到錯誤的類進行分析。