Android源码解析--dropbox日志:DropBoxManagerService(DBMS)服务详解

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: Connected\n");
    }
    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,
                                "\n\n[[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)

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值