监听上报ANR(Application Not Responding,应用无响应)是数据采集系统功能之一,本文讲述一种可行实现方案。
方案概述
ANR一般有三种类型[1]
:
- KeyDispatchTimeout(5 seconds) --主要类型按键或触摸事件在特定时间内无响应
- BroadcastTimeout(10 seconds) --BroadcastReceiver在特定时间内无法处理完成
- ServiceTimeout(20 seconds) --小概率类型 Service在特定的时间内无法处理完成
当应用发生ANR,Android系统会将ANR Log输出至/data/anr/traces.txt。traces.txt中log stack有固定格式(见以下示例代码[2]
)。本文的方案就是监听traces.txt文件,对traces.txt内容作解析,对解析结果进行存储和上报;
----- pid 30307 at 2015-05-30 14:51:14 -----
Cmd line: com.example.androidyue.bitmapdemo
JNI: CheckJNI is off; workarounds are off; pins=0; globals=272
DALVIK THREADS:
(mutexes: tll=0 tsl=0 tscl=0 ghl=0)
"main" prio=5 tid=1 TIMED_WAIT
| group="main" sCount=1 dsCount=0 obj=0x416eaf18 self=0x416d8650
| sysTid=30307 nice=0 sched=0/0 cgrp=apps handle=1074565528
| state=S schedstat=( 0 0 0 ) utm=5 stm=4 core=3
at java.lang.VMThread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:1044)
at java.lang.Thread.sleep(Thread.java:1026)
at com.example.androidyue.bitmapdemo.MainActivity$1.run(MainActivity.java:27)
at android.app.Activity.runOnUiThread(Activity.java:4794)
at com.example.androidyue.bitmapdemo.MainActivity.onResume(MainActivity.java:33)
at android.app.Instrumentation.callActivityOnResume(Instrumentation.java:1282)
at android.app.Activity.performResume(Activity.java:5405)
方案实现
监听文件在Android中有android.os.FileObserver[3]
实现方式。FileObserver是一个用于监听文件访问、创建、修改、删除、移动等操作的监听器。FileObserver是理想方案,但在测试过程中发现很多设备不支持FileObserver方式。为此本文同时提供一种线程轮询措施,用于辅助ANR监听。以下列出几个重要的实现类。
/**
* A Observer to detect traces.txt CLOSE WRITE event
*
*/
final class ANRFileObserver extends FileObserver {
private IDetectCallback mIDetectCallback;
public ANRFileObserver(String path, IDetectCallback callback) {
super(path, FileObserver.CLOSE_WRITE);
this.mIDetectCallback = callback;
}
@Override
public void onEvent(int event, String path) {
if (path == null) {
return;
}
path = "/data/anr/" + path;
if (path.contains("trace") && mIDetectCallback != null) {
mIDetectCallback.onANR(path, false,System.currentTimeMillis());
}
}
}
/**
* loop traces.txt in 500MS
*
*/
final class ANRLoopRunnable implements Runnable {
private long lastModified;
private IDetectCallback mIDetectCallback;
private static final String ANR_PATH = "/data/anr/traces.txt";
private int loop = 0;
public ANRLoopRunnable(IDetectCallback callback) {
mIDetectCallback = callback;
}
@Override
public void run() {
if (mIDetectCallback == null) {
return;
}
if (!exists()) {
mIDetectCallback.onFileNotExist();
return;
}
if (!canRead()) {
return;
}
lastModified = lastModified();
while (!interrupted()) {
long last = lastModified();
if (last != lastModified) {
loop++;
trySleep(last);
continue;
} else {
if (loop > 0) {
mIDetectCallback.onANR(ANR_PATH,true, System.currentTimeMillis());
break;
} else {
trySleep(last);
continue;
}
}
}
}
private void trySleep(long last) {
lastModified = last;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
/**
* 获取ANR文件最后一次更新时间
*
* @return
*/
private long lastModified() {
long time = 0L;
File localFile = new File(ANR_PATH);
if (localFile.exists()) {
time = localFile.lastModified();
}
return time;
}
private boolean canRead() {
boolean bool = false;
File localFile = new File(ANR_PATH);
if (localFile.exists()) {
bool = localFile.canRead();
}
return bool;
}
private boolean exists() {
File localFile = new File(ANR_PATH);
return localFile.exists();
}
}
/**
* An Item used to store traces info.`[4]`
*/
public class TracesItem {
/**
* Constant for output
*/
public static final String PID = "PID";
/**
* Constant for output
*/
public static final String APP = "APP";
/**
* Constant for output
*/
public static final String STACK = "STACK";
/**
* Constant for output
*/
public static final String STATE = "STATE";
private Map<String, Object> mAttributes = new HashMap<String, Object>();
/**
* Get the PID of the event.
*/
public Integer getPid() {
return (Integer) getAttribute(PID);
}
/**
* Set the PID of the event.
*/
public void setPid(Integer pid) {
setAttribute(PID, pid);
}
/**
* Get the reason of the event.
*/
public String getState() {
return (String) getAttribute(STATE);
}
/**
* Set the reason of the event.
*/
public void setState(String reason) {
setAttribute(STATE, reason);
}
/**
* Get the app or package name of the event.
*/
public String getApp() {
return (String) getAttribute(APP);
}
/**
* Set the app or package name of the event.
*/
public void setApp(String app) {
setAttribute(APP, app);
}
/**
* Get the stack for the crash.
*/
public String getStack() {
return (String) getAttribute(STACK);
}
/**
* Set the stack for the crash.
*/
public void setStack(String stack) {
setAttribute(STACK, stack);
}
/**
* Set an attribute to a value.
*
* @param attribute The name of the attribute.
* @param value The value.
* @throws IllegalArgumentException If the attribute is not in allowedAttributes.
*/
protected void setAttribute(String attribute, Object value) throws IllegalArgumentException {
mAttributes.put(attribute, value);
}
/**
* Get the value of an attribute.
*
* @param attribute The name of the attribute.
* @return The value or null if the attribute has not been set.
* @throws IllegalArgumentException If the attribute is not in allowedAttributes.
*/
protected Object getAttribute(String attribute) throws IllegalArgumentException {
return mAttributes.get(attribute);
}
}
/**
* A Parser to parse Android traces files. `[4]`
*/
public class TracesParser {
/**
* Matches: ----- pid PID at YYYY-MM-DD hh:mm:ss -----
*/
private static final Pattern PID = Pattern.compile(
"^----- pid (\\d+) at \\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2} -----$");
/**
* Matches: Cmd line: APP
*/
private static final Pattern APP = Pattern.compile("^Cmd ?line: (\\S+).*$");
/**
* Matches: "main" prio=5 tid=1 STATE
*/
private static final Pattern STACK = Pattern.compile("^\"main\" (prio=\\d+) (tid=\\d+) (.*)");
/**
* parse traces.txt
*
* @param path traces.txt
* @return
*/
public static TracesItem parse(String path) {
BufferedReader br = null;
FileReader fr = null;
TracesItem traces = new TracesItem();
StringBuffer stack = null;
String state = null;
try {
fr = new FileReader(path);
br = new BufferedReader(fr);
String sCurrentLine;
br = new BufferedReader(new FileReader(path));
while ((sCurrentLine = br.readLine()) != null) {
if (state == null) {
Matcher m = PID.matcher(sCurrentLine);
if (m.matches()) {
traces.setPid(Integer.parseInt(m.group(1)));
}
m = APP.matcher(sCurrentLine);
if (m.matches()) {
traces.setApp(m.group(1));
}
m = STACK.matcher(sCurrentLine);
if (m.matches()) {
state = m.group(3);
traces.setState(state);
}
} else if (!"".equals(sCurrentLine)) {
if (stack == null) {
stack = new StringBuffer();
}
stack.append(sCurrentLine);
stack.append("\n");
} else {
traces.setStack(stack.toString().trim());
return traces;
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (br != null)
br.close();
if (fr != null)
fr.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
if (stack == null) {
return null;
}
traces.setStack(stack.toString().trim());
return traces;
}
}
引用:
[1] ANR简介以及解决方案 : http://www.snowdream.tech/201...
[2] 说说Android中的ANR:http://droidyue.com/blog/2015...
[3] FileObserver类:https://developer.android.com...
[4] loganalysis:https://android.googlesource....