写点什么

Android 系统服务 DropBoxManagerService 详解与实践应用

  • 2023-03-27
    广东
  • 本文字数:5837 字

    阅读完需:约 19 分钟

作者:vivo 互联网客户端团队- Ma Lian


借助系统 DropBoxManagerService 对于系统文件目录 dropbox 管理的设计,了解其文件管理的规则、运行机制、读写机制、管控机制,根据其设计一个客户端日志文件管理与上报功能

一、背景

随着公司应用的逐渐增多,需要集中收集公司部分应用线上运行的一些崩溃数据和日志来进行分析处理,在此实践过程中了解到系统 data/system/dropbox 目录会生成所有应用的相关日志文件。


这个目录是由 Android 系统服务之一 DropBoxManagerService 来管理,所以由此详细阅读了 DropBoxManagerService 相关的源码,以下简称 DBMS。


DBMS 可能是 Android 系统服务源码较少的一个,所以阅读起来相对比较简单,阅读之后发现,其实这就是一个简易的日志文件管理服务。


我们在对应用本地的部分日志文件进行记录和管理的时候,恰巧可以借鉴 DBMS 源码对于文件管理的设计方案。

假设不读源码,如果我们自己设计日志文件管理系统,应该需要考虑哪些?


除了最基础的获取各类日志文件的方案,我们针对文件管理可以提出几个需要考虑的点:

  1. 存取日志采用何种策略

  2. 设计哪些防呆策略

  3. 是否需要对外提供接口,提供哪些接口

  4. 如何保证性能

  5. 多进程的问题如何解决

  6. 文件丢失该如何处理

  7. 文件变化如何通知使用方


我们带着以上问题来对 DBMS 进行一个了解。

二、DropBoxManagerService 简介

DropBoxManagerService 是 Android 系统的服务之一,采用 C/S 结构:


  • Client 端:DropboxManager,用于对应用层提供接口。

  • Server 端:DropBoxManagerService,管理系统目录(data/system/dropbox)的系统服务。

  • 系统 Setting 数据库:负责管理 DBMS 的一些配置信息。


整体架构关系如下图所示:



2.1 DropBox 目录简介

这个目录的目录结构如下图所示:



里面存放的都是系统的一些日志文件,针对不同类型的文件,文件名称和后缀也有所不同。

2.1.1 文件格式

tag@timeStampMillis.extentions

  • tag:代表日志类型,常见的 tag:data_app_anr,system_app_crash,data_app_nativecrash,其中 data_app 表示普通应用,system_app 表示系统应用。

  • timeStampMillis:日志的时间戳,一般情况下等于崩溃的时间,有些情况下系统会做一些调整。

  • extentions:后缀名,常见的文件后缀名:.txt,.lost,.txt.gz,.tmp,一般的日志文件都是.txt 或者.txt.gz,文件被删除后的记录会以.lost 命名


这种文件命名方式优点是可以一眼看出这是什么类型的文件。

2.1.2 常见的文件

  1. JE 文件:data_app_crash@175423252525.txt.gz

  2. NE 文件:data_app_native_crash@123478923458923.txt.gz

  3. ANR 文件:data_app_anr@175423252525.txt.gz


还包括一些系统其它的错误日志,内存,重启相关的等等。

2.2 提供的接口

2.2.1 添加文件

addData/addFile/addEntry

2.2.2 获取文件

getNextEntry,根据 tag 和时间戳来获取想要的文件。

2.2.3 dump 目录信息

获取 DropBox 目录的一些信息:文件个数,文件列表,文件详细信息等,可以通过命令行操作(dumpsys dropbox)。

$ dumpsys dropboxDrop box contents: 131 entriesMax entries: 1000// 以下省略......
复制代码

2.2.4 其它 CMD 命令

提供其他一些 CMD 操作的命令,如 set-rate-limit,add-low-priority 等等。

2.3 目录管控配置

2.3.1 默认基础配置及文件清除策略

这些配置存在系统的 setting 数据库里面,可以通过 settings.global 来访问配置。


文件存储的配置主要包括以下几个维度:

  1. 文件存活时长(默认 3 天);

  2. 最大存储文件数量(默认 1000 个);

  3. 低内存情况下最大文件数量(默认 300 个);

  4. DropBox 目录所能使用的空间(默认 10MB);

  5. DropBox 目录最多占可用存储(可用存储=系统可用存储-系统总存储*预留比例)的比例(10%);

  6. DropBox 使用需要预留的存储占总存储的比例(10%);

  7. 清除空间时扫描磁盘空间的时间间隔;

  8. 需要压缩的最小文件大小。


根据以上配置,我们可以知道该目录下的日志文件清除策略,触发配置上限后会及时的删除文件。


在以下三种情况会执行文件清除策略,防止 DropBox 占用太多的空间:

  1. 设备低内存;

  2. setting 配置发生变更;

  3. 添加文件。


同时在添加文件的时候,超过配置的可占用空间,会被丢弃。

/** * 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 {    return mCachedQuotaBlocks * mBlockSize;}
复制代码

2.3.2 文件删除及标记处理策略

在上述策略不满足后,部分文件会被删除,删除后,会在 DropBox 添加一个.lost 的空文件标记被删除的文件。

2.3.3 文件类型管控

DropBoxMangerService 对于可存储的文件类型也有控制,主要是对于 TAG 的控制。

public boolean isTagEnabled(String tag) {}
复制代码

2.3.4 权限管控

使用 DropBox 需要 READ_LOGS 权限和 PACKAGE_USAGE_STATS 两个权限。

2.4 读写策略

这块涉及到 DBMS 几个关键方法和属性,主要涉及到初始化(init),添加文件(addEntry),获取文件(getNextEntry),文件类型(EntryFile)。


DBMS 作为系统服务会由 SystemServer 启动,添加文件(addEntry)和获取文件(getNextEntry)在调用时会先进行初始化(init)。


其中每个文件都会转换成一个 EntryFile 类来管理,关系见下图:



下面了解一下初始化,EntryFile,添加文件和获取文件的具体内容:


2.4.1 初始化

初始化会将 DropBox 文件列表缓存到内存中。


/** If never run before, scans disk contents to build in-memory tracking data. */private synchronized void init() throws IOException { // 省略代码...... File[] files = mDropBoxDir.listFiles(); // 列出所有文件 for (File file : files) { EntryFile entry = new EntryFile(file, mBlockSize); // 一个日志文件对应一个EntryFile对象 enrollEntry(entry); // 加入到mAllFiles }}
复制代码


初始化的时机:

  • 设备存储容量低广播回调 

  • 设置配置项修改

  • 添加日志文件

  • 获取日志文件

  • dump 命令行列出 DropBox 的一些内容

2.4.2 EntryFile 文件属性

每个文件对应一个 EntryFile,用 block 数来统计大小,DBMS 涉及的读写都是根据磁盘的 blockSize 来进行,效率会更高。

static final class EntryFile implements Comparable<EntryFile> {        public final String tag; // 日志文件的tag,类型        public final long timestampMillis; // 日志文件的时间戳        public final int flags; // 日志文件的flag,标志TEXT,EMPTY,GZIPPED        public final int blocks; // 存放文件的块数}
复制代码

2.4.3 添加文件

添加一个日志文件,常见的在 Ams 中的 addErrorToDropBox 方法调用。


添加文件管控策略

① .lost 的文件格式不允许添加。

// 如果添加.lost的文件,抛异常if ((flags & DropBoxManager.IS_EMPTY) != 0) throw new IllegalArgumentException();
复制代码


② 配置不允许记录的 TAG,不会被添加。

// 从设置里面读取这个tag是否被允许记录if (!isTagEnabled(tag)) return;
复制代码


③ 根据系统设置的磁盘块大小进行写入,提高写入效率。

int bufferSize = mBlockSize;
复制代码


④ 异常时间戳文件矫正:写入文件前会将超过当前时间 10s 的文件修改时间后重新命名并加入到缓存文件列表中。

// 找出当前时间10s之后的所有文件SortedSet<EntryFile> tail = mAllFiles.contents.tailSet(new EntryFile(t + 10000));EntryFile[] future = null;if (!tail.isEmpty()) {    future = tail.toArray(new EntryFile[tail.size()]);    tail.clear();  // 从文件列表中mAllFiles清除掉超过当前时间的}// 省略代码......for (EntryFile late : future) {    if ((late.flags & DropBoxManager.IS_EMPTY) == 0) { // 将这些超过当前时间的文件重命名,时间戳依次+1,并且重新加入到mAllFiles中        enrollEntry(new EntryFile());    }}
复制代码


⑤ 添加文件的顺序,先创建临时文件,然后使用文件的 rename 方法,rename 方法是原子操作,保证并发操作的安全。

// 通过rename方法保存文件,保证并发操作的安全temp.renameTo(file))
复制代码


⑥ 文件添加完成之后通过发送广播通知,广播分为实时广播和延迟广播,延迟广播用来通知优先级较低的文件。

//低优先级的可以发送延时广播mHandler.maybeDeferBroadcast(tag, time);//高优先级的发送实时广播mHandler.sendBroadcast(tag, time);
复制代码

2.4.4 获取文件

DBMS 获取文件的逻辑比较简单,根据方法名 getNextEntry(String tag, long millis,...)我们可以见名知意,主要根据使用者传入的时间戳,找出这个时间戳往后的第一个文件。

for (EntryFile entry : list.contents.tailSet(new EntryFile(millis + 1))) {         return new DropBoxManager.Entry(entry.tag, entry.timestampMillis, file, entry.flags);}
复制代码

2.5 源码阅读总结

2.5.1 回答我们阅读前提出的问题

① 存取日志的策略

  • 会在低存储,添加获取文件等时机将文件列表初始化到内存中。


② 设计哪些防呆策略

  • 提供了文件大小,存储占比等限制。

  • 会在低存储,配置更改的时候清除文件。

  • 配置保存在 setting 中,然后通过 ContentObserver 来监听配置变化。


③ 对外提供哪些接口

  • 提供添加获取,以及 cmd 命令相关的接口,开发调试都能兼顾。


④ 如何保证性能

  • 从源码的注解可以看出,目前每个 Entry 无论大小都对应一个文件效率是比较低,源码也列出了 TODO,考虑用单文件队列来优化。

// TODO: This implementation currently uses one file per entry, which is// inefficient for smallish entries -- consider using a single queue file// per tag (or even globally) instead.
复制代码
  • 采用文件系统块大小来读写来提高效率。


⑤ 多进程的问题如何解决

  • 文件操作都是先写 temp,然后采用 rename 的方案来保证原子操作从而保证并发操作的安全。

  • addEntry 和 getNextEntry 都做了加锁处理。


⑥ 文件丢失该如何处理

  • 文件被删除后,会用一个同名的空文件来替代,从而标记有文件被删除了。

⑦ 文件变化如何通知使用方

  • 通过发广播的方式来通知外界,针对不同优先级的文件又设置实时和延时广播。

2.5.2 其它点

  1. 文件存储不光限制大小,也会限制文件类型

  2. 文件不是全部压缩的,超过一定大小的文件会进行压缩

  3. 文件命名有讲究,包含了应用类型,崩溃信息,发生时间等相关信息

  4. 文件获取是根据时间戳先后来获取的,对于时间戳异常的文件会进行时间上的调整

2.5.3 作为使用者的看法

当然,我在使用源码的过程中,也发现我个人觉得可以优化的点。

  1. 在使用中,部分文件命名应该加上包名,类似应用产生的崩溃文件,可以按包名区分文件,对使用更友好,当然这个设计的初衷是给系统统一使用,可能不对外开放。

  2. 权限管控过于单一,对于业务本身的一些异常日志,应当支持自由查看。

  3. 这些文件的信息应该用数据库维护起来更好,方便使用者用,当然可能设计可能会变得更复杂,不够简约。

三、源码阅读应用–日志文件管理 &上报设计

3.1 概述

背景:

部分应用希望上报应用运行时的一些日志,包括运行时 log,崩溃 log,Hprof 内存快照,捕获异常等等


需求:

需要设计一套客户端的日志文件收集、管理及上报一个功能


参考:

  1. 日志保存管理方案可以参考 DBMS 中的一些策略

  2. 日志上传方案参考业内已有的一些优秀模型

3.2 方案

整体方案方案采用生产者-消费者模型,其中几个关键节点:

  1. 生产者:应用的多个进程,他们可能会生成不同类型的日志,并写入到指定的文件目录

  2. 临时文件目录:根据文件类型、优先级设置不同目录来存放临时文件

  3. 上报数据目录:临时文件目录中的文件会通过 rename 方案写到上报数据目录

  4. 消费者:上报进程,上报进程会通过 FileObserver 监听变化,从而来上报文件


整体的流程图如下:


3.3 确定对外接口

  1. 获取文件的接口

  2. 存文件的接口

  3. 统计文件(类型,数量)的接口

  4. 更改部分配置策略的接口

  5. 主动上报的接口

  6. 其它自定义参数的接口

3.4 确定收集管控策略

  1. 是否允许收集:该配置关闭后,本地不会执行任何收集行为

  2. 日志存储目录:私有目录固化出一个空间

  3. 文件命名方式:参照 DBMS,进程名_日志类型_前后台 @时间戳.txt.gz

  4. 日志类型开关:每个日志类型设置是否允许手机

  5. 收集日志类型:崩溃日志,运行时日志,内存快照,捕获日志,其它自定义日志等

  6. 日志存活时长:参照 DBMS,超过一定时间,则删除文件

  7. 日志存储空间:参照 DBMS,设置一个手机可用存储的比例·

  8. 日志文件数量:超过指定数量,则删除部分文件;参照 DBMS,当可用存储较低的情况,应该存储更少的文件数量

  9. 其余初始化的一些时机,同样参考 DBMS

3.5 确定上报管控策略

  1. 是否允许上报,该配置关闭后,不允许上报行为

  2. 是否允许在流量情况下上报,该配置设置不允许后,只允许在 wifi 情况下上报

  3. 流量情况下单次、单日、单月最多可上报的文件大小,该配置控制流量情况下,应用在上报时可以上报的文件大小

  4. wifi 情况下单次、单日、单月最多可上报的文件大小,该配置控制 wifi 情况下,应用在上报时可以上报的文件大小

  5. 上报间隔时间,该配置控制低优先级的文件上报时间间隔

  6. 上报失败次数限制,该配置控制在失败一定次数以后,不再允许上报

  7. 上报优先级(低优先级的日志无需频繁上报)

  8. 弱网络情况本次上报的文件大小

  9. 单次、单日、单月允许使用的流量大小,该配置控制应用在上报时可以使用的流量大小

  10. 可上报的最低电量限制,该配置控制上报情况下最小电量限制

3.6 收集日志方案

  • DropBox 日志:先读取到本地,然后存储上报

  • 运行时日志:利用 adb logcat 命令输出日志到本地储存上

  • 内存快照:dump Hprof 文件,然后进行一些裁剪,以便于能够以更小的体积上传

  • 其它日志:实时输出记录到本地,按需上报

以上具体方案不作为本次重点,不再详述。

3.7 写入日志方案

通过网络课程的学习,了解到 mmap 的性能非常高,所以最终采用“多进程写+mmap”的方案,并且避免了跨进程的调用堆积,效率很高

3.8 上报日志方案

参照 DBMS 添加文件的实时和延时通知方案,上报也分为实时上报和延时上报

  • 实时上报:出现一份日志,就直接上报,针对重要性较高的日志

  • 延时上报:达到一定数量,或者达到一定时间进行上报

3.9 数据监控

3.9.1 质量监控

3.9.2 容灾监控

四、总结

本文主要讲了两块内容:

1、DropBoxManagerService 源码阅读与解析,包括接口设计、文件存储的管控机制和策略,多进程的处理,异常防呆机制

2、应用日志收集与上报方案,主要参考 DropBoxManagerService 源码的设计


我们经常强调源码阅读,源码究竟能给我们带来什么呢?我认为主要有以下几点:

  • 编码技术的提升

  • 分析问题的思路

  • 解决方案的设计

  • 设计模式的应用


本文抛砖引玉,借助以上案例简单地讲了一下 DBMS 源码以及源码阅读的应用,希望在源码阅读方面能够带给大家一些启发,同时对 Android 系统一些不常见的服务有一个了解。


参考:

  1. Android12.0《DropBoxMangerService 源码》

  2. 极客时间《Android 开发高手课》关于高性能上报方案和高性能 I/O 方案两节

发布于: 刚刚阅读数: 3
用户头像

官方公众号:vivo互联网技术,ID:vivoVMIC 2020-07-10 加入

分享 vivo 互联网技术干货与沙龙活动,推荐最新行业动态与热门会议。

评论

发布
暂无评论
Android系统服务DropBoxManagerService详解与实践应用_文件管理_vivo互联网技术_InfoQ写作社区