捋一捋dropbox源码

来源:互联网 发布:如何抢小米6 知乎 编辑:程序博客网 时间:2024/04/29 09:30

最近工作中涉及部分dropbox相关内容,整理一下,本文涉及的Android源码版本为Android 6.0。

DropBoxManagerService简述

DropBoxManagerService(简称DBMS)主要用于记录 Android 运行过程中, 内核, 系统进程, 用户进程等出现严重问题时的 log, 可以认为这是一个可持续存储的系统级别的 logcat,主要用于Debug调试。

一:启动源码

1:如何注册服务

SystemServer.java中ServiceManager.addService添加dropbox服务

            try {                Slog.i(TAG, "DropBox Service");                ServiceManager.addService(Context.DROPBOX_SERVICE,                        new DropBoxManagerService(context, new File("/data/system/dropbox")));            } catch (Throwable e) {                reportWtf("starting DropBoxManagerService", e);            }

Context.java中定义DROPBOX_SERVICE:

public static final String DROPBOX_SERVICE = "dropbox";

可以看出DBMS的文件目录为/data/system/dropbox,服务名为dropbox

2:创建过程

public final class DropBoxManagerService extends IDropBoxManagerService.Stub {    private static final String TAG = "DropBoxManagerService";    ......    public DropBoxManagerService(final Context context, File path) {        mDropBoxDir = path;        // Set up intent receivers        mContext = context;        mContentResolver = context.getContentResolver();        IntentFilter filter = new IntentFilter();        //监听Intent.ACTION_DEVICE_STORAGE_LOW广播        filter.addAction(Intent.ACTION_DEVICE_STORAGE_LOW);        //监听ACTION_BOOT_COMPLETED广播        filter.addAction(Intent.ACTION_BOOT_COMPLETED);        context.registerReceiver(mReceiver, filter);        //监听settings数据库变化,如果变化,触发onReveive方法        mContentResolver.registerContentObserver(            Settings.Global.CONTENT_URI, true,            new ContentObserver(new Handler()) {                @Override                public void onChange(boolean selfChange) {                    mReceiver.onReceive(context, (Intent) null);                }            });        mHandler = new Handler() {            @Override            public void handleMessage(Message msg) {                if (msg.what == MSG_SEND_BROADCAST) {                    mContext.sendBroadcastAsUser((Intent)msg.obj, UserHandle.OWNER,                            android.Manifest.permission.READ_LOGS);                }            }        };    ......

当存储设备可用空间低,手机开机,或者settings数据库变动,都会触发mReceiver.onreceive方法

 private final BroadcastReceiver mReceiver = new BroadcastReceiver() {        @Override        public void onReceive(Context context, Intent intent) {            //如果是开机时间,基本无其他操作            if (intent != null && Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {                mBooted = true;                return;            }            // Else, for ACTION_DEVICE_STORAGE_LOW:            mCachedQuotaUptimeMillis = 0;  // Force a re-check of quota size            // Run the initialization in the background (not this main thread).            // The init() and trimToFit() methods are synchronized, so they still            // block other users -- but at least the onReceive() call can finish.            new Thread() {                public void run() {                    try {                        init();                        trimToFit();                    } catch (IOException e) {                        Slog.e(TAG, "Can't init", e);                    }                }            }.start();        }    };

看一下init方法和trimToFit方法:

 /** If never run before, scans disk contents to build in-memory tracking data. */    private synchronized void init() throws IOException {        if (mStatFs == null) {            if (!mDropBoxDir.isDirectory() && !mDropBoxDir.mkdirs()) {                throw new IOException("Can't mkdir: " + mDropBoxDir);            }            try {                mStatFs = new StatFs(mDropBoxDir.getPath());                mBlockSize = mStatFs.getBlockSize();            } catch (IllegalArgumentException e) {  // StatFs throws this on error                throw new IOException("Can't statfs: " + mDropBoxDir);            }        }        if (mAllFiles == null) {            File[] files = mDropBoxDir.listFiles();            if (files == null) throw new IOException("Can't list files: " + mDropBoxDir);            mAllFiles = new FileList();            mFilesByTag = new HashMap<String, FileList>();            // Scan pre-existing files.            for (File file : files) {                //删除tmp文件                if (file.getName().endsWith(".tmp")) {                    Slog.i(TAG, "Cleaning temp file: " + file);                    file.delete();                    continue;                }                EntryFile entry = new EntryFile(file, mBlockSize);                if (entry.tag == null) {                    //忽略tag为null文件                    Slog.w(TAG, "Unrecognized file: " + file);                    continue;                } else if (entry.timestampMillis == 0) {                    //删除时间戳为0的文件                    Slog.w(TAG, "Invalid filename: " + file);                    file.delete();                    continue;                }                //添加entry至mAllFiles                enrollEntry(entry);            }        }    }
   /**     * Trims the files on disk to make sure they aren't using too much space.     * @return the overall quota for storage (in bytes)     */    private synchronized long trimToFit() throws IOException {        // Expunge aged items (including tombstones marking deleted data).    //DEFAULT_AGE_SECONDS = 3 * 86400:文件最长可存活时长为3天    //DEFAULT_MAX_FILES = 1000:最大dropbox文件个数为1000        int ageSeconds = Settings.Global.getInt(mContentResolver,                Settings.Global.DROPBOX_AGE_SECONDS, DEFAULT_AGE_SECONDS);        int maxFiles = Settings.Global.getInt(mContentResolver,                Settings.Global.DROPBOX_MAX_FILES, DEFAULT_MAX_FILES);        long cutoffMillis = System.currentTimeMillis() - ageSeconds * 1000;        while (!mAllFiles.contents.isEmpty()) {            EntryFile entry = mAllFiles.contents.first();            //当文件创建时间在3天内,且文件大小小于1000个,跳出循环            if (entry.timestampMillis > cutoffMillis && mAllFiles.contents.size() < maxFiles) break;            FileList tag = mFilesByTag.get(entry.tag);            if (tag != null && tag.contents.remove(entry)) tag.blocks -= entry.blocks;            if (mAllFiles.contents.remove(entry)) mAllFiles.blocks -= entry.blocks;            if (entry.file != null) entry.file.delete();        }        // Compute overall quota (a fraction of available free space) in blocks.        // The quota changes dynamically based on the amount of free space;        // that way when lots of data is available we can use it, but we'll get        // out of the way if storage starts getting tight.        long uptimeMillis = SystemClock.uptimeMillis();        //时间间隔大于5s才可再次执行restat        if (uptimeMillis > mCachedQuotaUptimeMillis + QUOTA_RESCAN_MILLIS) {            int quotaPercent = Settings.Global.getInt(mContentResolver,                    Settings.Global.DROPBOX_QUOTA_PERCENT, DEFAULT_QUOTA_PERCENT);            int reservePercent = Settings.Global.getInt(mContentResolver,                    Settings.Global.DROPBOX_RESERVE_PERCENT, DEFAULT_RESERVE_PERCENT);            int quotaKb = Settings.Global.getInt(mContentResolver,                    Settings.Global.DROPBOX_QUOTA_KB, DEFAULT_QUOTA_KB);            try {                mStatFs.restat(mDropBoxDir.getPath());                int available = mStatFs.getAvailableBlocks();                int nonreserved = available - mStatFs.getBlockCount() * reservePercent / 100;                int maximum = quotaKb * 1024 / mBlockSize;                mCachedQuotaBlocks = Math.min(maximum, Math.max(0, nonreserved * quotaPercent / 100));                mCachedQuotaUptimeMillis = uptimeMillis;            } catch (IllegalArgumentException e) {  // restat throws this on error                throw new IOException("Can't restat: " + mDropBoxDir);            }        }        // If we're using too much space, delete old items to make room.        //        // We trim each tag independently (this is why we keep per-tag lists).        // Space is "fairly" shared between tags -- they are all squeezed        // equally until enough space is reclaimed.        //        // A single circular buffer (a la logcat) would be simpler, but this        // way we can handle fat/bursty data (like 1MB+ bugreports, 300KB+        // kernel crash dumps, and 100KB+ ANR reports) without swamping small,        // well-behaved data streams (event statistics, profile data, etc).        //        // Deleted files are replaced with zero-length tombstones to mark what        // was lost.  Tombstones are expunged by age (see above).        //当使用空间太大时,删除部分老的items来保证空间        if (mAllFiles.blocks > mCachedQuotaBlocks) {            // Find a fair share amount of space to limit each tag            //公平处理每个tag的空间            int unsqueezed = mAllFiles.blocks, squeezed = 0;            TreeSet<FileList> tags = new TreeSet<FileList>(mFilesByTag.values());            for (FileList tag : tags) {                if (squeezed > 0 && tag.blocks <= (mCachedQuotaBlocks - unsqueezed) / squeezed) {                    break;                }                unsqueezed -= tag.blocks;                squeezed++;            }            int tagQuota = (mCachedQuotaBlocks - unsqueezed) / squeezed;            // Remove old items from each tag until it meets the per-tag quota.            //删除每个tag中较老的items            for (FileList tag : tags) {                if (mAllFiles.blocks < mCachedQuotaBlocks) break;                while (tag.blocks > tagQuota && !tag.contents.isEmpty()) {                    EntryFile entry = tag.contents.first();                    if (tag.contents.remove(entry)) tag.blocks -= entry.blocks;                    if (mAllFiles.contents.remove(entry)) mAllFiles.blocks -= entry.blocks;                    try {                        if (entry.file != null) entry.file.delete();                        enrollEntry(new EntryFile(mDropBoxDir, entry.tag, entry.timestampMillis));                    } catch (IOException e) {                        Slog.e(TAG, "Can't write tombstone file", e);                    }                }            }        }        return mCachedQuotaBlocks * mBlockSize;    }

trimToFit的工作主要做减法操作,当文件有效时长超过3天,最大文件数超过1000,占用空间太大时,执行trimToFit操作
其中也规定了某些dropbox的阈值:
//DEFAULT_QUOTA_KB = 5 * 1024:分配dropbox空间的最大值5M
//DEFAULT_QUOTA_PERCENT = 10:是指dropbox目录最多可占用空间比例10%
//DEFAULT_RESERVE_PERCENT = 10:是指dropbox不可使用的存储空间比例10%
//QUOTA_RESCAN_MILLIS = 5000:重新扫描retrim时长为5s

二:dropbox可记录哪些系统错误

1:crash(Force Close)

ActivityManagerService.java中处理crash的接口handleApplicationCrash:

    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);        handleApplicationCrashInner("crash", r, processName, crashInfo);    }
/* Native crash reporting uses this inner version because it needs to be somewhat     * decoupled from the AM-managed cleanup lifecycle     */    void handleApplicationCrashInner(String eventType, ProcessRecord r, String processName,            ApplicationErrorReport.CrashInfo crashInfo) {        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(eventType, r, processName, null, null, null, null, null, crashInfo);        crashApplication(r, crashInfo);    }

2:anr (Application Not Responding)

ActivityManagerService.java中处理ANR接口appNotResponding:

    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);    ......

3:wtf (What a Terrible Failure)

ActivityManagerService.java中处理wtf接口handleApplicationWtf:

public boolean handleApplicationWtf(final IBinder app, final String tag, boolean system,            final ApplicationErrorReport.CrashInfo crashInfo) {    ......        final ProcessRecord r = handleApplicationWtfInner(callingUid, callingPid, app, tag,        crashInfo);    ......ProcessRecord handleApplicationWtfInner(int callingUid, int callingPid, IBinder app, String tag,            final ApplicationErrorReport.CrashInfo crashInfo) {        final ProcessRecord r = findAppProcess(app, "WTF");        final String processName = app == null ? "system_server"                : (r == null ? "unknown" : r.processName);        EventLog.writeEvent(EventLogTags.AM_WTF, UserHandle.getUserId(callingUid), callingPid,                processName, r == null ? -1 : r.info.flags, tag, crashInfo.exceptionMessage);        addErrorToDropBox("wtf", r, processName, null, null, tag, null, null, crashInfo);        return r;    }

4:strict_mode (StrictMode Violation)

ActivityManagerService.java中处理strict_mode接口handleApplicationStrictModeViolation:

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);            }        }    ......

5:lowmem (低内存)

ActivityManagerService.java中

final class MainHandler extends Handler {        public MainHandler(Looper looper) {            super(looper, null, true);        }        @Override        public void handleMessage(Message msg) {    ......            case REPORT_MEM_USAGE_MSG: {                final ArrayList<ProcessMemInfo> memInfos = (ArrayList<ProcessMemInfo>)msg.obj;                Thread thread = new Thread() {                    @Override public void run() {                        reportMemUsage(memInfos);                    }                };                thread.start();                break;            }    ......    void reportMemUsage(ArrayList<ProcessMemInfo> memInfos) {    ......        addErrorToDropBox("lowmem", null, "system_server", null,                null, tag.toString(), dropBuilder.toString(), null, null);    ......

6:watchdog

Watchdog.java中:

@Override    public void run() {    ......            // 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,                                subject, null, newFd, null);                    }                };            dropboxThread.start();    ......

7:netstatys_error

NetworkStatsService 负责收集并持久化存储网络状态的统计数据, 当遇到明显的网络状态错误时, 它会增加一条 netstats_error 记录到 DropBoxManager.

    private class DropBoxNonMonotonicObserver implements NonMonotonicObserver<String> {        @Override        public void foundNonMonotonic(NetworkStats left, int leftIndex, NetworkStats right,                int rightIndex, String cookie) {            Log.w(TAG, "found non-monotonic values; saving to dropbox");            // record error for debugging            final StringBuilder builder = new StringBuilder();            builder.append("found non-monotonic " + cookie + " values at left[" + leftIndex                    + "] - right[" + rightIndex + "]\n");            builder.append("left=").append(left).append('\n');            builder.append("right=").append(right).append('\n');            final DropBoxManager dropBox = (DropBoxManager) mContext.getSystemService(                    Context.DROPBOX_SERVICE);            dropBox.addText(TAG_NETSTATS_ERROR, builder.toString());        }    } 

8:BATTERY_DISCHARGE_INFO

BatteryService 负责检测充电状态, 并更新手机电池信息. 当遇到明显的 discharge 事件, 它会增加一条 BATTERY_DISCHARGE_INFO 记录到 DropBoxManager.

private void logBatteryStatsLocked() {        IBinder batteryInfoService = ServiceManager.getService(BatteryStats.SERVICE_NAME);        if (batteryInfoService == null) return;        DropBoxManager db = (DropBoxManager) mContext.getSystemService(Context.DROPBOX_SERVICE);        if (db == null || !db.isTagEnabled("BATTERY_DISCHARGE_INFO")) return;        File dumpFile = null;        FileOutputStream dumpStream = null;        try {            // dump the service to a file            dumpFile = new File(DUMPSYS_DATA_PATH + BatteryStats.SERVICE_NAME + ".dump");            dumpStream = new FileOutputStream(dumpFile);            batteryInfoService.dump(dumpStream.getFD(), DUMPSYS_ARGS);            FileUtils.sync(dumpStream);            // add dump file to drop box            db.addFile("BATTERY_DISCHARGE_INFO", dumpFile, DropBoxManager.IS_TEXT);    ......

9:系统服务(System Serve)启动完成后的检测

系统服务(System Serve)启动完成后会进行一系列自检, 包括:

<1>开机

每次开机都会增加一条 SYSTEM_BOOT 记录.

<2>System Server 重启

如果系统服务(System Server)不是开机后的第一次启动, 会增加一条 SYSTEM_RESTART 记录, 正常情况下系统服务(System Server)在一次开机中只会启动一次, 启动第二次就意味着 bug.

<3>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)
<4>SYSTEM_TOMBSTONE (Native 进程的崩溃)

Tombstone 是 Android 用来记录 native 进程崩溃的 core dump 日志, 系统服务在启动完成后会增加一个 Observer 来侦测 tombstone 日志文件的变化, 每当生成新的 tombstone 文件, 就会增加一条 SYSTEM_TOMBSTONE 记录到 DropBoxManager 中.
通过检测文件 /cache/recovery/log 是否存在来检测设备是否因为系统恢复而重启, 并增加一条 SYSTEM_RECOVERY_LOG 记录到 DropBoxManager 中.

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();        final String bootReason = SystemProperties.get("ro.boot.bootreason", null);        String recovery = RecoverySystem.handleAftermath();        if (recovery != null && db != null) {            db.addText("SYSTEM_RECOVERY_LOG", headers + recovery);        }        String lastKmsgFooter = "";        if (bootReason != null) {            lastKmsgFooter = new StringBuilder(512)                .append("\n")                .append("Boot info:\n")                .append("Last boot reason: ").append(bootReason).append("\n")                .toString();        }        if (SystemProperties.getLong("ro.runtime.firstboot", 0) == 0) {            if ("encrypted".equals(SystemProperties.get("ro.crypto.state"))                && "trigger_restart_min_framework".equals(SystemProperties.get("vold.decrypt"))){                // Encrypted, first boot to get PIN/pattern/password so data is tmpfs                // Don't set ro.runtime.firstboot so that we will do this again                // when data is properly mounted            } else {                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())            addFileWithFootersToDropBox(db, prefs, headers, lastKmsgFooter,                    "/proc/last_kmsg", -LOG_SIZE, "SYSTEM_LAST_KMSG");            addFileWithFootersToDropBox(db, prefs, headers, lastKmsgFooter,                    "/sys/fs/pstore/console-ramoops", -LOG_SIZE,                    "SYSTEM_LAST_KMSG");            addFileToDropBox(db, prefs, headers, "/cache/recovery/log",                    -LOG_SIZE, "SYSTEM_RECOVERY_LOG");            addFileToDropBox(db, prefs, headers, "/cache/recovery/last_kmsg",                    -LOG_SIZE, "SYSTEM_RECOVERY_KMSG");            addAuditErrorsToDropBox(db, prefs, headers, -LOG_SIZE, "SYSTEM_AUDIT");            addFsckErrorsToDropBox(db, prefs, headers, -LOG_SIZE, "SYSTEM_FSCK");        } 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++) {            if (tombstoneFiles[i].isFile()) {                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 {                    File file = new File(TOMBSTONE_DIR, path);                    if (file.isFile()) {                        addFileToDropBox(db, prefs, headers, file.getPath(), LOG_SIZE, "SYSTEM_TOMBSTONE");                    }                } catch (IOException e) {                    Slog.e(TAG, "Can't log tombstone", e);                }            }        };        sTombstoneObserver.startWatching();    }

三:工作流程

以处理crash为例,从源码角度简述整个过程
由上文可知,在处理crash时,会执行addErrorToDropBox,从这个方法说起吧

1:AMS.addErrorToDropBox

public void addErrorToDropBox(String eventType,            ProcessRecord process, String processName, ActivityRecord activity,            ActivityRecord parent, String subject,            final String report, final File logFile,            final ApplicationErrorReport.CrashInfo crashInfo) {        // NOTE -- this must never acquire the ActivityManagerService lock,        // otherwise the watchdog may be prevented from resetting the system.        //根据process判断tag,比如如果是data分区的app crash,则dropboxTag为data_app_crash        final String dropboxTag = processClass(process) + "_" + eventType;        final DropBoxManager dbox = (DropBoxManager)                mContext.getSystemService(Context.DROPBOX_SERVICE);        // Exit early if the dropbox isn't configured to accept this report type.        if (dbox == null || !dbox.isTagEnabled(dropboxTag)) return;        final StringBuilder sb = new StringBuilder(1024);        //将processName,flag等信息添置输出信息中        appendDropBoxProcessHeaders(process, processName, sb);        if (activity != null) {            sb.append("Activity: ").append(activity.shortComponentName).append("\n");        }        if (parent != null && parent.app != null && parent.app.pid != process.pid) {            sb.append("Parent-Process: ").append(parent.app.processName).append("\n");        }        if (parent != null && parent != activity) {            sb.append("Parent-Activity: ").append(parent.shortComponentName).append("\n");        }        if (subject != null) {            sb.append("Subject: ").append(subject).append("\n");        }        sb.append("Build: ").append(Build.FINGERPRINT).append("\n");        if (Debug.isDebuggerConnected()) {            sb.append("Debugger: Connected\n");        }        sb.append("\n");        // Do the rest in a worker thread to avoid blocking the caller on I/O        // (After this point, we shouldn't access AMS internal data structures.)        //创建新线程,避免将调用者阻塞在I/O        Thread worker = new Thread("Error dump: " + dropboxTag) {            @Override            public void run() {                if (report != null) {                    sb.append(report);                }                if (logFile != null) {                    try {                        //添加logFile信息至dropbox,最大为256KB                        sb.append(FileUtils.readTextFile(logFile, DROPBOX_MAX_SIZE,                                    "\n\n[[TRUNCATED]]"));                    } catch (IOException e) {                        Slog.e(TAG, "Error reading " + logFile, e);                    }                }                if (crashInfo != null && crashInfo.stackTrace != null) {                    //添加栈信息至dropbox中                    sb.append(crashInfo.stackTrace);                }                //当dropbox对应settings项不为0,则添加logcat信息至dropbox                String setting = Settings.Global.ERROR_LOGCAT_PREFIX + dropboxTag;                int lines = Settings.Global.getInt(mContext.getContentResolver(), setting, 0);                if (lines > 0) {                    sb.append("\n");                    // Merge several logcat streams, and take the last N lines                    InputStreamReader input = null;                    try {                        java.lang.Process logcat = new ProcessBuilder("/system/bin/logcat",                                "-v", "time", "-b", "events", "-b", "system", "-b", "main",                                "-b", "crash",                                "-t", String.valueOf(lines)).redirectErrorStream(true).start();                        try { logcat.getOutputStream().close(); } catch (IOException e) {}                        try { logcat.getErrorStream().close(); } catch (IOException e) {}                        input = new InputStreamReader(logcat.getInputStream());                        int num;                        char[] buf = new char[8192];                        while ((num = input.read(buf)) > 0) sb.append(buf, 0, num);                    } catch (IOException e) {                        Slog.e(TAG, "Error running logcat", e);                    } finally {                        if (input != null) try { input.close(); } catch (IOException e) {}                    }                }                //调用addText方法,分析见下文                dbox.addText(dropboxTag, sb.toString());            }        };        if (process == null) {            // If process is null, we are being called from some internal code            // and may be about to die -- run this synchronously.            //当进程为空,意味着system_server进程崩溃,系统可能很快就要挂了,            //那么不再创建新线程,而是直接在system_server进程中同步运行            worker.run();        } else {            worker.start();        }    }
private static String processClass(ProcessRecord process) {        if (process == null || process.pid == MY_PID) {            return "system_server";        } else if ((process.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {            return "system_app";        } else {            return "data_app";        }    }

(1). dropbox文件输出内容项:

Process,flags, package等头信息;
当logFile不为空,则添加log信息到dropbox,最大上限为256KB;
当stack不为空,则添加stacktrace到dropbox;
当dropboxTag所对应的settings项不等于0,则输出logcat的events/system/main/crash信息。
(2). dropbox文件名

dropbox文件名为dropboxTag@xxx.txt

dropboxTag = processClass(process) + “_” + eventType;
processClass:分为system_server, system_app, data_app;
eventType:分为crash,anr,wtf等
xxx代表的是时间戳;
后缀除了.txt,还可以是.txt.gz压缩格式。
例如system_server_crash@1465650845355.txt,代表的是system_server进程出现crash,记录该文件时间戳为1465650845355。

2:DBM.addText

public void addText(String tag, String data) {    try { mService.add(new Entry(tag, 0, data)); } catch (RemoteException e) {}}

调用DropBoxManagerService.add方法

@Override    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);            //信息量较大时,采用gzip形式存储            if (read == buffer.length && ((flags & DropBoxManager.IS_GZIPPED) == 0)) {                output = new GZIPOutputStream(output);                flags = flags | DropBoxManager.IS_GZIPPED;            }            do {                output.write(buffer, 0, read);                long now = System.currentTimeMillis();                if (now - lastTrim > 30 * 1000) {                    max = trimToFit();  // In case data dribbles in slowly                    lastTrim = now;                }                read = input.read(buffer);                if (read <= 0) {                    FileUtils.sync(foutput);                    output.close();  // Get a final size measurement                    output = null;                } else {                    output.flush();  // So the size measurement is pseudo-reasonable                }                long len = temp.length();                if (len > max) {                    Slog.w(TAG, "Dropping: " + tag + " (" + temp.length() + " > " + max + " bytes)");                    temp.delete();                    temp = null;  // Pass temp = null to createEntry() to leave a tombstone                    break;                }            } while (read > 0);            long time = createEntry(temp, tag, flags);            temp = null;            final Intent dropboxIntent = new Intent(DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED);            dropboxIntent.putExtra(DropBoxManager.EXTRA_TAG, tag);            dropboxIntent.putExtra(DropBoxManager.EXTRA_TIME, time);            if (!mBooted) {                dropboxIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);            }            // Call sendBroadcast after returning from this call to avoid deadlock. In particular            // the caller may be holding the WindowManagerService lock but sendBroadcast requires a            // lock in ActivityManagerService. ActivityManagerService has been caught holding that            // very lock while waiting for the WindowManagerService lock.            //发送dropboxIntent            mHandler.sendMessage(mHandler.obtainMessage(MSG_SEND_BROADCAST, dropboxIntent));        } catch (IOException e) {            Slog.e(TAG, "Can't write: " + tag, e);        } finally {            try { if (output != null) output.close(); } catch (IOException e) {}            entry.close();            if (temp != null) temp.delete();        }    }

四:如何利用 DropBoxManager ?

利用 DropBoxManager 来记录需要持久化存储的错误日志信息

DropBoxManager 提供了 logcat 之外的另外一种错误日志记录机制, 程序可以在出错的时候自动将相关信息记录到 DropBoxManager 中. 相对于 logcat, DropBoxManager 更适合于程序的自动抓错, 避免人为因素而产生的错误遗漏. 并且 DropBoxManager 是 Android 系统的公开服务, 相对于很多私有实现, 出现兼容性问题的几率会大大降低.

错误自动上报

可以将 DropBoxManager 和设备的 BugReport 结合起来, 实现自动上报错误到服务器. 每当生成新的记录, DropBoxManager 就会广播一个 DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED Intent, 设备的 BugReport 服务需要侦听这个 Intent, 然后触发错误的自动上报.

参考:http://blog.csdn.net/ljchlx/article/details/8559963
参考:http://gityuan.com/2016/06/12/DropBoxManagerService/

0 0