【Android】【源码分析】系统各类异常日志收集服务(DropBoxManagerService)
DropBoxManagerService(简称DBMS) 统一收集管理各类系统关键日志和异常日志。
1.DropBoxManager &DropBoxManagerService简介
Android系统启动过程SystemServer进程时,在startOtherServices()过程会启动DBMS服务。和大多数系统服务一样,DropBoxManager 和 DropBoxManagerService 通过aidl实现跨进程通信。客户端可以通过DropBoxManager 提供的接口来 写入 和 读取 日志。
(1)写入日志
DropBoxManager写入日志提供了以下三种方式:
public void addText(String tag, String data) {
。。。
mService.add(new Entry(tag, 0, data));
。。。
}
}
public void addData(String tag, byte[] data, int flags) {
。。。
mService.add(new Entry(tag, 0, data, flags));
。。。
}
public void addFile(String tag, File file, int flags) throws IOException {
。。。
mService.add(new Entry(tag, 0, file, flags));
。。。
}
(代码1,来自android.os.DropBoxManager.java)
将三种添加日志的方法进行了简化。
可以看出支持String,byte[]以及file三种格式输入。在添加方法中并没有直接处理,而是交给了Entry 这个类的构造函数。这个Entry 是DropBoxManager的内部类。
(2)读取日志
/**
* Gets the next entry from the drop box <em>after</em> the specified time.
* Requires <code>android.permission.READ_LOGS</code>. You must always call
* {@link Entry#close()} on the return value!
*
* @param tag of entry to look for, null for all tags
* @param msec time of the last entry seen
* @return the next entry, or null if there are no more entries
*/
public Entry getNextEntry(String tag, long msec) {
try {
return mService.getNextEntry(tag, msec);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
(代码2,来自android.os.DropBoxManager.java)
读取日志只有这一个方法,获取的就是Entry实例。需要注意的是,tag传null的话,就会返回所有的日志。
(3)DropBoxManagerService.aidl
我们知道, DropBoxManager 和 DropBoxManagerService 交互的接口都定义在DropBoxManagerService.aidl。
interface IDropBoxManagerService {
/**
* @see DropBoxManager#addText
* @see DropBoxManager#addData
* @see DropBoxManager#addFile
*/
//三种添加日志的方式汇和成了这一个
void add(in DropBoxManager.Entry entry);
/** @see DropBoxManager#getNextEntry */
//如果tag关闭了,写入不了
boolean isTagEnabled(String tag);
/** @see DropBoxManager#getNextEntry */
DropBoxManager.Entry getNextEntry(String tag, long millis);
}
(代码3,来自DropBoxManagerService.aidl)
看到DropBoxManagerService.aidl的定义,很简洁,只包含三个方法。
(4)DropBoxManagerService
/**
* Creates an instance of managed drop box storage using the default dropbox
* directory.
*
* @param context to use for receiving free space & gservices intents
*/
public DropBoxManagerService(final Context context) {
this(context, new File("/data/system/dropbox"));
}
(代码4,来自com.android.server.DropBoxManagerService)
SystemServer默认会以反射方式得到(代码4)中的构造方法,并以之实例化。第二个参数也就是我们日志的存放位置 /data/system/dropbox。
public void add(DropBoxManager.Entry entry) {
。。。
// 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");
。。。
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;
}
//写入文件略
。。。
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.
//发送有新日志生成的广播
mHandler.sendMessage(mHandler.obtainMessage(MSG_SEND_BROADCAST, dropboxIntent));
}
。。。。。。。。
}
(代码5,来自com.android.server.DropBoxManagerService)
(5)自动瘦身
DropBoxManagerService在一些条件下可以自动清理日志。以下是可配置的条件:
private static final int DEFAULT_AGE_SECONDS = 3 * 86400;//文件最长可存活时长为3天
private static final int DEFAULT_MAX_FILES = 1000;//最大dropbox文件个数为1000
private static final int DEFAULT_QUOTA_KB = 5 * 1024;//分配dropbox空间的最大值5M
private static final int DEFAULT_QUOTA_PERCENT = 10;//是指dropbox目录最多可占用空间比例10%
private static final int DEFAULT_RESERVE_PERCENT = 10;//是指dropbox不可使用的存储空间比例10%
private static final int QUOTA_RESCAN_MILLIS = 5000;//重新扫描retrim时长为5s
(代码6,来自com.android.server.DropBoxManagerService)
2.Crash异常如何被收集至DropBoxManagerService
DBMS会收集各种系统异常日志,接下来以Crash异常为例进行分析。
由《应用内Crash异常日志收集(UncaughtExceptionHandler)》中的最后部分,有讲到,在RuntimeInit.java中的默认Crash拦截器内,会通过方法handleApplicationCrash将Crash信息报给ActivityManagerNative。
ActivityManagerNative只提供接口功能,中间代码就不贴了,最终调的是AMS的handleApplicationCrash方法。
public void handleApplicationCrash(IBinder app, ApplicationErrorReport.CrashInfo crashInfo) {
ProcessRecord r = findAppProcess(app, "Crash");
//获取进程名,用于生成tag
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) {
。。。
addErrorToDropBox(eventType, r, processName, null, null, null, null, null, crashInfo);
。。。
}
(代码7,来自com.android.server.am.ActivityManagerService)
由上面可知,最终是调用addErrorToDropBox来实现向DropBoxManager写入日志的。接下来主要看看addErrorToDropBox的逻辑:
public void addErrorToDropBox(String eventType,
ProcessRecord process, String processName, ActivityRecord activity,
ActivityRecord parent, String subject,
final String report, final File dataFile,
final ApplicationErrorReport.CrashInfo crashInfo) {
。。。
//获取DropBoxManager 服务
final DropBoxManager dbox = (DropBoxManager)
mContext.getSystemService(Context.DROPBOX_SERVICE);
// Exit early if the dropbox isn't configured to accept this report type.
//如果tag被关闭,那么不写入
if (dbox == null || !dbox.isTagEnabled(dropboxTag)) return;
//生成log具体内容
final StringBuilder sb = new StringBuilder(1024);
appendDropBoxProcessHeaders(process, processName, sb);
if (process != null) {
sb.append("Foreground: ")
.append(process.isInterestingToUserLocked() ? "Yes" : "No")
.append("\n");
}
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操作
//(在此之后,我们不应该访问AMS内部数据结构。)
Thread worker = new Thread("Error dump: " + dropboxTag) {
@Override
public void run() {
if (report != null) {
sb.append(report);
}
String setting = Settings.Global.ERROR_LOGCAT_PREFIX + dropboxTag;
int lines = Settings.Global.getInt(mContext.getContentResolver(), setting, 0);
int maxDataFileSize = DROPBOX_MAX_SIZE - sb.length()
- lines * RESERVED_BYTES_PER_LOGCAT_LINE;
if (dataFile != null && maxDataFileSize > 0) {
try {
sb.append(FileUtils.readTextFile(dataFile, maxDataFileSize,
"\n\n[[TRUNCATED]]"));
} catch (IOException e) {
Slog.e(TAG, "Error reading " + dataFile, e);
}
}
if (crashInfo != null && crashInfo.stackTrace != null) {
sb.append(crashInfo.stackTrace);
}
if (lines > 0) {
sb.append("\n");
// Merge several logcat streams, and take the last N lines
//合并几个logcat流,取最后N行
InputStreamReader input = null;
try {
java.lang.Process logcat = new ProcessBuilder(
"/system/bin/timeout", "-k", "15s", "10s",
"/system/bin/logcat", "-v", "threadtime", "-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) {}
}
}
//调用(代码1)处提供的写入日志接口
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.
//如果进程为空,那么直接在当前线程执行
worker.run();
} else {
//否则用新线程执行
worker.start();
}
}
(代码8,来自com.android.server.am.ActivityManagerService)
至此,Crash异常通过(代码一)处逻辑被收集至DropBoxManagerService。
3.DropBoxManagerService收集那些数据
图上是以我自身设备为例。
DropBoxManagerService并没有将日志TAG提前定义死。我想这是为了方便后期扩展自定义的日志。
以下是一些常见的日志类型:
system_app_crash 系统app崩溃
system_app_anr 系统app无响应
data_app_crash 普通app崩溃
data_app_anr 普通app无响应
system_server_anr system进程无响应
system_server_watchdog system进程发生watchdog
system_server_crash system进程崩溃
system_server_native_crash system进程native出现崩溃
system_server_wtf system进程发生严重错误
system_server_lowmem system进程内存不足
SYSTEM_TOMBSTONE Native 进程的崩溃
SYSTEM_RECOVERY_LOG 系统恢复
StrictMode:
比正常模式检测得更严格, 通常用来监测不应当在主线程执行的网络, 文件等操作
内核错误相关:
SYSTEM_LAST_KMSG, 如果 /proc/last_kmsg 存在.
APANIC_CONSOLE, 如果 /data/dontpanic/apanic_console 存在.
APANIC_THREADS, 如果 /data/dontpanic/apanic_threads 存在
参考:Android DropBoxManager服务分析:https://blog.csdn.net/linliang815/article/details/72842311
Android DropboxManager介绍:https://www.jianshu.com/p/f9174a8d0a10
本文地址:https://blog.csdn.net/a953210725/article/details/107562158