Android 城市事件记录局:DropBoxManagerService 的工作日常

一、记录局的成立:DBMS 的启动流程

在 Android 城市的市政厅(SystemServer)里,有一个专门负责记录城市重大事件的部门 ------ 事件记录局(DropBoxManagerService,简称 DBMS)。当城市开始新一天的运转时,市政厅秘书长会首先宣布记录局成立:

java

csharp 复制代码
// 市政厅启动代码(SystemServer.java)
private void startOtherServices() {
    // 成立事件记录局,地址设在/data/system/dropbox
    ServiceManager.addService("dropbox", 
        new DropBoxManagerService(城市, new File("/data/system/dropbox")));
}

记录局的筹备工作

记录局局长上任后,首先要做的是筹备办公室:

java

scss 复制代码
// 记录局局长的初始化工作(DropBoxManagerService.java)
public DropBoxManagerService(城市, 地址) {
    mDropBoxDir = 地址; // 办公室地址
    mContext = 城市;
    
    // 安装城市广播接收器,监听重要事件
    IntentFilter filter = new IntentFilter();
    filter.addAction("存储警报"); // 存储空间不足时的警报
    filter.addAction("城市启动完成"); // 城市启动完毕的通知
    城市.registerReceiver(记录员, filter);
    
    // 监听城市规划变更(Settings变化)
    mContentResolver.registerContentObserver(
        城市规划URI, true, new ContentObserver(新Handler()) {
            public void onChange(是否变更) {
                记录员.onReceive(城市, null);
            }
        });
    
    // 准备消息处理员
    mHandler = new Handler() {
        public void handleMessage(消息 msg) {
            if (msg.what == 发送广播) {
                城市.sendBroadcastAsUser((Intent)msg.obj, 市长, 读取日志权限);
            }
        }
    };
}

记录员的日常工作

当记录局收到广播通知(如城市启动完成、存储警报等),记录员会立即开始工作:

java

typescript 复制代码
// 记录员的工作流程(DropBoxManagerService.java)
private final 广播记录员 mReceiver = new 广播记录员() {
    public void onReceive(城市, 广播 intent) {
        if (广播是启动完成) {
            mBooted = true;
            return;
        }
        
        // 重置存储检查时间
        mCachedQuotaUptimeMillis = 0; 
        
        // 开启后台工作线程
        new Thread() {
            public void run() {
                try {
                    筹备办公室(); // 初始化目录和文件
                    整理文件(); // 清理过期文件
                } catch (IOException e) {
                    // 处理异常
                }
            }
        }.start();
    }
};

筹备办公室:初始化目录和文件

java

ini 复制代码
private synchronized void 筹备办公室() throws IOException {
    if (mStatFs == null) {
        // 创建办公室目录,如果不存在
        if (!mDropBoxDir.isDirectory() && !mDropBoxDir.mkdirs()) {
            // 处理创建失败
        }
        mStatFs = new StatFs(mDropBoxDir.getPath());
        mBlockSize = mStatFs.getBlockSize(); // 文件块大小4096字节
    }

    if (mAllFiles == null) {
        // 列出所有文件
        File[] files = mDropBoxDir.listFiles();
        mAllFiles = new FileList();
        mFilesByTag = new HashMap<String, FileList>();

        for (File file : files) {
            if (文件是临时文件) {
                file.delete(); // 删除临时文件
                continue;
            }
            // 创建文件记录对象
            EntryFile entry = new EntryFile(file, mBlockSize);
            if (entry.tag == null) {
                continue; // 忽略无标签文件
            } else if (entry.timestampMillis == 0) {
                file.delete(); // 删除时间戳为0的文件
                continue;
            }
            // 登记文件记录
            enrollEntry(entry);
        }
    }
}

整理文件:清理过期记录

java

java 复制代码
private synchronized long 整理文件() {
    // 获取配置参数
    int 保存天数 = Settings.Global.getInt(城市规划, 
            Settings.Global.DROPBOX_AGE_SECONDS, 3天);
    int 最大文件数 = Settings.Global.getInt(城市规划, 
            Settings.Global.DROPBOX_MAX_FILES, 1000);
    long 过期时间 = 当前时间 - 保存天数 * 1000;
    
    // 删除过期或超出数量的文件
    while (!mAllFiles.contents.isEmpty()) {
        EntryFile entry = mAllFiles.contents.first();
        if (entry.timestampMillis > 过期时间 && mAllFiles.contents.size() < 最大文件数) break;
        // 移除文件记录并删除物理文件
        // ...
    }
    
    // 检查存储空间使用情况
    long 系统运行时间 = SystemClock.uptimeMillis();
    if (系统运行时间 > mCachedQuotaUptimeMillis + 5秒) {
        // 重新计算存储配额
        int 配额百分比 = Settings.Global.getInt(城市规划, 
                Settings.Global.DROPBOX_QUOTA_PERCENT, 10);
        int 保留百分比 = Settings.Global.getInt(城市规划, 
                Settings.Global.DROPBOX_RESERVE_PERCENT, 10);
        int 配额大小 = Settings.Global.getInt(城市规划, 
                Settings.Global.DROPBOX_QUOTA_KB, 5*1024);
        mStatFs.restat(mDropBoxDir.getPath());
        int 可用空间 = mStatFs.getAvailableBlocks();
        int 非保留空间 = 可用空间 - mStatFs.getBlockCount() * 保留百分比 / 100;
        int 最大配额块数 = 配额大小 * 1024 / mBlockSize;
        mCachedQuotaBlocks = Math.min(最大配额块数, Math.max(0, 非保留空间 * 配额百分比 / 100));
        mCachedQuotaUptimeMillis = 系统运行时间;
    }
    
    // 当文件占用超过配额时,按标签公平清理
    if (mAllFiles.blocks > mCachedQuotaBlocks) {
        // 公平分配配额并清理旧文件
        // ...
    }

    return mCachedQuotaBlocks * mBlockSize;
}

二、事件记录:DBMS 处理系统异常

当 Android 城市中发生重大事件(如应用崩溃、ANR 等),城市警察局(AMS)会通知记录局来收集证据:

java

arduino 复制代码
// 警察局的事件报告方法(ActivityManagerService.java)
public void 报告事件(String 事件类型,
        进程记录 process, String 进程名, ActivityRecord activity,
        ActivityRecord parent, String 事件主题,
        final String 报告内容, final File 日志文件,
        final 崩溃信息 crashInfo) {

    // 生成事件标签(如"system_server_crash")
    final String 标签 = 进程类别(process) + "_" + 事件类型;
    // 获取记录局代理
    final DropBoxManager 记录局 = (DropBoxManager)
            mContext.getSystemService(记录局服务);

    if (记录局 == null || !记录局.isTagEnabled(标签)) return;

    final StringBuilder 报告 = new StringBuilder(1024);
    // 添加进程头部信息
    添加进程信息(process, 进程名, 报告);
    // 添加事件主题和构建信息
    if (事件主题 != null) {
        报告.append("Subject: ").append(事件主题).append("\n");
    }
    报告.append("Build: ").append(Build.FINGERPRINT).append("\n");
    报告.append("\n");

    // 开启后台线程处理,避免阻塞
    Thread 工作者 = new Thread("Error dump: " + 标签) {
        @Override
        public void run() {
            if (报告内容 != null) {
                报告.append(报告内容); // 添加ANR时的CPU信息或内存信息
            }
            if (日志文件 != null) {
                // 添加traces文件内容,最大256KB
                报告.append(FileUtils.readTextFile(日志文件, 最大大小, "\n\n[[TRUNCATED]]"));
            }
            if (crashInfo != null && crashInfo.stackTrace != null) {
                报告.append(crashInfo.stackTrace); // 添加崩溃堆栈
            }

            // 添加相关logcat日志
            int 日志行数 = Settings.Global.getInt(mContext.getContentResolver(), 
                    Settings.Global.ERROR_LOGCAT_PREFIX + 标签, 0);
            if (日志行数 > 0) {
                // 执行logcat命令获取日志
                java.lang.Process logcat = new ProcessBuilder("/system/bin/logcat",
                        "-v", "time", "-b", "events", "-b", "system", "-b", "main",
                        "-b", "crash", "-t", String.valueOf(日志行数))
                        .redirectErrorStream(true).start();

                // 读取logcat输出并添加到报告
                // ...
            }
            // 将报告提交给记录局
            记录局.addText(标签, 报告.toString());
        }
    };

    if (process == null) {
        // 系统进程崩溃时直接处理
        工作者.run();
    } else {
        工作者.start(); // 普通情况开启后台线程
    }
}

事件分类:标签的含义

记录局对不同事件使用不同标签,例如:

java

arduino 复制代码
// 进程分类方法(ActivityManagerService.java)
private static String 进程类别(进程记录 process) {
    if (process == null || process.pid == 市政厅进程ID) {
        return "system_server"; // 市政厅进程
    } else if ((process.info.flags & 系统应用标志) != 0) {
        return "system_app"; // 系统应用
    } else {
        return "data_app"; // 普通应用
    }
}

常见标签及其含义:

  • system_server_anr:市政厅进程无响应
  • system_server_crash:市政厅进程崩溃
  • data_app_crash:普通应用崩溃
  • system_app_anr:系统应用无响应

记录局的文件存储

记录局收到事件报告后,会将信息存储为文件:

java

scss 复制代码
// 记录局的文件存储流程(DropBoxManagerService.java)
public void add(DropBoxManager.Entry 事件) {
    File 临时文件 = null;
    OutputStream 输出 = null;
    final String 标签 = 事件.getTag();
    try {
        int 标志 = 事件.getFlags();
        筹备办公室(); // 初始化
        long 最大空间 = 整理文件(); // 清理空间
        long 上次整理时间 = System.currentTimeMillis();
        byte[] 缓冲区 = new byte[mBlockSize];
        InputStream 输入 = 事件.getInputStream();

        // 读取事件数据
        int 读取大小 = 0;
        while (读取大小 < 缓冲区.length) {
            int n = 输入.read(缓冲区, 读取大小, 缓冲区.length - 读取大小);
            if (n <= 0) break;
            读取大小 += n;
        }
        
        // 创建临时文件
        临时文件 = new File(mDropBoxDir, "drop" + Thread.currentThread().getId() + ".tmp");
        int 缓冲区大小 = mBlockSize;
        if (缓冲区大小 > 4096) 缓冲区大小 = 4096;
        if (缓冲区大小 < 512) 缓冲区大小 = 512;
        FileOutputStream 文件输出 = new FileOutputStream(临时文件);
        输出 = new BufferedOutputStream(文件输出, 缓冲区大小);
        
        // 压缩文件(如果需要)
        if (读取大小 == 缓冲区.length && (标志 & 是否压缩) == 0) {
            输出 = new GZIPOutputStream(输出);
            标志 = 标志 | 是否压缩;
        }
        
        // 写入数据并持续清理空间
        do {
            输出.write(缓冲区, 0, 读取大小);
            long 当前时间 = System.currentTimeMillis();
            if (当前时间 - 上次整理时间 > 30秒) {
                最大空间 = 整理文件(); // 超过30秒则清理空间
                上次整理时间 = 当前时间;
            }

            读取大小 = 输入.read(缓冲区);
            if (读取大小 <= 0) {
                FileUtils.sync(文件输出);
                输出.close();
                输出 = null;
            } else {
                输出.flush();
            }

            long 文件大小 = 临时文件.length();
            if (文件大小 > 最大空间) {
                临时文件.delete();
                临时文件 = null;
                break;
            }
        } while (读取大小 > 0);
        
        // 创建正式记录文件
        long 时间戳 = 创建记录(临时文件, 标签, 标志);
        临时文件 = null;
        
        // 发送新记录广播
        final Intent 新记录广播 = new Intent(DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED);
        新记录广播.putExtra(标签, 标签);
        新记录广播.putExtra(时间, 时间戳);
        mHandler.sendMessage(mHandler.obtainMessage(发送广播, 新记录广播));
    } catch (IOException e) {
        // 处理异常
    } finally {
        // 清理资源
    }
}

三、记录局的工作总结

DBMS 的核心职责

  • 存储目录:所有记录保存在/data/system/dropbox目录

  • 记录事件:当系统发生以下事件时记录信息:

    • 应用崩溃(crash)
    • 应用无响应(anr)
    • 严重错误(wtf)
    • 内存不足(lowmem)
    • Watchdog 事件(watchdog)
  • 记录内容:

    • 进程信息(进程名、flags、包名及版本)
    • 事件相关日志(logcat)
    • 崩溃堆栈(crash 时)
    • CPU 信息(ANR 时)

文件管理策略

  • 过期时间:默认 3 天

  • 最大文件数:默认 1000 个

  • 最大存储空间:默认 5MB

  • 当空间不足时,按标签公平清理旧文件

通过这套完整的事件记录系统,Android 城市的开发人员可以在系统出现问题时,从记录局获取详细的事件报告,快速定位和解决问题,确保城市的正常运转。

相关推荐
TechMerger9 小时前
Android 17 重磅重构!服役 20 年的 MessageQueue 迎来无锁改造,卡顿大幅优化!
android·性能优化
yuhuofei202111 小时前
【Python入门】Python中字符串相关拓展
android·java·python
dalancon12 小时前
Android Input Spy Window
android
dalancon13 小时前
InputDispatcher派发事件,查找目标窗口
android
我命由我1234513 小时前
Android Framework P3 - MediaServer 进程、认识 ServiceManager 进程
android·c语言·开发语言·c++·visualstudio·visual studio·android runtime
天才少年曾牛14 小时前
Android14 新增系统服务后,应用调用出现 “hidden api” 警告的原因与解决方案
android·frameworks
赏金术士14 小时前
Jetpack Compose 底部导航实战教程(完整版)
android·kotlin·compose
随遇丿而安15 小时前
第5周:XML 资源、样式和主题,真正解决的是“页面以后还改不改得动”
android
zh_xuan15 小时前
Android 获取系统内存页大小:sysconf(_SC_PAGESIZE) 与 JNI 实现
android·jni·ndk·内存页大小
fundroid17 小时前
Google I/O 2026 | Android 全面进化:从操作系统到“智能中枢”
android·jetpack compose·google i/o 2026