我是靠谱客的博主 甜甜小鸭子,这篇文章主要介绍Android源码解析--dropbox日志:DropBoxManagerService(DBMS)服务详解,现在分享给大家,希望可以做个参考。

DropBoxManagerService

简介

DropBoxManagerService(简称DBMS)是日志相关的服务,用于生成与管理 系统运行时的一些日志文件。日志文件大多记录的是系统或某个应用出错的日志信息。该日志输出在dropbox目录下

它在SystemServer启动以后被添加到ServiceManager中:

复制代码
1
2
3
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的构造函数如下:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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函数:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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方法内部做了什么操作:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
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方法如何添加日志的:

复制代码
1
2
3
4
public void addText(String tag, String data) { try { mService.add(new Entry(tag, 0, data)); } catch (RemoteException e) {} }

回到DBMS中,查看add方法,主要是日志文件的生成、压缩, IO操作比较多:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
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配置项有以下

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//用来判断是否允许生成该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日志内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(84)

评论列表共有 0 条评论

立即
投稿
返回
顶部