1. 可能导致 anr的情景:activity, service, provice, broadcast都可能产生anr
具体产生的策略这里没有关注,这里只关注发生 anr后的代码流程,也就是关注appNotResponding函数
/**
* Handle input dispatching timeouts.
* Returns whether input dispatching should be aborted or not.
*/
inputDispatch:
public boolean inputDispatchingTimedOut(final ProcessRecord proc,
final ActivityRecord activity, final ActivityRecord parent,
final boolean aboveSystem, String reason) {
mHandler.post(new Runnable() {
@Override
public void run() {
appNotResponding(proc, activity, parent, aboveSystem, annotation);
}
});
}
Provider:
public void appNotRespondingViaProvider(IBinder connection) {
enforceCallingPermission(
android.Manifest.permission.REMOVE_TASKS, "appNotRespondingViaProvider()");
final ContentProviderConnection conn = (ContentProviderConnection) connection;
if (conn == null) {
Slog.w(TAG, "ContentProviderConnection is null");
return;
}
final ProcessRecord host = conn.provider.proc;
if (host == null) {
Slog.w(TAG, "Failed to find hosting ProcessRecord");
return;
}
final long token = Binder.clearCallingIdentity();
try {
appNotResponding(host, null, null, false, "ContentProvider not responding");
} finally {
Binder.restoreCallingIdentity(token);
}
}
service:
void serviceTimeout(ProcessRecord proc) {
String anrMessage = null;
synchronized(mAm) {
if (proc.executingServices.size() == 0 || proc.thread == null) {
return;
}
if (timeout != null && mAm.mLruProcesses.contains(proc)) {
Slog.w(TAG, "Timeout executing service: " + timeout);
StringWriter sw = new StringWriter();
PrintWriter pw = new FastPrintWriter(sw, false, 1024);
pw.println(timeout);
timeout.dump(pw, " ");
pw.close();
mLastAnrDump = sw.toString();
mAm.mHandler.removeCallbacks(mLastAnrDumpClearer);
mAm.mHandler.postDelayed(mLastAnrDumpClearer, LAST_ANR_LIFETIME_DURATION_MSECS);
anrMessage = "executing service " + timeout.shortName;
} else {
Message msg = mAm.mHandler.obtainMessage(
ActivityManagerService.SERVICE_TIMEOUT_MSG);
msg.obj = proc;
mAm.mHandler.sendMessageAtTime(msg, proc.execServicesFg
? (nextTime+SERVICE_TIMEOUT) : (nextTime + SERVICE_BACKGROUND_TIMEOUT));
}
}
if (anrMessage != null) {
mAm.appNotResponding(proc, null, null, false, anrMessage);
}
}
Broadcast:
final void broadcastTimeoutLocked(boolean fromMsg) {
mHandler.post(new AppNotResponding(app, anrMessage));
}
/************************************************************************************/
2. appNotResponding代码流程:
final void appNotResponding(ProcessRecord app, ActivityRecord activity,
ActivityRecord parent, boolean aboveSystem, final String annotation) {
ArrayList
firstPids = new ArrayList(5);
SparseArray lastPids = new SparseArray(20);
long anrTime = SystemClock.uptimeMillis();
if (MONITOR_CPU_USAGE) {
updateCpuStatsNow();
}
// In case we come through here for the same app before completing
// this one, mark as anring now so we will bail out.
app.notResponding = true;
// Log the ANR to the event log.
EventLog.writeEvent(EventLogTags.AM_ANR, app.userId, app.pid,
app.processName, app.info.flags, annotation);
// Dump thread traces as quickly as we can, starting with "interesting" processes.
firstPids.add(app.pid);
// Log the ANR to the main log.
StringBuilder info = new StringBuilder();
info.setLength(0);
info.append("ANR in ").append(app.processName);
if (activity != null && activity.shortComponentName != null) {
info.append(" (").append(activity.shortComponentName).append(")");
}
info.append("\n");
info.append("PID: ").append(app.pid).append("\n");
if (annotation != null) {
info.append("Reason: ").append(annotation).append("\n");
}
if (parent != null && parent != activity) {
info.append("Parent: ").append(parent.shortComponentName).append("\n");
}
String cpuInfo = null;
if (MONITOR_CPU_USAGE) {
updateCpuStatsNow();
synchronized (mProcessCpuTracker) {
cpuInfo = mProcessCpuTracker.printCurrentState(anrTime);
}
info.append(processCpuTracker.printCurrentLoad());
info.append(cpuInfo);
}
info.append(processCpuTracker.printCurrentState(anrTime));
Slog.e(TAG, info.toString());
File tracesFile = dumpStackTraces(true, firstPids, processCpuTracker, lastPids,
NATIVE_STACKS_OF_INTEREST);
addErrorToDropBox("anr", app, app.processName, activity, parent, annotation,
cpuInfo, tracesFile, null);
//Set the trace file name to app name + current date format to avoid overrinding trace file
String tracesPath = SystemProperties.get("dalvik.vm.stack-trace-file", null);
if (tracesPath != null && tracesPath.length() != 0) {
File traceRenameFile = new File(tracesPath);
String newTracesPath;
int lpos = tracesPath.lastIndexOf (".");
if (-1 != lpos)
newTracesPath = tracesPath.substring (0, lpos) + "_" + app.processName + "_" + mTraceDateFormat.format(new Date()) + tracesPath.substring (lpos);
else
newTracesPath = tracesPath + "_" + app.processName;
traceRenameFile.renameTo(new File(newTracesPath));
}
// Bring up the infamous App Not Responding dialog
Message msg = Message.obtain();
HashMap map = new HashMap();
msg.what = SHOW_NOT_RESPONDING_MSG;
msg.obj = map;
msg.arg1 = aboveSystem ? 1 : 0;
map.put("app", app);
if (activity != null) {
map.put("activity", activity);
}
mUiHandler.sendMessage(msg);
}
3. ANR是logcat输出:
1398 1445 E ActivityManager: ANR in com.example.ritter.fcandanr (com.example.ritter.fcandanr/.MainActivity)
1398 1445 E ActivityManager: PID: 5639
1398 1445 E ActivityManager: Reason: Input dispatching timed out (Waiting because the touched window's input channel is full. Outbound queue length: 1. Wait queue length: 52.)
1398 1445 E ActivityManager: Load: 5.79 / 5.61 / 5.5
1398 1445 E ActivityManager: CPU usage from 30465ms to 0ms ago:
1398 1445 E ActivityManager: 13% 1398/system_server: 5.9% user + 7.2% kernel / faults: 1567 minor 3 major
1398 1445 E ActivityManager: 2% 603/sensors.qcom: 0.6% user + 1.3% kernel / faults: 6 minor
1398 1445 E ActivityManager: 1.4% 5938/kworker/u8:8: 0% user + 1.4% kernel
logcat -b events输出:
am_anr : [0,5639,com.example.ritter.fcandanr,948485702,Input dispatching timed out (Waiting because the touched window's input channel is full. Outbound queue length: 1. Wait queue length: 52.)]
4. dumpStackTraces
4.1 创建文件和文件夹
/**
* If a stack trace dump file is configured, dump process stack traces.
* @param clearTraces causes the dump file to be erased prior to the new
* traces being written, if true; when false, the new traces will be
* appended to any existing file content.
* @param firstPids of dalvik VM processes to dump stack traces for first
* @param lastPids of dalvik VM processes to dump stack traces for last
* @param nativeProcs optional list of native process names to dump stack crawls
* @return file containing stack traces, or null if no dump file is configured
*/
public static File dumpStackTraces(boolean clearTraces, ArrayList firstPids,
ProcessCpuTracker processCpuTracker, SparseArray lastPids, String[] nativeProcs) {
String tracesPath = SystemProperties.get("dalvik.vm.stack-trace-file", null);
if (tracesPath == null || tracesPath.length() == 0) {
return null;
}
File tracesFile = new File(tracesPath);
try {
File tracesDir = tracesFile.getParentFile();
if (!tracesDir.exists()) {
tracesDir.mkdirs();
if (!SELinux.restorecon(tracesDir)) {
return null;
}
}
FileUtils.setPermissions(tracesDir.getPath(), 0775, -1, -1); // drwxrwxr-x
if (clearTraces && tracesFile.exists()) tracesFile.delete();
tracesFile.createNewFile();
FileUtils.setPermissions(tracesFile.getPath(), 0666, -1, -1); // -rw-rw-rw-
} catch (IOException e) {
Slog.w(TAG, "Unable to prepare ANR traces file: " + tracesPath, e);
return null;
}
dumpStackTraces(tracesPath, firstPids, processCpuTracker, lastPids, nativeProcs);
return tracesFile;
}
4.2: Process.sendSignal(firstPids.get(i), Process.SIGNAL_QUIT) and observer.wait(200)
private static void dumpStackTraces(String tracesPath, ArrayList firstPids,
ProcessCpuTracker processCpuTracker, SparseArray lastPids, String[] nativeProcs) {
// Use a FileObserver to detect when traces finish writing.
// The order of traces is considered important to maintain for legibility.
FileObserver observer = new FileObserver(tracesPath, FileObserver.CLOSE_WRITE) {
@Override
public synchronized void onEvent(int event, String path) { notify(); }
};
try {
observer.startWatching();
// First collect all of the stacks of the most important pids.
if (firstPids != null) {
try {
int num = firstPids.size();
for (int i = 0; i < num; i++) {
synchronized (observer) {
Process.sendSignal(firstPids.get(i), Process.SIGNAL_QUIT);
observer.wait(200); // Wait for write-close, give up after 200msec
}
}
} catch (InterruptedException e) {
Slog.wtf(TAG, e);
}
}
// Next collect the stacks of the native pids
if (nativeProcs != null) {
int[] pids = Process.getPidsForCommands(nativeProcs);
if (pids != null) {
for (int pid : pids) {
Debug.dumpNativeBacktraceToFile(pid, tracesPath);
}
}
}
// Lastly, measure CPU usage.
if (processCpuTracker != null) {
processCpuTracker.init();
System.gc();
processCpuTracker.update();
try {
synchronized (processCpuTracker) {
processCpuTracker.wait(500); // measure over 1/2 second.
}
} catch (InterruptedException e) {
}
processCpuTracker.update();
// We'll take the stack crawls of just the top apps using CPU.
final int N = processCpuTracker.countWorkingStats();
int numProcs = 0;
for (int i=0; i ProcessCpuTracker.Stats stats = processCpuTracker.getWorkingStats(i);
if (lastPids.indexOfKey(stats.pid) >= 0) {
numProcs++;
try {
synchronized (observer) {
Process.sendSignal(stats.pid, Process.SIGNAL_QUIT);
observer.wait(200); // Wait for write-close, give up after 200msec
}
} catch (InterruptedException e) {
Slog.wtf(TAG, e);
}
}
}
}
} finally {
observer.stopWatching();
}
}
5. addErrorToDropBox("anr", app, app.processName, activity, parent, annotation,
cpuInfo, tracesFile, null);
是怎样编程压缩文件的?
// First, accumulate up to one block worth of data in memory before
// deciding whether to compress the data or not.
[email protected]
/**
* Stores human-readable text. The data may be discarded eventually (or even
* immediately) if space is limited, or ignored entirely if the tag has been
* blocked (see {@link #isTagEnabled}).
*
* @param tag describing the type of entry being stored
* @param data value to store
*/
public void addText(String tag, String data) {
try { mService.add(new Entry(tag, 0, data)); } catch (RemoteException e) {}
}
/** Create a new Entry with plain text contents. */
public Entry(String tag, long millis, String text) {
if (tag == null) throw new NullPointerException("tag == null");
if (text == null) throw new NullPointerException("text == null");
mTag = tag;
mTimeMillis = millis;
mData = text.getBytes();
mFileDescriptor = null;
mFlags = IS_TEXT;
}
/** @return the uncompressed contents of the entry, or null if the contents were lost */
public InputStream getInputStream() throws IOException {
InputStream is;
if (mData != null) {
is = new ByteArrayInputStream(mData);
} else if (mFileDescriptor != null) {
is = new ParcelFileDescriptor.AutoCloseInputStream(mFileDescriptor);
} else {
return null;
}
return (mFlags & IS_GZIPPED) != 0 ? new GZIPInputStream(is) : is;
}
public void add(DropBoxManager.Entry entry) {
File temp = null;
OutputStream output = null;
final String tag = entry.getTag();
try {
int flags = entry.getFlags();
if ((flags & DropBoxManager.IS_EMPTY) != 0) throw new IllegalArgumentException();
init();
if (!isTagEnabled(tag)) return;
long max = trimToFit();
long lastTrim = System.currentTimeMillis();
byte[] buffer = new byte[mBlockSize];
InputStream input = entry.getInputStream();
// First, accumulate up to one block worth of data in memory before
// deciding whether to compress the data or not.
int read = 0;
while (read < buffer.length) {
int n = input.read(buffer, read, buffer.length - read);
if (n <= 0) break;
read += n;
}
// If we have at least one block, compress it -- otherwise, just write
// the data in uncompressed form.
temp = new File(mDropBoxDir, "drop" + Thread.currentThread().getId() + ".tmp");
int bufferSize = mBlockSize;
if (bufferSize > 4096) bufferSize = 4096;
if (bufferSize < 512) bufferSize = 512;
FileOutputStream foutput = new FileOutputStream(temp);
output = new BufferedOutputStream(foutput, bufferSize);
if (read == buffer.length && ((flags & DropBoxManager.IS_GZIPPED) == 0)) {
output = new GZIPOutputStream(output);
flags = flags | DropBoxManager.IS_GZIPPED;
}
}
6. 更改 /data/anr/trace.txt 的文件名:更改文件名中包含process name:可以从中提取出来
//Set the trace file name to app name + current date format to avoid overrinding trace file
String tracesPath = SystemProperties.get("dalvik.vm.stack-trace-file", null);
if (tracesPath != null && tracesPath.length() != 0) {
File traceRenameFile = new File(tracesPath);
String newTracesPath;
int lpos = tracesPath.lastIndexOf (".");
if (-1 != lpos)
newTracesPath = tracesPath.substring (0, lpos) + "_" + app.processName + "_" + mTraceDateFormat.format(new Date()) + tracesPath.substring (lpos);
else
newTracesPath = tracesPath + "_" + app.processName;
traceRenameFile.renameTo(new File(newTracesPath));
}
7. /data/anr/traces.txt文件的内容
和/data/system/dropbox的压缩文件[email protected]
的内容是不同的:
/data/anr/traces.txt是 java进程下各个线程的栈;可能不只是当前进程;
dropbox中还有其他的内容如:cpu占用率等和logcat的比较像,也包含部分栈信息(可能有大小的限制)