概述
DropBoxManagerService
简介
DropBoxManagerService(简称DBMS)是日志相关的服务,用于生成与管理 系统运行时的一些日志文件。日志文件大多记录的是系统或某个应用出错的日志信息。该日志输出在dropbox目录下。
它在SystemServer启动以后被添加到ServiceManager中:
ServiceManager.addService(Context.DROPBOX_SERVICE,
new DropBoxManagerService(context, new File("/data/system/dropbox")));
ServiceManager是用来管理所有Service的管理器。addService方法第一个参数是 服务的名字,第二个参数传入服务的对象。 对应的,我们使用context.getSystemService的时候,也需要传入对应的服务名字。ServiceManager本身对上层APP是hide的,当然也可以通过反射ServiceManager获取对应的服务。
DropBoxManagerService的构造函数以及onReceive方法
DBMS的构造函数如下:
public DropBoxManagerService(final Context context, File path) {
mDropBoxDir = path;
mContext = context;
mContentResolver = context.getContentResolver();
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_DEVICE_STORAGE_LOW);
filter.addAction(Intent.ACTION_BOOT_COMPLETED);
context.registerReceiver(mReceiver, filter);
mContentResolver.registerContentObserver(
Settings.Global.CONTENT_URI, true,
new ContentObserver(new Handler()) {
@Override
public void onChange(boolean selfChange) {
//监听到Settings数据库变化的话,就调用onReceive
mReceiver.onReceive(context, (Intent) null);
}
});
//此Handler是为了防止死锁而添加的 发送一个广播
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);
}
}
};
}
可以看到DBMS的构造方法主要是注册了一个BroadcastReceiver,监听了三个事件:
- 1、ACTION_DEVICE_STORAGE_LOW:当设备存储空间不足,就会发送此广播,触发onReceive;
- 2、ACTION_BOOT_COMPLETED: 当设备启动完毕后,发送此广播,触发onReceive;
- 3、当Settings的数据库变化后,触发onReceive
然后主要的处理逻辑就在onReceive函数中去了,看一下onReceive函数:
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
...
new Thread() {
public void run() {
try {
init(); // 1、先做初始化工作,如生产dropbox目录、统计已有文件的大小等
trimToFit(); //2、整理日志,控制在一定的存储空间范围内
} catch (IOException e) {
Slog.e(TAG, "Can't init", e);
}
}
}.start();
}
};
上述的 代码1 和 代码2 的两个方法都是synchronize的,主要作用有
- 1、init 用来扫描磁盘,做一些初始化操作,统计目前总的文件大小等;
- 2、trimToFit 用来控制总的日志文件大小,多了就删除旧的日志文件。
dropbox日志文件的生成
要想理清一个Service,就从它提供的服务开始分析,DBMS主要记录部分日志信息,某个应用crash时,ActivityManagerService的handleApplicationCrash方法就会调用,内部就会调用DBMS:
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);
//继续往此方法调用, 类型为crash, 同时传入进程名字
handleApplicationCrashInner("crash", r, processName, crashInfo);
}
void handleApplicationCrashInner(String eventType, ProcessRecord r, String processName,
ApplicationErrorReport.CrashInfo crashInfo) {
...
//此处调用DBMS把日志写入dropbox
addErrorToDropBox(eventType, r, processName, null, null, null, null, null, crashInfo);
crashApplication(r, crashInfo);
}
我们接着看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) {
//dropbox日志 前缀是一个特定tag,由 “进程类型_事件类型” 组成
//进程类型有:system_server, system_app, data_app三种
//事件类型有:crash, wtf(what a terrible failure),anr ,lowmem四种(以前只有前三种)
final String dropboxTag = processClass(process) + "_" + eventType;
final DropBoxManager dbox = (DropBoxManager)
mContext.getSystemService(Context.DROPBOX_SERVICE);
if (dbox == null || !dbox.isTagEnabled(dropboxTag)) return;
final StringBuilder sb = new StringBuilder(1024);
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: Connectedn");
}
sb.append("n");
//单独一个线程想DBMS添加日志信息
Thread worker = new Thread("Error dump: " + dropboxTag) {
@Override
public void run() {
if (report != null) {
sb.append(report);
}
if (logFile != null) {
try {
//如果有log,则入去到sb中
sb.append(FileUtils.readTextFile(logFile, DROPBOX_MAX_SIZE,
"nn[[TRUNCATED]]"));
} catch (IOException e) {
Slog.e(TAG, "Error reading " + logFile, e);
}
}
if (crashInfo != null && crashInfo.stackTrace != null) {
//读取crashinfo,一般是堆栈调用信息
sb.append(crashInfo.stackTrace);
}
String setting = Settings.Global.ERROR_LOGCAT_PREFIX + dropboxTag;
int lines = Settings.Global.getInt(mContext.getContentResolver(), setting, 0);
if (lines > 0) {
sb.append("n");
InputStreamReader input = null;
try {
//创建一个新进程运行logcat,传入的参数是logcat常用参数
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();
...
}
//调用DBMS的 addText方法
dbox.addText(dropboxTag, sb.toString());
}
};
if (process == null) {
worker.run();//如果SystemServer进程崩溃了,则只能在当前线程执行
} else {
worker.start();//否则开启这个新线程去执行
}
}
上述代码中,生成日志的内容的过程是最重要的, 添加了Activity、process等信息。 最后只是调用addText把日志内容传递给DBMS.
dropbox日志文件的添加
DropBoxManagerService的 addText方法, 最终进入DropBoxManagerService的add方法, 这两个类就类似于AIDL中的客户端服务端, 他们的本质都是通过Binder通信,如果还不太理解的,可以先参考Android的IPC机制–实现AIDL的简单例子。
我们接着看DBMS的addText方法如何添加日志的:
public void addText(String tag, String data) {
try { mService.add(new Entry(tag, 0, data)); } catch (RemoteException e) {}
}
回到DBMS中,查看add方法,主要是日志文件的生成、压缩, IO操作比较多:
public void add(DropBoxManager.Entry entry) {
File temp = null;
OutputStream output = null;
final String tag = entry.getTag();//先取出tag
try {
int flags = entry.getFlags();
if ((flags & DropBoxManager.IS_EMPTY) != 0) throw new IllegalArgumentException();
init(); //先做初始化工作,如生产dropbox目录、统计已有文件的大小等
if (!isTagEnabled(tag)) return;//如果tag被禁止,则不能生产日志文件
long max = trimToFit();
long lastTrim = System.currentTimeMillis();
byte[] buffer = new byte[mBlockSize];
InputStream input = entry.getInputStream();
//在决定是否压缩数据前,至少要有一块数据
int read = 0;
while (read < buffer.length) {
int n = input.read(buffer, read, buffer.length - read);
if (n <= 0) break;
read += n;
}
// 如果至少有一块数据,则进行压缩,否则以不压缩形式写入数据
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)) {
//如果要压缩,就输出到压缩文件
//DBMS很珍惜data分区,文件size大于一个mBlockSize,则一定要压缩,节省空间
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
}
...
} while (read > 0);
...
}
上述代码主要是将日志通过IO写入dropbox日志目录中,压缩过程能将几十KB 压缩到个位数KB大小. 这些代码细节可以看到Google深厚的功力,值得我们学习。
SettingsProvider和Settings数据库
在Android系统中, DBMS以及SystemServer中很多服务都依赖一些配置项,这些配置项都是通过SettingsProvider操作Settings数据库来设置和查询的。
SettingsProvider是系统中很重要的一个APK,如果删除了,系统就不能正常启动。浙西配置都在Settings数据库的Secure表内,不过也有很多选项在此表内没有设置,都是实际运行时使用代码中的默认值。
感兴趣的可以通过adb shell 进入 /data/data/com.android.providers.settings/database目录,用sqlite命令操作settings.db,查看其中的secure表。
本文和DBMS相关的系统Settings配置项有以下:
//用来判断是否允许生成该tag类型的日志文件,默认允许任何类型
Secure.DROPBOX_TAG_PREFIX+tag
: "dropbos"+tag
//用于控制每个日志文件的存活时间,默认三天,大于三天会被删除
Secure.DROPBOX_AGE_SECONDS : "dropbox_age_seconds"
//用于控制文件个数,默认1000个
Secure.DROPBOX_MAX_FILES : "dropbox_max_files"
//dropbox目录占存储的比例,默认最大10%
Secure.DROPBOX_QUOTA_PERCENT: "dopxbox_quota_percent"
//不允许日志使用的内存空间比例,默认10%,即日志最大只能使用90%空间
Secure.DROPBOX_RESERVE_PRECENT : "dropbox_reserve_percent"
//dropbox最大使用空间, 默认5MB
Secure.DROPBOX_QUOTA_KB : "dropbox_quota_kb"
参考《深入理解Android》
分析源码为(android 22)
最后
以上就是甜甜小鸭子为你收集整理的Android源码解析--dropbox日志:DropBoxManagerService(DBMS)服务详解的全部内容,希望文章能够帮你解决Android源码解析--dropbox日志:DropBoxManagerService(DBMS)服务详解所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复