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 城市的开发人员可以在系统出现问题时,从记录局获取详细的事件报告,快速定位和解决问题,确保城市的正常运转。

相关推荐
敲代码的剑缘一心1 小时前
手把手教你学会写 Gradle 插件
android·gradle
青蛙娃娃1 小时前
漫画Android:动画是如何实现的?
android·android studio
aningxiaoxixi2 小时前
android 之 CALL
android
用户2018792831673 小时前
Android 核心大管家 ActivityManagerService (AMS)
android
春马与夏4 小时前
Android自动化AirScript
android·运维·自动化
键盘歌唱家5 小时前
mysql索引失效
android·数据库·mysql
webbin6 小时前
Compose @Immutable注解
android·android jetpack
无知的前端6 小时前
Flutter开发,GetX框架路由相关详细示例
android·flutter·ios
玲小珑6 小时前
Auto.js 入门指南(十二)网络请求与数据交互
android·前端
webbin6 小时前
Compose 副作用
android·android jetpack