淺談Android ANR的信息收集過程

一. ANR場景

無論是四大組件或者進程等隻要發生ANR,最終都會調用AMS.appNotResponding()方法,下面從這個方法說起。

以下場景都會觸發調用AMS.appNotResponding方法:

  • Service Timeout:比如前臺服務在20s內未執行完成;
  • BroadcastQueue Timeout:比如前臺廣播在10s內未執行完成
  • InputDispatching Timeout: 輸入事件分發超時5s,包括按鍵和觸摸事件。

二. appNotResponding處理流程

1. AMS.appNotResponding

final void appNotResponding(ProcessRecord app, ActivityRecord activity, ActivityRecord parent, boolean aboveSystem, final String annotation) {
    ...
    updateCpuStatsNow(); //第一次 更新cpu統計信息
    synchronized (this) {
      //PowerManager.reboot() 會阻塞很長時間,因此忽略關機時的ANR
      if (mShuttingDown) {
          return;
      } else if (app.notResponding) {
          return;
      } else if (app.crashing) {
          return;
      }
      //記錄ANR到EventLog
      EventLog.writeEvent(EventLogTags.AM_ANR, app.userId, app.pid,
              app.processName, app.info.flags, annotation);
              
      // 將當前進程添加到firstPids
      firstPids.add(app.pid);
      int parentPid = app.pid;
      
      //將system_server進程添加到firstPids
      if (MY_PID != app.pid && MY_PID != parentPid) firstPids.add(MY_PID);
      
      for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
          ProcessRecord r = mLruProcesses.get(i);
          if (r != null && r.thread != null) {
              int pid = r.pid;
              if (pid > 0 && pid != app.pid && pid != parentPid && pid != MY_PID) {
                  if (r.persistent) {
                      firstPids.add(pid); //將persistent進程添加到firstPids
                  } else {
                      lastPids.put(pid, Boolean.TRUE); //其他進程添加到lastPids
                  }
              }
          }
      }
    }
    
    // 記錄ANR輸出到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");
    }
    
    //創建CPU tracker對象
    final ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true);
    //輸出traces信息【見小節2】
    File tracesFile = dumpStackTraces(true, firstPids, processCpuTracker, 
            lastPids, NATIVE_STACKS_OF_INTEREST);
            
    updateCpuStatsNow(); //第二次更新cpu統計信息
    //記錄當前各個進程的CPU使用情況
    synchronized (mProcessCpuTracker) {
        cpuInfo = mProcessCpuTracker.printCurrentState(anrTime);
    }
    //記錄當前CPU負載情況
    info.append(processCpuTracker.printCurrentLoad());
    info.append(cpuInfo);
    //記錄從anr時間開始的Cpu使用情況
    info.append(processCpuTracker.printCurrentState(anrTime));
    //輸出當前ANR的reason,以及CPU使用率、負載信息
    Slog.e(TAG, info.toString()); 
    
    //將traces文件 和 CPU使用率信息保存到dropbox,即data/system/dropbox目錄
    addErrorToDropBox("anr", app, app.processName, activity, parent, annotation,
            cpuInfo, tracesFile, null);

    synchronized (this) {
        ...
        //後臺ANR的情況, 則直接殺掉
        if (!showBackground && !app.isInterestingToUserLocked() && app.pid != MY_PID) {
            app.kill("bg anr", true);
            return;
        }

        //設置app的ANR狀態,病查詢錯誤報告receiver
        makeAppNotRespondingLocked(app,
                activity != null ? activity.shortComponentName : null,
                annotation != null ? "ANR " + annotation : "ANR",
                info.toString());

        //重命名trace文件
        String tracesPath = SystemProperties.get("dalvik.vm.stack-trace-file", null);
        if (tracesPath != null && tracesPath.length() != 0) {
            //traceRenameFile = "/data/anr/traces.txt"
            File traceRenameFile = new File(tracesPath);
            String newTracesPath;
            int lpos = tracesPath.lastIndexOf (".");
            if (-1 != lpos)
                // 新的traces文件= /data/anr/traces_進程名_當前日期.txt
                newTracesPath = tracesPath.substring (0, lpos) + "_" + app.processName + "_" + mTraceDateFormat.format(new Date()) + tracesPath.substring (lpos);
            else
                newTracesPath = tracesPath + "_" + app.processName;

            traceRenameFile.renameTo(new File(newTracesPath));
        }
                
        //彈出ANR對話框
        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);
        }
        
        //向ui線程發送,內容為SHOW_NOT_RESPONDING_MSG的消息
        mUiHandler.sendMessage(msg);
    }
    
}

當發生ANR時, 會按順序依次執行:

  1. 輸出ANR Reason信息到EventLog. 也就是說ANR觸發的時間點最接近的就是EventLog中輸出的am_anr信息;
  2. 收集並輸出重要進程列表中的各個線程的traces信息,該方法較耗時; 【見小節2】
  3. 輸出當前各個進程的CPU使用情況以及CPU負載情況;
  4. 將traces文件和 CPU使用情況信息保存到dropbox,即data/system/dropbox目錄
  5. 根據進程類型,來決定直接後臺殺掉,還是彈框告知用戶.

ANR輸出重要進程的traces信息,這些進程包含:

  • firstPids隊列:第一個是ANR進程,第二個是system_server,剩餘是所有persistent進程;
  • Native隊列:是指/system/bin/目錄的mediaserver,sdcard 以及surfaceflinger進程;
  • lastPids隊列: 是指mLruProcesses中的不屬於firstPids的所有進程。

2. AMS.dumpStackTraces

public static File dumpStackTraces(boolean clearTraces, ArrayList<Integer> firstPids, ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids, String[] nativeProcs) {
    //默認為 data/anr/traces.txt
    String tracesPath = SystemProperties.get("dalvik.vm.stack-trace-file", null);
    if (tracesPath == null || tracesPath.length() == 0) {
        return null;
    }

    File tracesFile = new File(tracesPath);
    try {
        //當clearTraces,則刪除已存在的traces文件
        if (clearTraces && tracesFile.exists()) tracesFile.delete();
        //創建traces文件
        tracesFile.createNewFile();
        FileUtils.setPermissions(tracesFile.getPath(), 0666, -1, -1);
    } catch (IOException e) {
        return null;
    }
    //輸出trace內容【見小節3】
    dumpStackTraces(tracesPath, firstPids, processCpuTracker, lastPids, nativeProcs);
    return tracesFile;
}

這裡會保證data/anr/traces.txt文件內容是全新的方式,而非追加。

3. AMS.dumpStackTraces

private static void dumpStackTraces(String tracesPath, ArrayList<Integer> firstPids, ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids, String[] nativeProcs) {
    FileObserver observer = new FileObserver(tracesPath, FileObserver.CLOSE_WRITE) {
        @Override
        public synchronized void onEvent(int event, String path) { notify(); }
    };

    try {
        observer.startWatching();

        //首先,獲取最重要進程的stacks
        if (firstPids != null) {
            try {
                int num = firstPids.size();
                for (int i = 0; i < num; i++) {
                    synchronized (observer) {
                        //向目標進程發送signal來輸出traces
                        Process.sendSignal(firstPids.get(i), Process.SIGNAL_QUIT);
                        observer.wait(200);  //等待直到寫關閉,或者200ms超時
                    }
                }
            } catch (InterruptedException e) {
                Slog.wtf(TAG, e);
            }
        }

        //下一步,獲取native進程的stacks
        if (nativeProcs != null) {
            int[] pids = Process.getPidsForCommands(nativeProcs);
            if (pids != null) {
                for (int pid : pids) {
                    //輸出native進程的trace【見小節4】
                    Debug.dumpNativeBacktraceToFile(pid, tracesPath);
                }
            }
        }

        if (processCpuTracker != null) {
            processCpuTracker.init();
            System.gc();
            processCpuTracker.update();
            synchronized (processCpuTracker) {
                processCpuTracker.wait(500); //等待500ms
            }
            //測量CPU使用情況
            processCpuTracker.update();

            //從lastPids中選取CPU使用率 top 5的進程,輸出這些進程的stacks
            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++;
                    synchronized (observer) {
                        Process.sendSignal(stats.pid, Process.SIGNAL_QUIT);
                        observer.wait(200); 
                    }
                }
            }
        }
    } finally {
        observer.stopWatching();
    }
}

該方法的主要功能,依次輸出:

1.收集firstPids進程的stacks;

第一個是發生ANR進程;

第二個是system_server;

mLruProcesses中所有的persistent進程;

2.收集Native進程的stacks;(dumpNativeBacktraceToFile)

依次是mediaserver,sdcard,surfaceflinger進程;

3.收集lastPids進程的stacks;;

依次輸出CPU使用率top 5的進程;

Tips: firstPids列表中的進程, 兩個進程之間會休眠200ms, 可見persistent進程越多,則時間越長. top 5進程的traces過程中, 同樣是間隔200ms, 另外進程使用情況的收集也是比較耗時.

4. dumpNativeBacktraceToFile

Debug.dumpNativeBacktraceToFile(pid, tracesPath)經過JNI調用如下方法:

static void android_os_Debug_dumpNativeBacktraceToFile(JNIEnv* env, jobject clazz, jint pid, jstring fileName) {
    ...
    const jchar* str = env->GetStringCritical(fileName, 0);
    String8 fileName8;
    if (str) {
        fileName8 = String8(reinterpret_cast<const char16_t*>(str),
                            env->GetStringLength(fileName));
        env->ReleaseStringCritical(fileName, str);
    }

    //打開/data/anr/traces.txt
    int fd = open(fileName8.string(), O_CREAT | O_WRONLY | O_NOFOLLOW, 0666);  /* -rw-rw-rw- */
    ...
    
    if (lseek(fd, 0, SEEK_END) < 0) {
        fprintf(stderr, "lseek: %s\n", strerror(errno));
    } else {
        //【見小節5】
        dump_backtrace_to_file(pid, fd);
    }

    close(fd);
}

5. dump_backtrace_to_file

[-> debugger.c]

int dump_backtrace_to_file(pid_t tid, int fd) {
    return dump_backtrace_to_file_timeout(tid, fd, 0);
}

int dump_backtrace_to_file_timeout(pid_t tid, int fd, int timeout_secs) {
  //通過socket向服務端發送dump backtrace的請求
  int sock_fd = make_dump_request(DEBUGGER_ACTION_DUMP_BACKTRACE, tid, timeout_secs);
  if (sock_fd < 0) {
    return -1;
  }

  int result = 0;
  char buffer[1024];
  ssize_t n;
  //阻塞等待,從sock_fd中讀取到服務端發送過來的數據,並寫入buffer
  while ((n = TEMP_FAILURE_RETRY(read(sock_fd, buffer, sizeof(buffer)))) > 0) {
    //再將buffer數據輸出到traces.txt文件
    if (TEMP_FAILURE_RETRY(write(fd, buffer, n)) != n) {
      result = -1;
      break;
    }
  }
  close(sock_fd);
  return result;
}

可見,這個過程主要是通過向debuggerd守護進程發送命令DEBUGGER_ACTION_DUMP_BACKTRACE, debuggerd收到該命令,在子進程中調用 dump_backtrace()來輸出backtrace。

三. 總結

觸發ANR時系統會輸出關鍵信息:(這個較耗時,可能會有10s)

1.將am_anr信息,輸出到EventLog.(ANR開始起點看EventLog)

2.獲取重要進程trace信息,保存到/data/anr/traces.txt;(會先刪除老的文件)

Java進程的traces;

Native進程的traces;

3.ANR reason以及CPU使用情況信息,輸出到main log;

4.再將CPU使用情況和進程trace文件信息,再保存到/data/system/dropbox;

整個過程中進程Trace的輸出是最為核心的環節,Java和Native進程采用不同的策略,如下:

進程類型 trace命令 描述
Java kill -3 [pid] 不適用於Native進程
Native debuggerd -b [pid] 也適用於Java進程

說明:kill -3命令需要虛擬機的支持,所以無法輸出Native進程traces.而debuggerd -b [pid]也可用於Java進程,但信息量遠沒有kill -3多。 總之,ANR信息最為重要的是dropbox信息,比如system_server_anr。

重要節點:

  • 進程名:cat /proc/[pid]/cmdline
  • 線程名:cat /proc/[tid]/comm
  • Kernel棧:cat /proc/[tid]/stack
  • Native棧: 解析 /proc/[pid]/maps

以上就是淺談Android ANR的信息收集過程的詳細內容,更多關於Android ANR 的資料請關註WalkonNet其它相關文章!

推薦閱讀: