本文共 13286 字,大约阅读时间需要 44 分钟。
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<Integer> firstPids = new ArrayList<Integer>(5); SparseArray<Boolean> lastPids = new SparseArray<Boolean>(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<String, Object> map = new HashMap<String, Object>(); 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<Integer> firstPids, ProcessCpuTracker processCpuTracker, SparseArray<Boolean> 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<Integer> firstPids, ProcessCpuTracker processCpuTracker, SparseArray<Boolean> 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<N && numProcs<5; 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. data_app_anr@1482834214980.txt.gz /** * 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的压缩文件data_app_anr@1482909385649.txt.gz 的内容是不同的: /data/anr/traces.txt是 java进程下各个线程的栈;可能不只是当前进程; dropbox中还有其他的内容如:cpu占用率等和logcat的比较像,也包含部分栈信息(可能有大小的限制)转载地址:http://ayuzk.baihongyu.com/