💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
要想理清一个Service,最面好从它提供的服务开始进行分析。根据前面对DBMS的介绍可知,它提供了记录系统运行时日志信息的功能,所以这里先从dropbox日志文件的生成时说起。 当某个应用程序因为发生异常而崩溃(crash)时,ActivityManagerService(简称AMS,下同)的handleApplicationCrash函数被调用,其代码如下: **ActivityManagerService.java** ~~~ public void handleApplicationCrash(IBinder app, ApplicationErrorReport.CrashInfocrashInfo) { ProcessRecordr = findAppProcess(app, "Crash"); ...... //调用addErrorToDropBox函数,第一个参数是一个字符串,为“crash” addErrorToDropBox("crash",r, null, null, null, null, null, crashInfo); ...... } ~~~ 下面来看addErrorToDropBox函数: **ActivityManagerService.java** ~~~ public void addErrorToDropBox(String eventType, ProcessRecord process, ActivityRecord activity, ActivityRecordparent, String subject, final String report, final File logFile, final ApplicationErrorReport.CrashInfo crashInfo) { /* dropbox日志文件的命名有一定的规则,其前缀都是一个特定的tag(标签), tag由两部分组成,合起来是”进程类型”_”事件类型”。 下边代码中的processClass函数返回该进程的类型,包括“system_server”、“system_app” 和“data_app”三种。eventType用于指定事件类型,目前也有三种类型:“crash“、”wtf“ (what aterrible failure)和“anr” */ finalString dropboxTag = processClass(process) + "_" + eventType; //获取DBMS Bn端的对象DropBoxManager final DropBoxManager dbox = (DropBoxManager) mContext.getSystemService(Context.DROPBOX_SERVICE); /* 对于DBMS,不仅通过tag于标示文件名,还可以根据配置的情况,允许或禁止特定tag日志 文件的记录。isTagEnable将判断DBMS是否禁止该标签,如果该tag已被禁止,则不允许记 录日志文件 */ if(dbox == null || !dbox.isTagEnabled(dropboxTag)) return; //创建一个StringBuilder,用于保存日志信息 final StringBuilder sb = new StringBuilder(1024); appendDropBoxProcessHeaders(process, sb); ......//将信息保存到字符串sb中 //单独启动一个线程用于向DBMS添加信息 Thread worker = new Thread("Error dump: " + dropboxTag) { @Override public void run() { if (report != null) { sb.append(report); } if (logFile != null) { try {//如果有log文件,那么就把log文件内容读到sb中 sb.append(FileUtils.readTextFile(logFile, 128 * 1024,"\n\n[[TRUNCATED]]")); } ...... } //读取crashInfo信息,一般记录的是调用堆栈信息 if (crashInfo != null && crashInfo.stackTrace != null) { sb.append(crashInfo.stackTrace); } String setting = Settings.Secure.ERROR_LOGCAT_PREFIX + dropboxTag; //查询Settings数据库,判断该tag类型的日志是否对所记录的信息有行数限制, //例如某些tag的日志文件只准记录1000行的信息 int lines =Settings.Secure.getInt(mContext.getContentResolver(), setting, 0); if (lines > 0) { sb.append("\n"); InputStreamReader input =null; try { //创建一个新进程以运行logcat,后面的参数都是logcat常用的参数 java.lang.Processlogcat = new ProcessBuilder("/system/bin/logcat", "-v","time", "-b", "events", "-b","system", "-b", "main", "-t", String.valueOf(lines)) .redirectErrorStream(true).start(); //由于新进程的输出已经重定向,因此这里可以获取最后lines行的信息, //不熟悉ProcessBuidler的读者可以查看SDK中关于它的用法说明 ...... } } //调用DBMS的addText dbox.addText(dropboxTag, sb.toString()); } }; if(process == null || process.pid == MY_PID) { worker.run(); //如果是SystemServer进程crash了,则不能在别的线程执行 }else { worker.start(); } } ~~~ 由上面代码可知,addErrorToDropBox在生成日志的内容上花了不少工夫,因为这些是最重要的,最后仅调用addText函数便将内容传给DBMS的功能。 addText函数定义在DropBoxManager类中,代码如下: **DropBoxManager.java** ~~~ public void addText(String tag, String data) { /* mService和DBMS交互。DBMS对外只提供一个add函数用于日志添加,而DBM提供了3个函数, 分别是addText、addData、addFile,以方便我们的使用 */ try {mService.add(new Entry(tag, 0, data)); } ...... } ~~~ DBM向DBMS传递的数据被封装在一个Entry中。下面来看DBMS的add函数,其代码如下: **DropBoxManagerService.java** ~~~ public void add(DropBoxManager.Entry entry) { Filetemp = null; OutputStream output = null; finalString tag = entry.getTag();//先取出这个Entry的tag try{ int flags = entry.getFlags(); ...... //做一些初始化工作,包括生成dropbox目录、统计当前已有的dropbox文件信息等 init(); if (!isTagEnabled(tag)) return;//如果该tag被禁止,则不能生成日志文件 long max = trimToFit(); long lastTrim = System.currentTimeMillis(); //BlockSize一般是4KB byte[] buffer = new byte[mBlockSize]; //从Entry中得到一个输入流。与Java I/O相关的类比较多,且用法非常灵活 //建议读者阅读《Java编程思想》中“Java I/O系统”一章 InputStreaminput = entry.getInputStream(); ...... int read = 0; while (read < buffer.length) { int n = input.read(buffer, read, buffer.length - read); if (n <= 0) break; read += n; } //先生成一个临时文件,命名方式为”drop线程id.tmp” 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); //生成GZIP压缩文件 if (read == buffer.length && ((flags &DropBoxManager.IS_GZIPPED) == 0)) { output = new GZIPOutputStream(output); flags = flags | DropBoxManager.IS_GZIPPED; } /* DBMS很珍惜/data分区,若所生成文件的size大于一个BlockSize, 则一定要先压缩。 */ ......//写文件,这段代码非常繁琐,其主要目的是尽量节省存储空间 /* 生成一个EntryFile对象,并保存到DBMS内部的一个数据容器中。 DBMS除了负责生成文件外,还提供查询的功能,这个功能由getNextEntry完成。 另外,刚才生成的临时文件在createEntry函数中会重命为带标签的名字, 读者可自行分析createEntry函数 */ long time = createEntry(temp, tag, flags); temp = null; Intent dropboxIntent = new Intent(DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED); dropboxIntent.putExtra(DropBoxManager.EXTRA_TAG, tag); dropboxIntent.putExtra(DropBoxManager.EXTRA_TIME, time); if (!mBooted) { dropboxIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); } //发送DROPBOX_ENTRY_ADDED广播。系统中目前还没有程序接收该广播 mContext.sendBroadcast(dropboxIntent, android.Manifest.permission.READ_LOGS); }...... } ~~~ 上面代码中略去了DBMS写文件的部分,我们从代码注释中可获悉,DBMS非常珍惜/data分区的空间,每一个日志文件都需要考虑压缩以节省存储空间。如果说细节体现功力,那么这正是一个极好的例证。 一个真实设备上/data/system/dropbox目录的内容如图3-2所示。 :-: ![](http://img.blog.csdn.net/20150803104313385?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center) 图3-2 真实设备中dropbox目录的内容 图3-2中最后一项data_app_anr@1324836096560.txt.gz的大小是6.1KB,该文件解压后得到的文件大小是42kB。看来,压缩确实节省了不少存储空间。 另外,我们从图3-2中还发现了其他不同的tag,如SYSTEM_BOOT、SYSTEM_TOMBSTONE等,这些都是由BootReceiver在收到BOOT_COMPLETE广播后收集相关信息并传递给DBMS而生成的日志文件。