·??分析SystemServer
·??分析EntropyService、DropBoxManagerService、DiskStatsService
·??分析DeviceStorageMonitorService、SamplingProfilerService以及ClipboardService
·??SystemServer.java
frameworks/base/services/java/com/android/server/SystemServer.java
·??com_android_server_SystemServer.cpp
frameworks/base/services/jni/com_android_server_SystemServer.cpp
·??System_init.cpp
frameworks/base/cmds/system_server/library/System_init.cpp
·??EntropyService.java
frameworks/base/services/java/com/android/server/EntropyService.java
·??DropBoxManagerService.java
frameworks/base/services/java/com/android/server/DropBoxManagerService.java
·??ActivityManagerService.java
frameworks/base/services/java/com/android/server/am/ActivityManagerService.java
·??DiskStatsService.java
frameworks/base/services/java/com/android/server/DiskStatsService.java
·??dumpsys.cpp
frameworks/base/cmds/dumpsys/dumpsys.cpp
·??DeviceStorageMonitorService.java
frameworks/base/services/java/com/android/server/DeviceStorageMonitorService.java
·??SamplingProfilerService.java
frameworks/base/services/java/com/android/server/SamplingProfilerService.java
·??SamplingProfilerIntegration.java
frameworks/base/core/java/com/android/internal/os/SamplingProfilerIntegration.java
·??SamplingProfiler.java
libcore/dalvik/src/main/java/dalvik/system/profiler/SamplingProfiler.java
·??ClipboardService.java
frameworks/base/services/java/com/android/server/ClipboardService.java
·??ClipboardManager.java(android.content)
frameworks/base/core/java/android/content/ClipboardManager.java
·??ClipboardManager.java(android.text)
frameworks/base/core/java/android/text/ClipboardManager.java
·??ClipData.java
frameworks/base/core/java/android/content/ClipData.java
SystemServer是什么?它可是Android Java世界的兩大支柱之一。另外一個(gè)支柱是專門負(fù)責(zé)孵化Java進(jìn)程的Zygote。這兩大支柱倒了任何一根,都會(huì)導(dǎo)致Android Java世界的崩潰(所有由Zygote孵化的Java進(jìn)程都會(huì)被銷毀。SystemServer就是由Zygote孵化而來)。崩潰之后,幸好Linux系統(tǒng)中的天字號(hào)進(jìn)程init會(huì)重新啟動(dòng)它們以重建Java世界。[①]
SystemServer正如其名,和系統(tǒng)服務(wù)有著重要關(guān)系。Android系統(tǒng)中幾乎所有的核心Service都在這個(gè)進(jìn)程中,如ActivityManagerService、PowerManagerService和WindowManagerService等。那么,作為這些服務(wù)的大本營,SystemServer會(huì)是什么樣的呢?
SystemServer是由Zygote孵化而來的一個(gè)進(jìn)程,通過ps命令,可知其進(jìn)程名為system_server。
SystemServer核心邏輯的入口是main函數(shù),其代碼如下:
[-->SystemServer.java]
public static void main(String[] args) {
? ???if(System.currentTimeMillis() < EARLIEST_SUPPORTED_TIME) {
??????????? //如果系統(tǒng)時(shí)鐘早于1970,則設(shè)置系統(tǒng)時(shí)鐘從1970開始
???????????Slog.w(TAG, "System clock is before 1970; setting to 1970.");
???????????SystemClock.setCurrentTimeMillis(EARLIEST_SUPPORTED_TIME);
??????? }
??????? //判斷性能統(tǒng)計(jì)功能是否開啟
??????? if(SamplingProfilerIntegration.isEnabled()) {
???????????SamplingProfilerIntegration.start();
???????????timer = new Timer();
???????????timer.schedule(new TimerTask() {
???????????????@Override
???????????????public void run() {
???????????????????//SystemServer性能統(tǒng)計(jì),每小時(shí)統(tǒng)計(jì)一次,統(tǒng)計(jì)結(jié)果輸出為文件
???????????????????SamplingProfilerIntegration.writeSnapshot("system_server",
??????????????????????????????????????????????????????null);
???????????????}// SNAPSHOT_INTERVAL定義為1小時(shí)
???????????}, SNAPSHOT_INTERVAL, SNAPSHOT_INTERVAL);
??????? }
??????? //和Dalvik虛擬機(jī)相關(guān)的設(shè)置,主要是內(nèi)存使用方面的控制
???????dalvik.system.VMRuntime.getRuntime().clearGrowthLimit();
???????VMRuntime.getRuntime().setTargetHeapUtilization(0.8f);
??????? //加載動(dòng)態(tài)庫libandroid_servers.so
???????System.loadLibrary("android_servers");
???????init1(args);//調(diào)用native的init1函數(shù)
? }
main函數(shù)首先做一些初始化工作,然后加載動(dòng)態(tài)庫libandroid_servers.so,最后再調(diào)用native的init1函數(shù)。該函數(shù)在libandroid_servers.so庫中實(shí)現(xiàn),其代碼如下:
[-->com_android_server_SystemServer.cpp]
extern "C" int system_init();
static voidandroid_server_SystemServer_init1(JNIEnv* env, jobject clazz)
{
???system_init(); //調(diào)用上面那個(gè)用extern 聲明的system_init函數(shù)
}
?而system_init函數(shù)又在另外一個(gè)庫libsystem_server.so中實(shí)現(xiàn),代碼如下:
[-->System_init.cpp]
extern "C" status_t system_init()
{
???? LOGI("Enteredsystem_init()");
???? //初始化Binder系統(tǒng)
???sp<ProcessState> proc(ProcessState::self());
???? //獲取ServiceManager的客戶端對(duì)象BpServiceManager
???sp<IServiceManager> sm = defaultServiceManager();
???
???//GrimReaper是一個(gè)很“血腥“的名字,俗稱死神
??? sp<GrimReaper>grim = new GrimReaper();
??? /*
??? 下面這行代碼的作用就是注冊(cè)grim對(duì)象為ServiceManager死亡信息的接收者。一旦SM死亡,
??? Binder系統(tǒng)就會(huì)發(fā)送訃告信息,這樣grim對(duì)象的binderDied函數(shù)就會(huì)被調(diào)用。該函數(shù)內(nèi)部
??? 將kill自己(即SystemServer)。
??? 筆者覺得,對(duì)于這種因摯愛離世而自殺的物體,叫死神好像不太合適
??? */
???sm->asBinder()->linkToDeath(grim, grim.get(), 0);
?
??? charpropBuf[PROPERTY_VALUE_MAX];
??? //判斷SystemServer是否啟動(dòng)SurfaceFlinger服務(wù),該值由init.rc
??? //腳本設(shè)置,默認(rèn)為零,即不啟動(dòng)SF服務(wù)
??? property_get("system_init.startsurfaceflinger",propBuf, "1");
??? /*
??? 從4.0開始,和顯示相關(guān)的核心服務(wù)surfaceflinger可獨(dú)立到另外一個(gè)進(jìn)程中。
??? 筆者認(rèn)為,這可能和目前SystemServer的負(fù)擔(dān)過重有關(guān)。另外,隨著智能終端上HDMI的普及,
??? 未來和顯示相關(guān)的工作將會(huì)越來越繁重。將SF放在單獨(dú)進(jìn)程中,不僅可加強(qiáng)集中管理,也可充分
??? 利用未來智能終端上多核CPU的資源
??? */
??? if(strcmp(propBuf, "1") == 0) {
???????SurfaceFlinger::instantiate();
??? }
??? //判斷SystemServer是否啟動(dòng)傳感器服務(wù),默認(rèn)將啟動(dòng)傳感器服務(wù)
???property_get("system_init.startsensorservice", propBuf,"1");
??? if(strcmp(propBuf, "1") == 0) {
??????? //和SF相同,傳感器服務(wù)也支持在獨(dú)立進(jìn)程中實(shí)現(xiàn)
???????SensorService::instantiate();
??? }
??? //獲得AndroidRuntime對(duì)象
???AndroidRuntime* runtime = AndroidRuntime::getRuntime();
??? JNIEnv*env = runtime->getJNIEnv();
??? ......//查找Java層的SystemServer類,獲取init2函數(shù)的methodID
??? jclassclazz = env->FindClass("com/android/server/SystemServer");
??? ......
???jmethodID methodId = env->GetStaticMethodID(clazz, "init2","()V");
??? ......//通過JNI調(diào)用Java層的init2函數(shù)
??? env->CallStaticVoidMethod(clazz,methodId);
??? //主線程加入Binder線程池
???ProcessState::self()->startThreadPool();
???IPCThreadState::self()->joinThreadPool();
??? returnNO_ERROR;
}
那么,SystemServer的main函數(shù)究竟做了什么呢?
通過init1函數(shù),辛辛苦苦從Java層穿越到Native層,做了一些初始化工作后,又通過JNI從Native層穿越到Java層去調(diào)用init2函數(shù)。
init2函數(shù)返回后,最終又回歸到Native層。
是不是感覺init1和init2這兩個(gè)函數(shù)的命名似曾相識(shí),和我們初學(xué)編程時(shí)自定義的函數(shù)名非常像呢?其實(shí)代碼中有一段“扭捏”的注釋,解釋了編寫這種“初級(jí)”代碼的原因。很簡(jiǎn)單,就是在對(duì)AndroidRuntime初始化前必須對(duì)一些核心服務(wù)初始化。
通過注釋可看出,這段代碼的作者也擔(dān)心被人指責(zé),但至少可以把函數(shù)名取得更形象一點(diǎn)吧?
init1函數(shù)看起來一點(diǎn)也不復(fù)雜,其實(shí)好戲都在init2中,其代碼如下:
[-->SystemServer.java]
public static final void init2() {
?????? ?Thread thr = new ServerThread();
???????thr.setName("android.server.ServerThread");
???????thr.start();//啟動(dòng)一個(gè)線程,這個(gè)線程就像英雄大會(huì)一樣,聚集了各路英雄
}
上面的代碼將創(chuàng)建一個(gè)新的線程ServerThread,該線程的run函數(shù)有600多行。如此之長(zhǎng)的原因是,Android平臺(tái)中眾多Service都匯集于此。先看Services的集體亮相,如圖3-1所示。
http://wiki.jikexueyuan.com/project/deep-android-v2/images/chapter3/image001.png" alt="image" />
圖3-1? Services群英會(huì)
圖3-1中有7大類共43個(gè)Service(包括Watchdog)。實(shí)際上,還有一些Service并沒有在ServerThread的run函數(shù)中露面,后面遇到時(shí)再做介紹。圖3-1中的7大類服務(wù)主要包括:
·??位于第一大類的是Android的核心服務(wù),如ActivityManagerService、WindowManagerService等。
·??位于第二大類的是和通信相關(guān)的服務(wù),如Wifi相關(guān)服務(wù)、Telephone相關(guān)服務(wù)。
·??位于第三大類的是和系統(tǒng)功能相關(guān)的服務(wù),如AudioService、MountService、UsbService等。
·??位于第四大類的是BatteryService、VibratorService等服務(wù)。
·??位于第五大類的是EntropyService,DiskStatsService、Watchdog等相對(duì)獨(dú)立的服務(wù)。
·??位于第六大類的是藍(lán)牙服務(wù)
·??位于第七大類的是UI方面的服務(wù),如狀態(tài)欄服務(wù),通知管理服務(wù)等。
以上服務(wù)的分類并非官方標(biāo)準(zhǔn),僅是筆者個(gè)人之見。
本章將分析其中的第五類服務(wù)。該類中的Service之間關(guān)系簡(jiǎn)單,而且功能相對(duì)獨(dú)立。第五大類服務(wù)包括:
·??EntropyService,熵服務(wù),它和隨機(jī)數(shù)的生成有關(guān)。
·??ClipboardService,剪貼板服務(wù)。
·??DropBoxManagerService,該服務(wù)和系統(tǒng)運(yùn)行時(shí)日志的存儲(chǔ)與管理有關(guān)。
·??DiskStatsService以及DeviceStorageMonitorService,這兩個(gè)服務(wù)用于查看和監(jiān)測(cè)系統(tǒng)存儲(chǔ)空間。
·??SamplingProfilerService,這個(gè)服務(wù)是4.0新增的,功能非常簡(jiǎn)單。
·??Watchdog,即看門狗,是Android的“老員工”了。我們?cè)诰鞩第4章“深入理解Zygote”中曾分析過它。Android2.3以后其內(nèi)存檢測(cè)功能被去掉,所以與Android 2.2相比,更顯簡(jiǎn)單了。這只小狗很可愛,就留給讀者自己分析了。后面,將逐次分析這第五類服務(wù)的其他幾項(xiàng)服務(wù)。
根據(jù)物理學(xué)基本原理,一個(gè)系統(tǒng)的熵越大,該系統(tǒng)就越不穩(wěn)定。在Android中,目前也只有隨機(jī)數(shù)喜歡處于這種不穩(wěn)定的系統(tǒng)中了。
SystemServer中添加該服務(wù)的代碼如下:
ServiceManager.addService("entropy", newEntropyService());
上邊代碼非常簡(jiǎn)單,從中可直接分析EntropyService的構(gòu)造函數(shù):
[-->EntropyService.java]
public EntropyService() {
??????? //調(diào)用另外一個(gè)構(gòu)造函數(shù),getSystemDir函數(shù)返回的是/data/system目錄
???????this(getSystemDir() + "/entropy.dat","/dev/urandom");
}
public EntropyService(String entropyFile, StringrandomDevice) {
?? this.randomDevice= randomDevice;//urandom是Linux系統(tǒng)中產(chǎn)生隨機(jī)數(shù)的設(shè)備
? ?// /data/system/entropy.dat文件保存了系統(tǒng)此前的熵信息
??this.entropyFile = entropyFile;
? //下面有4個(gè)關(guān)鍵函數(shù)
??loadInitialEntropy();//①
??addDeviceSpecificEntropy();//②
??writeEntropy();//③
??scheduleEntropyWriter();//④
}
從以上代碼中可以看出,EntropyService構(gòu)造函數(shù)中依次調(diào)用了4個(gè)關(guān)鍵函數(shù),這4個(gè)函數(shù)比較簡(jiǎn)單,這里只介紹它們的作用。感興趣的讀者可自行分析其代碼。
·??loadInitialEntropy函數(shù):將entropy.dat文件的中內(nèi)容寫到urandom設(shè)備,這樣可增加系統(tǒng)的隨機(jī)性。根據(jù)代碼中的注釋,系統(tǒng)中有一個(gè)entropypool。在系統(tǒng)剛啟動(dòng)時(shí),該pool中的內(nèi)容為空,導(dǎo)致早期生成的隨機(jī)數(shù)變得可預(yù)測(cè)。通過將entropy.dat數(shù)據(jù)寫到該entropy pool(這樣該pool中的內(nèi)容就不為空)中,隨機(jī)數(shù)的生成就無規(guī)律可言了。
·??addDeviceSpecificEntropy函數(shù):將一些和設(shè)備相關(guān)的信息寫入urandom設(shè)備。這些信息如下:
out.println("Copyright (C) 2009 The AndroidOpen Source Project");
out.println("All Your Randomness Are BelongTo Us");
out.println(START_TIME);
out.println(START_NANOTIME);
out.println(SystemProperties.get("ro.serialno"));
out.println(SystemProperties.get("ro.bootmode"));
out.println(SystemProperties.get("ro.baseband"));
out.println(SystemProperties.get("ro.carrier"));
out.println(SystemProperties.get("ro.bootloader"));
out.println(SystemProperties.get("ro.hardware"));
out.println(SystemProperties.get("ro.revision"));
out.println(new Object().hashCode());
out.println(System.currentTimeMillis());
out.println(System.nanoTime());
該函數(shù)的注釋表明,即使向urandom的entropy pool中寫入固定信息,也能增加隨機(jī)數(shù)生成的隨機(jī)性。從熵的角度考慮,系統(tǒng)的質(zhì)量越大(即pool中的內(nèi)容越多),該系統(tǒng)越不穩(wěn)定。
·??writeEntropy函數(shù):讀取urandom設(shè)備的內(nèi)容到entropy.dat文件。
·??scheduleEntropyWriter函數(shù):向EntropyService內(nèi)部的Handler發(fā)送一個(gè)ENTROPY_WHAT消息。該消息每3小時(shí)發(fā)送一次。收到該消息后,EntropyService會(huì)再次調(diào)用writeEntropy函數(shù),將urandom設(shè)備的內(nèi)容寫到entropy.dat中。
通過上面的分析可知,entropy.dat文件保存了urandom設(shè)備內(nèi)容的快照(每三小時(shí)更新一次)。當(dāng)系統(tǒng)重新啟動(dòng)時(shí),EntropyService又利用這個(gè)文件來增加系統(tǒng)的熵,通過這種方式使隨機(jī)數(shù)的生成更加不可預(yù)測(cè)。
EntropyService本身的代碼很簡(jiǎn)單,但是為了盡量保證隨機(jī)數(shù)的隨機(jī)性,Android還是下了一番苦功的。
?
DropBoxManagerService(簡(jiǎn)稱DBMS,下同)用于生成和管理系統(tǒng)運(yùn)行時(shí)的一些日志文件。這些日志文件大多記錄的是系統(tǒng)或某個(gè)應(yīng)用程序出錯(cuò)時(shí)的信息。
下面來分析這項(xiàng)服務(wù)。其中向SystemServer添加DBMS的代碼:
ServiceManager.addService(Context.DROPBOX_SERVICE,//服務(wù)名為”dropbox”
???? ??????????????????????new DropBoxManagerService(context,
?????????????????????????? newFile("/data/system/dropbox")));
?
DBMS構(gòu)造函數(shù)如下:
[-->DropBoxManagerService.java]
public DropBoxManagerService(final Contextcontext, File path) {
???????mDropBoxDir = path;//path指定dropbox目錄為/data/system/dropbox
???????mContext = context;
???????mContentResolver = context.getContentResolver();
?
???????IntentFilter filter = new IntentFilter();
???????filter.addAction(Intent.ACTION_DEVICE_STORAGE_LOW);
???????filter.addAction(Intent.ACTION_BOOT_COMPLETED);
??????? //注冊(cè)一個(gè)Broadcast監(jiān)聽對(duì)象,當(dāng)系統(tǒng)啟動(dòng)完畢或者設(shè)備存儲(chǔ)空間不足時(shí),會(huì)收到廣播
???????context.registerReceiver(mReceiver, filter);
?????? //當(dāng)Settings數(shù)據(jù)庫相應(yīng)項(xiàng)發(fā)生變化時(shí)候,也需要告知DBMS進(jìn)行相應(yīng)處理
???????mContentResolver.registerContentObserver(
???????????Settings.Secure.CONTENT_URI, true,
???????????new ContentObserver(new Handler()) {
???????????????public void onChange(boolean selfChange) {
?????????????//當(dāng)Settings數(shù)據(jù)庫發(fā)生變化時(shí)候, BroadcastReceiver的onReceive函數(shù)
?????????????//將被調(diào)用。注意第二個(gè)參數(shù)為null
???????????????????mReceiver.onReceive(context,(Intent) null);
???????????????}
??????? });
}
根據(jù)上面代碼可知:DBMS注冊(cè)一個(gè)BroadcastReceiver對(duì)象,同時(shí)會(huì)監(jiān)聽Settings數(shù)據(jù)庫的變動(dòng)。其核心邏輯都在此BroadcastReceiver的onReceive函數(shù)中。該函數(shù)在以下三種情況發(fā)生時(shí)被調(diào)用:
·??當(dāng)系統(tǒng)啟動(dòng)完畢時(shí),由BOOT_COMPLETED廣播觸發(fā)。
·??當(dāng)設(shè)備存儲(chǔ)空間不足時(shí),由DEVICE_STORAGE_LOW廣播觸發(fā)。
·??當(dāng)Settings數(shù)據(jù)庫相應(yīng)項(xiàng)發(fā)生變化時(shí)候,該函數(shù)也會(huì)被觸發(fā)。
這個(gè)函數(shù)內(nèi)容較簡(jiǎn)單,主要功能是存儲(chǔ)空間不足時(shí)需要?jiǎng)h除一些老舊的日志文件以節(jié)省存儲(chǔ)空間。讀者可自行分析這個(gè)函數(shù)。
?
要想理清一個(gè)Service,最面好從它提供的服務(wù)開始進(jìn)行分析。根據(jù)前面對(duì)DBMS的介紹可知,它提供了記錄系統(tǒng)運(yùn)行時(shí)日志信息的功能,所以這里先從dropbox日志文件的生成時(shí)說起。
當(dāng)某個(gè)應(yīng)用程序因?yàn)榘l(fā)生異常而崩潰(crash)時(shí),ActivityManagerService(簡(jiǎn)稱AMS,下同)的handleApplicationCrash函數(shù)被調(diào)用,其代碼如下:
[-->ActivityManagerService.java]
public void handleApplicationCrash(IBinder app,
???? ????????????????ApplicationErrorReport.CrashInfocrashInfo) {
?? ProcessRecordr = findAppProcess(app, "Crash");
?? ......
? ?//調(diào)用addErrorToDropBox函數(shù),第一個(gè)參數(shù)是一個(gè)字符串,為“crash”
?? addErrorToDropBox("crash",r, null, null, null, null, null, crashInfo);
?? ......
}
下面來看addErrorToDropBox函數(shù):
[-->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日志文件的命名有一定的規(guī)則,其前綴都是一個(gè)特定的tag(標(biāo)簽),
??? tag由兩部分組成,合起來是”進(jìn)程類型”_”事件類型”。
??? 下邊代碼中的processClass函數(shù)返回該進(jìn)程的類型,包括“system_server”、“system_app”
??? 和“data_app”三種。eventType用于指定事件類型,目前也有三種類型:“crash“、”wtf“
??? (what aterrible failure)和“anr”
??? */
??? finalString dropboxTag = processClass(process) + "_" + eventType;
??? //獲取DBMS Bn端的對(duì)象DropBoxManager
???????final DropBoxManager dbox = (DropBoxManager)
???????????????mContext.getSystemService(Context.DROPBOX_SERVICE);
??? ?/*
??? ??對(duì)于DBMS,不僅通過tag于標(biāo)示文件名,還可以根據(jù)配置的情況,允許或禁止特定tag日志
????? 文件的記錄。isTagEnable將判斷DBMS是否禁止該標(biāo)簽,如果該tag已被禁止,則不允許記
????? 錄日志文件
????? */
??????? if(dbox == null || !dbox.isTagEnabled(dropboxTag)) return;
??????? //創(chuàng)建一個(gè)StringBuilder,用于保存日志信息
???????final StringBuilder sb = new StringBuilder(1024);
???????appendDropBoxProcessHeaders(process, sb);
??????? ......//將信息保存到字符串sb中
???????? //單獨(dú)啟動(dòng)一個(gè)線程用于向DBMS添加信息
???????Thread worker = new Thread("Error dump: " + dropboxTag) {
???????????@Override
???????????public void run() {
???????????????if (report != null) {
???????????????????sb.append(report);
???????????????}
???????????????if (logFile != null) {
???????????????????try {//如果有l(wèi)og文件,那么就把log文件內(nèi)容讀到sb中
???????????????????????sb.append(FileUtils.readTextFile(logFile,
??????????????????????????????? 128 * 1024,"\n\n[[TRUNCATED]]"));
???????????????????} ......
???????????????}
???????????????//讀取crashInfo信息,一般記錄的是調(diào)用堆棧信息
???????????????if (crashInfo != null && crashInfo.stackTrace != null) {
???????????????????sb.append(crashInfo.stackTrace);
???????????????}
???????????????String setting = Settings.Secure.ERROR_LOGCAT_PREFIX + dropboxTag;
??????????????//查詢Settings數(shù)據(jù)庫,判斷該tag類型的日志是否對(duì)所記錄的信息有行數(shù)限制,
?????????????//例如某些tag的日志文件只準(zhǔn)記錄1000行的信息
??? ??????????int lines =Settings.Secure.getInt(mContext.getContentResolver(),
???????????????????????????????????????????????????????setting, 0);
???????????????if (lines > 0) {
???????????????????sb.append("\n");
???????????????????? InputStreamReader input =null;
???????????????????try {
??????????????????????? //創(chuàng)建一個(gè)新進(jìn)程以運(yùn)行l(wèi)ogcat,后面的參數(shù)都是logcat常用的參數(shù)
??????????????????????? java.lang.Processlogcat = new
???????????????????????? ?ProcessBuilder("/system/bin/logcat",
??????????????????????? "-v","time", "-b", "events", "-b","system", "-b",
??????????????????????????? ?"main", "-t", String.valueOf(lines))
??????????????????????????????? .redirectErrorStream(true).start();
???????????????????? //由于新進(jìn)程的輸出已經(jīng)重定向,因此這里可以獲取最后lines行的信息,
???????????????????//不熟悉ProcessBuidler的讀者可以查看SDK中關(guān)于它的用法說明
???????????????????? ......
??????????????????}
???????????????}
??????????????//調(diào)用DBMS的addText
???????????????dbox.addText(dropboxTag, sb.toString());
???????????}
??????? };
??????? if(process == null || process.pid == MY_PID) {
???????????worker.run(); //如果是SystemServer進(jìn)程crash了,則不能在別的線程執(zhí)行
??????? }else {
???????????worker.start();
???? }
}
由上面代碼可知,addErrorToDropBox在生成日志的內(nèi)容上花了不少工夫,因?yàn)檫@些是最重要的,最后僅調(diào)用addText函數(shù)便將內(nèi)容傳給DBMS的功能。
addText函數(shù)定義在DropBoxManager類中,代碼如下:
[-->DropBoxManager.java]
public void addText(String tag, String data) {
? /*
?? mService和DBMS交互。DBMS對(duì)外只提供一個(gè)add函數(shù)用于日志添加,而DBM提供了3個(gè)函數(shù),
?? 分別是addText、addData、addFile,以方便我們的使用
? */
?? try ......
}
DBM向DBMS傳遞的數(shù)據(jù)被封裝在一個(gè)Entry中。下面來看DBMS的add函數(shù),其代碼如下:
[-->DropBoxManagerService.java]
public void add(DropBoxManager.Entry entry) {
??????? Filetemp = null;
???????OutputStream output = null;
??????? finalString tag = entry.getTag();//先取出這個(gè)Entry的tag
??????? try{
???????????int flags = entry.getFlags();
??????????? ......
??????? ????//做一些初始化工作,包括生成dropbox目錄、統(tǒng)計(jì)當(dāng)前已有的dropbox文件信息等
???????????init();
???????????if (!isTagEnabled(tag)) return;//如果該tag被禁止,則不能生成日志文件
???????????long max = trimToFit();
???????????long lastTrim = System.currentTimeMillis();
???????????//BlockSize一般是4KB
???????????byte[] buffer = new byte[mBlockSize];
???????????//從Entry中得到一個(gè)輸入流。與Java I/O相關(guān)的類比較多,且用法非常靈活
??? ???????//建議讀者閱讀《Java編程思想》中“Java I/O系統(tǒng)”一章
??????????? 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;
???????????}
???????????//先生成一個(gè)臨時(shí)文件,命名方式為”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分區(qū),若所生成文件的size大于一個(gè)BlockSize,
????????????????則一定要先壓縮。
??????????? */
??????????? ......//寫文件,這段代碼非常繁瑣,其主要目的是盡量節(jié)省存儲(chǔ)空間
??????????? /*
??????????? 生成一個(gè)EntryFile對(duì)象,并保存到DBMS內(nèi)部的一個(gè)數(shù)據(jù)容器中。
????????????DBMS除了負(fù)責(zé)生成文件外,還提供查詢的功能,這個(gè)功能由getNextEntry完成。
????????? ???另外,剛才生成的臨時(shí)文件在createEntry函數(shù)中會(huì)重命為帶標(biāo)簽的名字,
????????????讀者可自行分析createEntry函數(shù)
????????????*/
???????????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);
???????????}
???????????//發(fā)送DROPBOX_ENTRY_ADDED廣播。系統(tǒng)中目前還沒有程序接收該廣播
???????????mContext.sendBroadcast(dropboxIntent,
??????????????????????????? android.Manifest.permission.READ_LOGS);
??????? }......
}
上面代碼中略去了DBMS寫文件的部分,我們從代碼注釋中可獲悉,DBMS非常珍惜/data分區(qū)的空間,每一個(gè)日志文件都需要考慮壓縮以節(jié)省存儲(chǔ)空間。如果說細(xì)節(jié)體現(xiàn)功力,那么這正是一個(gè)極好的例證。
一個(gè)真實(shí)設(shè)備上/data/system/dropbox目錄的內(nèi)容如圖3-2所示。
http://wiki.jikexueyuan.com/project/deep-android-v2/images/chapter3/image002.png" alt="image" />
圖3-2? 真實(shí)設(shè)備中dropbox目錄的內(nèi)容
圖3-2中最后一項(xiàng)data_app_anr@1324836096560.txt.gz的大小是6.1KB,該文件解壓后得到的文件大小是42kB??磥?,壓縮確實(shí)節(jié)省了不少存儲(chǔ)空間。
另外,我們從圖3-2中還發(fā)現(xiàn)了其他不同的tag,如SYSTEM_BOOT、SYSTEM_TOMBSTONE等,這些都是由BootReceiver在收到BOOT_COMPLETE廣播后收集相關(guān)信息并傳遞給DBMS而生成的日志文件。
DBMS的運(yùn)行依賴一些配置項(xiàng)。其實(shí)除了DBMS,SystemServer中很多服務(wù)都依賴相關(guān)的配置選項(xiàng)。這些配置項(xiàng)都是通過SettingsProvider操作Settings數(shù)據(jù)庫來設(shè)置和查詢的。SettingsProvider是系統(tǒng)中很重要的一個(gè)APK,如果將其刪除,系統(tǒng)就不能正常啟動(dòng)。
這里總結(jié)一下和DBMS相關(guān)的設(shè)置項(xiàng),具體情況如下(注意,右邊雙引號(hào)的內(nèi)容是該配置項(xiàng)在數(shù)據(jù)庫中的名字。這些和系統(tǒng)相關(guān)的項(xiàng)都在Settings數(shù)據(jù)庫中的Secure表內(nèi)):
//用來判斷是否允許記錄該tag類型的日志文件。默認(rèn)是允許生成任何tag類型的文件
Secure.DROPBOX_TAG_PREFIX+tag: “dropbox:”+tag
//用于控制每個(gè)日志文件的存活時(shí)間,默認(rèn)是三天。大于三天的日志文件就會(huì)被刪除以節(jié)省空間
Secure.DROPBOX_AGE_SECONDS: ”dropbox_age_seconds”
//用于控制系統(tǒng)保存的日志文件個(gè)數(shù),默認(rèn)是1000個(gè)文件
Secure.DROPBOX_MAX_FILES:”dropbox_max_files”
//用于控制dropbox目錄最多占存儲(chǔ)空間容量的比例,默認(rèn)是10%
Secure.DROPBOX_QUOTA_PERCENT:”dropbox_quota_percent”
//不允許dropbox使用的存儲(chǔ)空間的比例,默認(rèn)是10%,也就是dropbox最多只能使用90%的空間
Secure.DROPBOX_RESERVE_PERCENT:”dropbox_reserve_percent”
//dropbox最大能使用的空間大小,默認(rèn)是5MB
Secure.DROPBOX_QUOTA_KB:”dropbox_quota_kb”
感興趣的讀者可以通過adb shell進(jìn)入/data/data/com.android.providers.settings/databases/目錄,然后利用sqlite3命令操作settings.db,其中有一個(gè)Secure表。不過系統(tǒng)中的很多選項(xiàng)在該表中都沒有相關(guān)設(shè)置,因此實(shí)際運(yùn)行時(shí)都會(huì)使用代碼中設(shè)置的默認(rèn)值。
DiskStatsService和DeviceStroageMonitorService與系統(tǒng)內(nèi)部存儲(chǔ)管理和監(jiān)控有關(guān)。
DiskStatsService代碼非常簡(jiǎn)單,不過也有一個(gè)很有意思的地方,例如:
[-->DiskStatsService.java]
public class DiskStatsService extends Binder
DiskStatsService從Binder派生,卻沒有實(shí)現(xiàn)任何接口,也就是說,DiskStatsService沒有任何業(yè)務(wù)函數(shù)可供調(diào)用。為什么系統(tǒng)會(huì)存在這樣的服務(wù)呢?
為了解釋這個(gè)問題,有必要先把系統(tǒng)中一個(gè)很重要的命令dumpsys請(qǐng)出來。正如其名,這個(gè)命令用于打印系統(tǒng)中指定服務(wù)的信息,代碼如下:
[-->dumpsys.cpp]
int main(int argc, char* const argv[])
{
??? //先獲取與ServiceManager進(jìn)程通信的BpServiceManager對(duì)象
???sp<IServiceManager> sm = defaultServiceManager();
???fflush(stdout);
???
?? ?Vector<String16> services;
???Vector<String16> args;
??? if (argc== 1) {//如果輸入?yún)?shù)個(gè)數(shù)為1,則先查詢?cè)赟M中注冊(cè)的所有Service
???????services = sm->listServices();
??????? //將service排序
???????services.sort(sort_func);
??????? args.add(String16("-a"));
??? } else {
??? ????//指定查詢某個(gè)service
???????services.add(String16(argv[1]));
??????? //保存剩余參數(shù),以后可以傳給service的dump函數(shù)
??????? for(int i=2; i<argc; i++) {
???????????args.add(String16(argv[i]));
??????? }
??? }
?
??? constsize_t N = services.size();
??? ......
??? for(size_t i=0; i<N; i++) {
???????sp<IBinder> service = sm->checkService(services[i]);
????????......
???????? //通過Binder調(diào)用該service的dump函數(shù),將args也傳給dump函數(shù)
??????? ?int err = service->dump(STDOUT_FILENO,args);
??????? ?......
??? }
??? return0;
}
從上面代碼可知,dumpsys通過Binder調(diào)用某個(gè)Service的dump函數(shù)。那么
“dumpsys diskstats”的輸出會(huì)是什么呢?馬上來試試,結(jié)果如圖3-3所示。
http://wiki.jikexueyuan.com/project/deep-android-v2/images/chapter3/image003.png" alt="image" />
圖3-3? dumpsys diskstats的結(jié)果圖示
圖3-3說明了執(zhí)行“dumpsysdiskstats”打印了系統(tǒng)中內(nèi)部存儲(chǔ)設(shè)備的使用情況。dumpsys是工作中常用的命令,建議讀者掌握它的用法。
再來看DiskStatsService的dump函數(shù),代碼如下:
[-->DiskStatsService.java]
protected void dump(FileDescriptor fd, PrintWriterpw, String[] args) {
???????byte[] junk = new byte[512];
??????? for(int i = 0; i < junk.length; i++) junk[i] = (byte) i;?
??????? //輸出/data/system/perftest.tmp文件信息,輸出后即刪除該文件
??????? //目前還不清楚這個(gè)文件由誰生成。從名字上看應(yīng)該和性能測(cè)試有關(guān)
??????? Filetmp = new File(Environment.getDataDirectory(),
??????????????????????????????? "system/perftest.tmp");
???????FileOutputStream fos = null;
???????IOException error = null;
??????? longbefore = SystemClock.uptimeMillis();
??????? try{
???????????fos = new FileOutputStream(tmp);
???????????fos.write(junk);
??????? } ......
??????? longafter = SystemClock.uptimeMillis();
??????? if(tmp.exists()) tmp.delete();
?
??????? if(error != null) {
??????????? ......
??????? }else {
???????????pw.print("Latency: ");
???????????pw.print(after - before);
???????????pw.println("ms [512B Data Write]");
??????? }
??????? //打印內(nèi)部存儲(chǔ)設(shè)備各個(gè)分區(qū)的信息
???????reportFreeSpace(Environment.getDataDirectory(), "Data", pw);
???????reportFreeSpace(Environment.getDownloadCacheDirectory(),"Cache", pw);
???????reportFreeSpace(new File("/system"), "System", pw);
??????? //有些廠商還會(huì)將/proc/yaffs信息打印出來
}
從前述代碼中可發(fā)現(xiàn),DiskStatsService沒有實(shí)現(xiàn)任何業(yè)務(wù)接口,似乎只是為了調(diào)試而存在。所以筆者認(rèn)為,DiskStatsService的功能完全可以被整合到后面即將介紹的DeviceStorageManagerService類中??傊?,本節(jié)最重要的就是dumpsys這個(gè)命令了,建議讀者一定要掌握它的用法。
DeviceStorageManagerService(簡(jiǎn)稱DSMS,下同)是用來監(jiān)測(cè)系統(tǒng)內(nèi)部存儲(chǔ)空間的狀態(tài)的,添加該服務(wù)的代碼如下:
//DSMS的服務(wù)名為“devicestoragemonitor “
ServiceManager.addService(DeviceStorageMonitorService.SERVICE,
??????????????????????? newDeviceStorageMonitorService(context));
DSMS的構(gòu)造函數(shù)的代碼如下:
[-->DeviceStorageManagerService.java]
public DeviceStorageMonitorService(Contextcontext) {
???????mLastReportedFreeMemTime = 0;
???????mContext = context;
???????mContentResolver = mContext.getContentResolver();
???????mDataFileStats = new StatFs(DATA_PATH);//獲取data分區(qū)的信息
???????mSystemFileStats = new StatFs(SYSTEM_PATH);// 獲取system分區(qū)的信息
???????mCacheFileStats = new StatFs(CACHE_PATH);// 獲取cache分區(qū)的信息
??????? //獲得data分區(qū)的總大小
???????mTotalMemory = ((long)mDataFileStats.getBlockCount() *
???????????????????????mDataFileStats.getBlockSize())/100L;
??????? /*
??? ????創(chuàng)建三個(gè)Intent,分別用于通知存儲(chǔ)空間不足、存儲(chǔ)空間恢復(fù)正常和存儲(chǔ)空間滿。
??????? 由于設(shè)置了REGISTERED_ONLY_BEFORE_BOOT標(biāo)志,這3個(gè)Intent廣播只能由
??? ????系統(tǒng)服務(wù)接收
??? ????*/
??? ???mStorageLowIntent = newIntent(Intent.ACTION_DEVICE_STORAGE_LOW);
????? ?mStorageLowIntent.addFlags(
??????????????????????? Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
???????mStorageOkIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_OK);
???????mStorageOkIntent.addFlags(
???????????????????Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
???????mStorageFullIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_FULL);
???????mStorageFullIntent.addFlags(
???????????????????Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
???????mStorageNotFullIntent = new
?? ?????????????????Intent(Intent.ACTION_DEVICE_STORAGE_NOT_FULL);
???????mStorageNotFullIntent.addFlags(
???????????????????Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
???????
??????? //查詢Settings數(shù)據(jù)庫中sys_storage_threshold_percentage的值,默認(rèn)是10,
??????? //即當(dāng)/data空間只剩下10%的時(shí)候,,認(rèn)為空間不足
???????mMemLowThreshold = getMemThreshold();
?????? //查詢Settings數(shù)據(jù)庫中的sys_storage_full_threshold_bytes的值,默認(rèn)是1MB,
?????? //即當(dāng)data分區(qū)只剩1MB時(shí),就認(rèn)為空間已滿,剩下的這1MB空間保留給系統(tǒng)自用
???????mMemFullThreshold = getMemFullThreshold();
????? ??//檢查內(nèi)存
???????checkMemory(true);
}
再來看checkMemory函數(shù),代碼如下:
?
private final void checkMemory(boolean checkCache){
??if(mClearingCache) {
??????......//如果正在清理空間,則不作處理
? ???} else{
???????????restatDataDir();//重新計(jì)算三個(gè)分區(qū)的剩余空間大小
??????????? //如果剩余空間低于mMemLowThreshold,那么先清理一次空間
????????????clearCache();
????????? ??//如果空間仍不足,則發(fā)送廣播,并在狀態(tài)欄上設(shè)置一個(gè)警告通知
?????????????sendNotification();
?????????????......
????????? ??//如果空間已滿,則調(diào)用下面這個(gè)函數(shù),以發(fā)送一次存儲(chǔ)已滿的廣播
???????? ????sendFullNotification();
??????? } ......
??????//DEFAULT_CHECK_INTERVAL為1分鐘,即每一分鐘會(huì)觸發(fā)一次檢查,似乎有點(diǎn)短
???????postCheckMemoryMsg(true, DEFAULT_CHECK_INTERVAL);
}
當(dāng)空間不足時(shí),DSMS會(huì)先嘗試clearCache函數(shù),該函數(shù)內(nèi)部會(huì)與PackageManagerService交互,其代碼如下:
[-->DeviceStorageManagerService.java]
private final void clearCache() {
?if(mClearCacheObserver == null) {
????? //創(chuàng)建一個(gè)CachePackageDataObserver對(duì)象,當(dāng)PKM清理完空間時(shí)會(huì)回調(diào)該對(duì)象的
????? //onRemoveCompleted函數(shù)
??????mClearCacheObserver = new CachePackageDataObserver();
?? }
?mClearingCache= true;//設(shè)置mClearingCache的值為true,表示我們正在清理空間
?try {
???? //調(diào)用PKM的freeStorageAndNotify函數(shù)以清理空間,這個(gè)函數(shù)在分析PKM時(shí)再介紹
??? IPackageManager.Stub.asInterface(
??????????? ServiceManager.getService("package")).
??????????? freeStorageAndNotify(mMemLowThreshold,mClearCacheObserver);
?? } ......
}
CachePackageDataObserver是DSMS定義的內(nèi)部類,其onRemoveCompleted函數(shù)很簡(jiǎn)單,就是重新發(fā)送消息,讓DSMS再檢測(cè)一次內(nèi)存空間。
DeviceStorageManagerService的功能單一,沒有重載dump函數(shù)。而DiskStatsService唯一有用的就是dump功能了。不知Google的工程師為什么沒有把DeviceStorageManagerService和DiskStatsService的功能整合到一起。
添加SamplingProfilerService服務(wù)的代碼如下:
ServiceManager.addService("samplingprofiler",//服務(wù)名
??????????????????????????? newSamplingProfilerService(context));
下面給來分析SamplingProfilerService的構(gòu)造函數(shù),其代碼如下:
[-->SamplingProfilerService.java]
public SamplingProfilerService(Context context) {
?????? //注冊(cè)一個(gè)CotentObserver,用于監(jiān)測(cè)Settings數(shù)據(jù)庫的變化
???????registerSett