介紹 Android DropBoxManager Service

轉自 http://xiaocong.github.io/blog/2012/11/21/to-introduce-android-dropboxmanager-service/

什麼是 DropBoxManager ?

Enqueues chunks of data (from various sources – application crashes, kernel log records, etc.). The queue is size bounded and will drop old data if the enqueued data exceeds the maximum size. You can think of this as a persistent, system-wide, blob-oriented “logcat”.

DropBoxManager 是 Android 在 Froyo(API level 8) 引入的用來持續化存儲系統數據的機制, 主要用於記錄 Android 運行過程中, 內核, 系統進程, 用戶進程等出現嚴重問題時的 log, 可以認爲這是一個可持續存儲的系統級別的 logcat.

我們可以通過用參數 DROPBOX_SERVICE 調用 getSystemService(String) 來獲得這個服務, 並查詢出所有存儲在 DropBoxManager 裏的系統錯誤記錄.
Android 缺省能記錄哪些系統錯誤 ?

我沒有在官方的網站上找到關於哪些系統錯誤會被記錄到 DropBoxManager 中的文檔, 但我們可以查看源代碼來找到相關信息. 從源代碼中可以查找到記錄到 DropBoxManager 中各種 tag(類似於 logcat 的 tag).
crash (應用程序強制關閉, Force Close)

當Java層遇到未被 catch 的例外時, ActivityManagerService 會記錄一次 crash 到 DropBoxManager中, 並彈出 Force Close 對話框提示用戶.
ActivityManagerServicelink

public void handleApplicationCrash(IBinder app, ApplicationErrorReport.CrashInfo crashInfo) {
    ProcessRecord r = findAppProcess(app, "Crash");
    final String processName = app == null ? "system_server"
            : (r == null ? "unknown" : r.processName);

    EventLog.writeEvent(EventLogTags.AM_CRASH, Binder.getCallingPid(),
            UserHandle.getUserId(Binder.getCallingUid()), processName,
            r == null ? -1 : r.info.flags,
            crashInfo.exceptionClassName,
            crashInfo.exceptionMessage,
            crashInfo.throwFileName,
            crashInfo.throwLineNumber);

    addErrorToDropBox("crash", r, processName, null, null, null, null, null, crashInfo);

    crashApplication(r, crashInfo);
}

anr (應用程序沒響應, Application Not Responding, ANR)

當應用程序的主線程(UI線程)長時間未能得到響應時, ActivityManagerService 會記錄一次 anr 到 DropBoxManager中, 並彈出 Application Not Responding 對話框提示用戶.
ActivityManagerServicelink

final void appNotResponding(ProcessRecord app, ActivityRecord activity,
        ActivityRecord parent, boolean aboveSystem, final String annotation) {
    //......
    addErrorToDropBox("anr", app, app.processName, activity, parent, annotation,
            cpuInfo, tracesFile, null);
    //......
}

wtf (What a Terrible Failure)

‘android.util.Log’ 類提供了靜態的 wtf 函數, 應用程序可以在代碼中用來主動報告一個不應當發生的情況. 依賴於系統設置, 這個函數會通過 ActivityManagerService 增加一個 wtf 記錄到 DropBoxManager中, 並/或終止當前應用程序進程.
ActivityManagerServicelink

public boolean handleApplicationWtf(IBinder app, String tag,
        ApplicationErrorReport.CrashInfo crashInfo) {
    ProcessRecord r = findAppProcess(app, "WTF");
    final String processName = app == null ? "system_server"
            : (r == null ? "unknown" : r.processName);

    EventLog.writeEvent(EventLogTags.AM_WTF,
            UserHandle.getUserId(Binder.getCallingUid()), Binder.getCallingPid(),
            processName,
            r == null ? -1 : r.info.flags,
            tag, crashInfo.exceptionMessage);

    addErrorToDropBox("wtf", r, processName, null, null, tag, null, null, crashInfo);

    if (r != null && r.pid != Process.myPid() &&
            Settings.Global.getInt(mContext.getContentResolver(),
                    Settings.Global.WTF_IS_FATAL, 0) != 0) {
        crashApplication(r, crashInfo);
        return true;
    } else {
        return false;
    }
}

strict_mode (StrictMode Violation)

StrictMode (嚴格模式), 顧名思義, 就是在比正常模式檢測得更嚴格, 通常用來監測不應當在主線程執行的網絡, 文件等操作. 任何 StrictMode 違例都會被 ActivityManagerService 在 DropBoxManager 中記錄爲一次 strict_mode 違例.
ActivityManagerServicelink

public void handleApplicationStrictModeViolation(
        IBinder app,
        int violationMask,
        StrictMode.ViolationInfo info) {
    ProcessRecord r = findAppProcess(app, "StrictMode");
    if (r == null) {
        return;
    }

    if ((violationMask & StrictMode.PENALTY_DROPBOX) != 0) {
        Integer stackFingerprint = info.hashCode();
        boolean logIt = true;
        synchronized (mAlreadyLoggedViolatedStacks) {
            if (mAlreadyLoggedViolatedStacks.contains(stackFingerprint)) {
                logIt = false;
                // TODO: sub-sample into EventLog for these, with
                // the info.durationMillis?  Then we'd get
                // the relative pain numbers, without logging all
                // the stack traces repeatedly.  We'd want to do
                // likewise in the client code, which also does
                // dup suppression, before the Binder call.
            } else {
                if (mAlreadyLoggedViolatedStacks.size() >= MAX_DUP_SUPPRESSED_STACKS) {
                    mAlreadyLoggedViolatedStacks.clear();
                }
                mAlreadyLoggedViolatedStacks.add(stackFingerprint);
            }
        }
        if (logIt) {
            logStrictModeViolationToDropBox(r, info);
        }
    }
    //......
}

lowmem (低內存)

在內存不足的時候, Android 會終止後臺應用程序來釋放內存, 但如果沒有後臺應用程序可被釋放時, ActivityManagerService 就會在 DropBoxManager 中記錄一次 lowmem.
ActivityManagerServicelink

    public void handleMessage(Message msg) {
        switch (msg.what) {
        //...
        case REPORT_MEM_USAGE: {
            //......
            Thread thread = new Thread() {
                @Override public void run() {
                    StringBuilder dropBuilder = new StringBuilder(1024);
                    StringBuilder logBuilder = new StringBuilder(1024);
                    //......
                    addErrorToDropBox("lowmem", null, "system_server", null,
                            null, tag.toString(), dropBuilder.toString(), null, null);
                    //......
                }
            };
            thread.start();
            break;
        }
        //......
    }

watchdog

如果 WatchDog 監測到系統進程(system_server)出現問題, 會增加一條 watchdog 記錄到 DropBoxManager 中, 並終止系統進程的執行.
Watchdoglink

/** This class calls its monitor every minute. Killing this process if they don't return **/
public class Watchdog extends Thread {
    //......
    @Override
    public void run() {
        boolean waitedHalf = false;
        while (true) {
            //......

            // If we got here, that means that the system is most likely hung.
            // First collect stack traces from all threads of the system process.
            // Then kill this process so that the system will restart.

            //......

            // Try to add the error to the dropbox, but assuming that the ActivityManager
            // itself may be deadlocked.  (which has happened, causing this statement to
            // deadlock and the watchdog as a whole to be ineffective)
            Thread dropboxThread = new Thread("watchdogWriteToDropbox") {
                    public void run() {
                        mActivity.addErrorToDropBox(
                                "watchdog", null, "system_server", null, null,
                                name, null, stack, null);
                    }
                };
            dropboxThread.start();
            try {
                dropboxThread.join(2000);  // wait up to 2 seconds for it to return.
            } catch (InterruptedException ignored) {}

            //......
        }
    }

    //......
}

netstats_error

NetworkStatsService 負責收集並持久化存儲網絡狀態的統計數據, 當遇到明顯的網絡狀態錯誤時, 它會增加一條 netstats_error 記錄到 DropBoxManager.
BATTERY_DISCHARGE_INFO

BatteryService 負責檢測充電狀態, 並更新手機電池信息. 當遇到明顯的 discharge 事件, 它會增加一條 BATTERY_DISCHARGE_INFO 記錄到 DropBoxManager.
系統服務(System Serve)啓動完成後的檢測

系統服務(System Serve)啓動完成後會進行一系列自檢, 包括:

開機

每次開機都會增加一條 SYSTEM_BOOT 記錄.

System Server 重啓

如果系統服務(System Server)不是開機後的第一次啓動, 會增加一條 SYSTEM_RESTART 記錄, 正常情況下系統服務(System Server)在一次開機中只會啓動一次, 啓動第二次就意味着 bug.

Kernel Panic (內核錯誤)

發生 Kernel Panic 時, Kernel 會記錄一些 log 信息到文件系統, 因爲 Kernel 已經掛掉了, 當然這時不可能有其他機會來記錄錯誤信息了. 唯一能檢測 Kernel Panic 的辦法就是在手機啓動後檢查這些 log 文件是否存在, 如果存在則意味着上一次手機是因爲 Kernel Panic 而宕機, 並記錄這些日誌到 DropBoxManager 中. DropBoxManager 記錄 TAG 名稱和對應的文件名分別是:
    SYSTEM_LAST_KMSG, 如果 /proc/last_kmsg 存在.
    APANIC_CONSOLE, 如果 /data/dontpanic/apanic_console 存在.
    APANIC_THREADS, 如果 /data/dontpanic/apanic_threads 存在.

系統恢復(System Recovery)

通過檢測文件 /cache/recovery/log 是否存在來檢測設備是否因爲系統恢復而重啓, 並增加一條 SYSTEM_RECOVERY_LOG 記錄到 DropBoxManager 中.

System Server BootReceiverlink

private void logBootEvents(Context ctx) throws IOException {
    final DropBoxManager db = (DropBoxManager) ctx.getSystemService(Context.DROPBOX_SERVICE);
    final SharedPreferences prefs = ctx.getSharedPreferences("log_files", Context.MODE_PRIVATE);
    final String headers = new StringBuilder(512)
        .append("Build: ").append(Build.FINGERPRINT).append("\n")
        .append("Hardware: ").append(Build.BOARD).append("\n")
        .append("Revision: ")
        .append(SystemProperties.get("ro.revision", "")).append("\n")
        .append("Bootloader: ").append(Build.BOOTLOADER).append("\n")
        .append("Radio: ").append(Build.RADIO).append("\n")
        .append("Kernel: ")
        .append(FileUtils.readTextFile(new File("/proc/version"), 1024, "...\n"))
        .append("\n").toString();

    String recovery = RecoverySystem.handleAftermath();
    if (recovery != null && db != null) {
        db.addText("SYSTEM_RECOVERY_LOG", headers + recovery);
    }

    if (SystemProperties.getLong("ro.runtime.firstboot", 0) == 0) {
        String now = Long.toString(System.currentTimeMillis());
        SystemProperties.set("ro.runtime.firstboot", now);
        if (db != null) db.addText("SYSTEM_BOOT", headers);

        // Negative sizes mean to take the *tail* of the file (see FileUtils.readTextFile())
        addFileToDropBox(db, prefs, headers, "/proc/last_kmsg",
                -LOG_SIZE, "SYSTEM_LAST_KMSG");
        addFileToDropBox(db, prefs, headers, "/cache/recovery/log",
                -LOG_SIZE, "SYSTEM_RECOVERY_LOG");
        addFileToDropBox(db, prefs, headers, "/data/dontpanic/apanic_console",
                -LOG_SIZE, "APANIC_CONSOLE");
        addFileToDropBox(db, prefs, headers, "/data/dontpanic/apanic_threads",
                -LOG_SIZE, "APANIC_THREADS");
    } else {
        if (db != null) db.addText("SYSTEM_RESTART", headers);
    }

    // Scan existing tombstones (in case any new ones appeared)
    File[] tombstoneFiles = TOMBSTONE_DIR.listFiles();
    for (int i = 0; tombstoneFiles != null && i < tombstoneFiles.length; i++) {
        addFileToDropBox(db, prefs, headers, tombstoneFiles[i].getPath(),
                LOG_SIZE, "SYSTEM_TOMBSTONE");
    }

    // Start watching for new tombstone files; will record them as they occur.
    // This gets registered with the singleton file observer thread.
    sTombstoneObserver = new FileObserver(TOMBSTONE_DIR.getPath(), FileObserver.CLOSE_WRITE) {
        @Override
        public void onEvent(int event, String path) {
            try {
                String filename = new File(TOMBSTONE_DIR, path).getPath();
                addFileToDropBox(db, prefs, headers, filename, LOG_SIZE, "SYSTEM_TOMBSTONE");
            } catch (IOException e) {
                Slog.e(TAG, "Can't log tombstone", e);
            }
        }
    };

    sTombstoneObserver.startWatching();
}

SYSTEM_TOMBSTONE (Native 進程的崩潰)

Tombstone 是 Android 用來記錄 native 進程崩潰的 core dump 日誌, 系統服務在啓動完成後會增加一個 Observer 來偵測 tombstone 日誌文件的變化, 每當生成新的 tombstone 文件, 就會增加一條 SYSTEM_TOMBSTONE 記錄到 DropBoxManager 中.
DropBoxManager 如何存儲記錄數據 ?

DropBoxManager 使用的是文件存儲, 所有的記錄都存儲在 /data/system/dropbox 目錄中, 一條記錄就是一個文件, 當文本文件的尺寸超過文件系統的最小區塊尺寸後, DropBoxManager 還會自動壓縮該文件, 通常文件名以調用 DropBoxManager 的 TAG 參數開頭.
System Server BootReceiverlink

$ adb shell ls -l /data/system/dropbox
-rw——- system system 258 2012-11-21 11:36 [email protected]
-rw——- system system 39 2012-11-21 11:40 [email protected]
-rw——- system system 39 2012-11-21 12:10 [email protected]
-rw——- system system 34 2012-11-21 18:10 [email protected]
-rw——- system system 34 2012-11-21 18:40 [email protected]
-rw——- system system 34 2012-11-22 10:10 [email protected]
-rw——- system system 1528 2012-11-21 22:54 [email protected]
-rw——- system system 1877 2012-11-21 11:36 [email protected]
-rw——- system system 3724 2012-11-21 11:36 [email protected]

如何利用 DropBoxManager ?

利用 DropBoxManager 來記錄需要持久化存儲的錯誤日誌信息

DropBoxManager 提供了 logcat 之外的另外一種錯誤日誌記錄機制, 程序可以在出錯的時候自動將相關信息記錄到 DropBoxManager 中. 相對於 logcat, DropBoxManager 更適合於程序的自動抓錯, 避免人爲因素而產生的錯誤遺漏. 並且 DropBoxManager 是 Android 系統的公開服務, 相對於很多私有實現, 出現兼容性問題的機率會大大降低.

錯誤自動上報

可以將 DropBoxManager 和設備的 BugReport 結合起來, 實現自動上報錯誤到服務器. 每當生成新的記錄, DropBoxManager 就會廣播一個 DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED Intent, 設備的 BugReport 服務需要偵聽這個 Intent, 然後觸發錯誤的自動上報.

參考
Android Official Site
DropBoxManager Overview
ActivityManager Service Overview
Android StrictMode Overview

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章