要想理清一個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所示。
:-: 
圖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而生成的日志文件。
- 前言
- 第1章 搭建Android源碼工作環境
- 1.1 Android系統架構
- 1.2 搭建開發環境
- 1.2.1 下載源碼
- 1.2.2 編譯源碼
- 1.2.3 利用Eclipse調試system_process
- 1.3 本章小結
- 第2章 深入理解Java Binder和MessageQueue
- 2.1 概述
- 2.2 Java層中的Binder架構分析
- 2.2.1 Binder架構總覽
- 2.2.2 初始化Java層Binder框架
- 2.2.3 addService實例分析
- 2.2.4 Java層Binder架構總結
- 2.3 心系兩界的MessageQueue
- 2.3.1 MessageQueue的創建
- 2.3.2 提取消息
- 2.3.3 nativePollOnce函數分析
- 2.3.4 MessageQueue總結
- 2.4 本章小結
- 第3章 深入理解SystemServer
- 3.1 概述
- 3.2 SystemServer分析
- 3.2.1 main函數分析
- 3.2.2 Service群英會
- 3.3 EntropyService分析
- 3.4 DropBoxManagerService分析
- 3.4.1 DBMS構造函數分析
- 3.4.2 dropbox日志文件的添加
- 3.4.3 DBMS和settings數據庫
- 3.5 DiskStatsService和DeviceStorageMonitorService分析
- 3.5.1 DiskStatsService分析
- 3.5.2 DeviceStorageManagerService分析
- 3.6 SamplingProfilerService分析
- 3.6.1 SamplingProfilerService構造函數分析
- 3.6.2 SamplingProfilerIntegration分析
- 3.7 ClipboardService分析
- 3.7.1 復制數據到剪貼板
- 3.7.2 從剪切板粘貼數據
- 3.7.3 CBS中的權限管理
- 3.8 本章小結
- 第4章 深入理解PackageManagerService
- 4.1 概述
- 4.2 初識PackageManagerService
- 4.3 PKMS的main函數分析
- 4.3.1 構造函數分析之前期準備工作
- 4.3.2 構造函數分析之掃描Package
- 4.3.3 構造函數分析之掃尾工作
- 4.3.4 PKMS構造函數總結
- 4.4 APK Installation分析
- 4.4.1 adb install分析
- 4.4.2 pm分析
- 4.4.3 installPackageWithVerification函數分析
- 4.4.4 APK 安裝流程總結
- 4.4.5 Verification介紹
- 4.5 queryIntentActivities分析
- 4.5.1 Intent及IntentFilter介紹
- 4.5.2 Activity信息的管理
- 4.5.3 Intent 匹配查詢分析
- 4.5.4 queryIntentActivities總結
- 4.6 installd及UserManager介紹
- 4.6.1 installd介紹
- 4.6.2 UserManager介紹
- 4.7 本章學習指導
- 4.8 本章小結
- 第5章 深入理解PowerManagerService
- 5.1 概述
- 5.2 初識PowerManagerService
- 5.2.1 PMS構造函數分析
- 5.2.2 init分析
- 5.2.3 systemReady分析
- 5.2.4 BootComplete處理
- 5.2.5 初識PowerManagerService總結
- 5.3 PMS WakeLock分析
- 5.3.1 WakeLock客戶端分析
- 5.3.2 PMS acquireWakeLock分析
- 5.3.3 Power類及LightService類介紹
- 5.3.4 WakeLock總結
- 5.4 userActivity及Power按鍵處理分析
- 5.4.1 userActivity分析
- 5.4.2 Power按鍵處理分析
- 5.5 BatteryService及BatteryStatsService分析
- 5.5.1 BatteryService分析
- 5.5.2 BatteryStatsService分析
- 5.5.3 BatteryService及BatteryStatsService總結
- 5.6 本章學習指導
- 5.7 本章小結
- 第6章 深入理解ActivityManagerService
- 6.1 概述
- 6.2 初識ActivityManagerService
- 6.2.1 ActivityManagerService的main函數分析
- 6.2.2 AMS的 setSystemProcess分析
- 6.2.3 AMS的 installSystemProviders函數分析
- 6.2.4 AMS的 systemReady分析
- 6.2.5 初識ActivityManagerService總結
- 6.3 startActivity分析
- 6.3.1 從am說起
- 6.3.2 AMS的startActivityAndWait函數分析
- 6.3.3 startActivityLocked分析
- 6.4 Broadcast和BroadcastReceiver分析
- 6.4.1 registerReceiver流程分析
- 6.4.2 sendBroadcast流程分析
- 6.4.3 BROADCAST_INTENT_MSG消息處理函數
- 6.4.4 應用進程處理廣播分析
- 6.4.5 廣播處理總結
- 6.5 startService之按圖索驥
- 6.5.1 Service知識介紹
- 6.5.2 startService流程圖
- 6.6 AMS中的進程管理
- 6.6.1 Linux進程管理介紹
- 6.6.2 關于Android中的進程管理的介紹
- 6.6.3 AMS進程管理函數分析
- 6.6.4 AMS進程管理總結
- 6.7 App的 Crash處理
- 6.7.1 應用進程的Crash處理
- 6.7.2 AMS的handleApplicationCrash分析
- 6.7.3 AppDeathRecipient binderDied分析
- 6.7.4 App的Crash處理總結
- 6.8 本章學習指導
- 6.9 本章小結
- 第7章 深入理解ContentProvider
- 7.1 概述
- 7.2 MediaProvider的啟動及創建
- 7.2.1 Context的getContentResolver函數分析
- 7.2.2 MediaStore.Image.Media的query函數分析
- 7.2.3 MediaProvider的啟動及創建總結
- 7.3 SQLite創建數據庫分析
- 7.3.1 SQLite及SQLiteDatabase家族
- 7.3.2 MediaProvider創建數據庫分析
- 7.3.3 SQLiteDatabase創建數據庫的分析總結
- 7.4 Cursor 的query函數的實現分析
- 7.4.1 提取query關鍵點
- 7.4.2 MediaProvider 的query分析
- 7.4.3 query關鍵點分析
- 7.4.4 Cursor query實現分析總結
- 7.5 Cursor close函數實現分析
- 7.5.1 客戶端close的分析
- 7.5.2 服務端close的分析
- 7.5.3 finalize函數分析
- 7.5.4 Cursor close函數總結
- 7.6 ContentResolver openAssetFileDescriptor函數分析
- 7.6.1 openAssetFileDescriptor之客戶端調用分析
- 7.6.2 ContentProvider的 openTypedAssetFile函數分析
- 7.6.3 跨進程傳遞文件描述符的探討
- 7.6.4 openAssetFileDescriptor函數分析總結
- 7.7 本章學習指導
- 7.8 本章小結
- 第8章 深入理解ContentService和AccountManagerService
- 8.1 概述
- 8.2 數據更新通知機制分析
- 8.2.1 初識ContentService
- 8.2.2 ContentResovler 的registerContentObserver分析
- 8.2.3 ContentResolver的 notifyChange分析
- 8.2.4 數據更新通知機制總結和深入探討
- 8.3 AccountManagerService分析
- 8.3.1 初識AccountManagerService
- 8.3.2 AccountManager addAccount分析
- 8.3.3 AccountManagerService的分析總結
- 8.4 數據同步管理SyncManager分析
- 8.4.1 初識SyncManager
- 8.4.2 ContentResolver 的requestSync分析
- 8.4.3 數據同步管理SyncManager分析總結
- 8.5 本章學習指導
- 8.6 本章小結