前言
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命令将错误的日志导出并找到错误的类进行分析。
网友评论