[深入理解Android卷二 全文-第五章]深入理解PowerManagerService
由於《深入理解Android 卷一》和《深入理解Android卷二》不再出版,而知識的傳播不應該因為紙質媒介的問題而中斷,所以我將在CSDN部落格中全文轉發這兩本書的全部內容
第5章 深入理解PowerManagerService
本章主要內容:
· 深入分析PowerManagerService
· 深入分析BatteryService和BatteryStatsService
本章所涉及的原始碼檔名及位置:
· PowerManagerService.java
frameworks/base/services/java/com/android/server/PowerManagerService.java
· com_android_server_PowerManagerService.cpp
frameworks/base/services/jni/com_android_server_PowerManagerService.cpp
· PowerManager.java
frameworks/base/core/java/android/os/PowerManager.java
· WorkSoure.java
frameworks/base/core/java/android/os/WorkSoure.java
· Power.java
frameworks/base/core/java/android/os/Power.java
· android_os_Power.cpp
frameworks/base/core/jni/android_os_Power.cpp
· com_android_server_InputManager.cpp
frameworks/base/services/jni/com_android_server_InputManager.cpp
· LightService.java
frameworks/base/services/java/com/android/server/LightService.java
· com_android_server_LightService.cpp
frameworks/base/services/jni/com_android_server_LightService.cpp
· BatteryService.java
frameworks/base/services/java/com/android/server/BatteryService.java
· com_android_server_BatteryService.cpp
frameworks/base/services/jni/com_android_server_BatteryService.cpp
· ActivityManagerService.java
frameworks/base/services/java/com/android/server/am/ActivityManagerService.java
· BatteryStatsService.java
frameworks/base/services/java/com/android/server/am/BatteryStatsService.java
· BatteryStatsImpl.java
frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
· LocalPowerManager.java
frameworks/base/core/java/android/os/LocalPowerManager.java
5.1 概述
PowerManagerService負責Andorid系統中電源管理方面的工作。作為系統核心服務之一,PowerManagerService與其他服務及HAL層等都有互動關係,所以PowerManagerService相對PackageManager來說,其社會關係更復雜,分析難度也會更大一些。
先來看直接與PowerManagerService有關的類家族成員,如圖5-1所示
圖5-1 PowerManagerService及相關類家族
由圖5-1可知:
· PowerManagerService從IPowerManager.Stub類派生,並實現了Watchdog.Monitor及LocalPowerManager介面。PowerManagerService內部定義了較多的成員變數,在後續分析中,我們會對其中比較重要的成員逐一進行介紹。
· 根據第4章介紹的知識,IPowerManager.Stub及內部類Proxy均由aidl工具處理PowerManager.aidl後得到。
· 客戶端使用PowerManager類,其內部通過代表BinderProxy端的mService成員變數與PowerManagerService進行跨Binder通訊。
現在開始PowerManagerService(以後簡寫為PMS)的分析之旅,先從它的呼叫流程入手。
提示PMS和BatteryService、BatteryStatsService均有互動關係,這些內容放在後面分析。
5.2 初識PowerManagerService
PMS由SystemServer在ServerThread執行緒中建立。這裡從中提取了4個關鍵呼叫點,如下所示:
[-->SystemServer.java]
......//ServerThread的run函式
power =new PowerManagerService();//①建立PMS物件
ServiceManager.addService(Context.POWER_SERVICE, power);//註冊到SM中
......
//②呼叫PMS的init函式
power.init(context,lights, ActivityManagerService.self(), battery);
......//其他服務
power.systemReady();//③呼叫PMS的systemReady
......//系統啟動完畢,會收到ACTION_BOOT_COMPLETED廣播
//④PMS處理ACTION_BOOT_COMPLETED廣播
先從第一個關鍵點即PMS的建構函式開始分析。
5.2.1 PMS建構函式分析
PMS建構函式的程式碼如下:
[-->PowerManagerService.java::建構函式]
PowerManagerService() {
longtoken = Binder.clearCallingIdentity();
MY_UID =Process.myUid();//取本程式(即SystemServer)的uid及pid
MY_PID =Process.myPid();
Binder.restoreCallingIdentity(token);
//設定超時時間為1周。Power類封裝了同Linux核心互動的介面。本章最後再來分析它
Power.setLastUserActivityTimeout(7*24*3600*1000);
//初始化兩個狀態變數,它們非常有意義。其具體作用後續再分析
mUserState= mPowerState = 0;
//將自己新增到看門狗的監控管理佇列中
Watchdog.getInstance().addMonitor(this);
}
PMS的建構函式比較簡單。值得注意的是mUserState和mPowerState兩個成員,至於它們的具體作用,後續分析時自會知曉。
下面分析第二個關鍵點。
5.2.2 init分析
第二個關鍵點是init函式,該函式將初始化PMS內部的一些重要成員變數,由於此函式程式碼較長,此處將分段討論。
從流程角度看,init大體可分為三段。
1. init分析之一
[-->PowerManagerService.java::init函式]
void init(Context context, LightsService lights,IActivityManager activity,
BatteryService battery) {
//①儲存幾個成員變數
mLightsService = lights;//儲存LightService
mContext= context;
mActivityService = activity;//儲存ActivityManagerService
//儲存BatteryStatsService
mBatteryStats = BatteryStatsService.getService();//
mBatteryService = battery;//儲存BatteryService
//從LightService中獲取代表不同硬體Light的Light物件
mLcdLight= lights.getLight(LightsService.LIGHT_ID_BACKLIGHT);
mButtonLight = lights.getLight(LightsService.LIGHT_ID_BUTTONS);
mKeyboardLight = lights.getLight(LightsService.LIGHT_ID_KEYBOARD);
mAttentionLight = lights.getLight(LightsService.LIGHT_ID_ATTENTION);
//②呼叫nativeInit函式
nativeInit();
synchronized (mLocks) {
updateNativePowerStateLocked();//③更新Native層的電源狀態
}
第一階段工作可分為三步:
· 對一些成員變數進行賦值。
· 呼叫nativeInit函式初始化Native層相關資源。
· 呼叫updateNativePowerStateLocked更新Native層的電源狀態。這個函式的呼叫次數較為頻繁,以後續分析時討論。
先來看第一階段出現的各類成員變數,如表5-1所示。
表5-1 成員變數說明
成員變數名 | 資料型別 | 作用 |
mLightsService | LightsService | 和LightsService互動用 |
mActivityService | IActivityManager | 和ActivityManagerService互動 |
mBatteryStats | IBatteryStats | 和BatteryStatsService互動,用於系統耗電量統計方面的工作 |
mBatteryService | BatteryService | 用於獲取電源狀態,例如是否為低電狀態、查詢電池電量等 |
mLcdLight、mButtonLight、 mKeyboardLight、mAttentionLight | LightsService.Light | 由PMS控制,在不同狀態下點亮或熄滅它們 |
下面來看nativeInit函式,其JNI層實現程式碼如下:
[-->com_android_server_PowerManagerService.cpp]
static void android_server_PowerManagerService_nativeInit(JNIEnv*env,
jobject obj) {
//非常簡單,就是建立一個全域性引用物件gPowerManagerServiceObj
gPowerManagerServiceObj = env->NewGlobalRef(obj);
}
init第一階段工作比較簡單,下面進入第二階段的分析。
2. init分析之二
init第二階段工作將建立兩個HandlerThread物件,即建立兩個帶訊息迴圈的工作執行緒。PMS本身由ServerThread執行緒建立,並且將自己的工作委託給這兩個執行緒,它們分別是:
· mScreenOffThread:按Power鍵關閉螢幕時,螢幕不是突然變黑的,而是一個漸暗的過程。mScreenOffThread執行緒就用於控制關屏過程中的亮度調節。
· mHandlerThread:該執行緒是PMS的主要工作執行緒。
先來看這兩個執行緒的建立。
(1) mScreenOffThread和mHandlerThread分析
[-->PowerManagerService.java::init函式]
......
mScreenOffThread= new HandlerThread("PowerManagerService.mScreenOffThread") {
protected void onLooperPrepared() {
mScreenOffHandler = new Handler();//向這個handler傳送的訊息,將由此執行緒處理
synchronized (mScreenOffThread) {
mInitComplete = true;
mScreenOffThread.notifyAll();
}
}
};
mScreenOffThread.start();//建立對應的工作執行緒
synchronized (mScreenOffThread) {
while(!mInitComplete) {
try {//等待mScreenOffThread執行緒建立完成
mScreenOffThread.wait();
} ......
}
}
注意,在Android程式碼中經常出現“執行緒A建立執行緒B,然後執行緒A等待執行緒B建立完成”的情況,讀者瞭解它們的作用即可。接著看以下程式碼。
[-->PowerManagerService.java::init函式]
mInitComplete= false;
//建立 mHandlerThread
mHandlerThread = new HandlerThread("PowerManagerService") {
protectedvoid onLooperPrepared() {
super.onLooperPrepared();
initInThread();//①初始化另外一些成員變數
}
};
mHandlerThread.start();
......//等待mHandlerThread建立完成
由於mHandlerThread承擔了PMS的主要工作任務,因此需要先做一些初始化工作,相關的程式碼在initInThread中,擬放在單獨一節中進行討論。
(2) initInThread分析
initInThread本身比較簡單,涉及三個方面的工作,總結如下:
· PMS需要了解外面的世界,所以它會註冊一些廣播接收物件,接收諸如啟動完畢、電池狀態變化等廣播。
· PMS所從事的電源管理工作需要遵守一定的規則,而這些規則在程式碼中就是一些配置引數,這些配置引數的值可以是固定寫死的(編譯完後就無法改動),也可以是經由Settings資料庫動態設定的。
· PMS需要對外發出一些通知,例如螢幕關閉/螢幕開啟。
瞭解initInThread的概貌後,再來看如下程式碼。
[-->PowerManagerService.java::initInThread]
void initInThread() {
mHandler= new Handler();
//PMS內部也需要使用WakeLock,此處定義了幾種不同的UnsynchronizedWakeLock。它們的
//作用見後文分析
mBroadcastWakeLock = newUnsynchronizedWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, "sleep_broadcast", true);
//建立廣播通知的Intent,用於通知SCREEN_ON和SCREEN_OFF訊息
mScreenOnIntent = new Intent(Intent.ACTION_SCREEN_ON);
mScreenOnIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
mScreenOffIntent = new Intent(Intent.ACTION_SCREEN_OFF);
mScreenOffIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
//取配置引數,這些引數是編譯時確定的,執行過程中無法修改
Resourcesresources = mContext.getResources();
mAnimateScreenLights = resources.getBoolean(
com.android.internal.R.bool.config_animateScreenLights);
......//見下文的配置引數彙總
//通過資料庫設定的配置引數
ContentResolver resolver =mContext.getContentResolver();
Cursor settingsCursor =resolver.query(Settings.System.CONTENT_URI, null,
......//設定查詢條件和查詢項的名字,見後文的配置引數彙總
null);
//ContentQueryMap是一個常用類,簡化了資料庫查詢工作。讀者可參考SDK中該類的說明文件
mSettings= new ContentQueryMap(settingsCursor, Settings.System.NAME,
true, mHandler);
//監視上邊建立的ContentQueryMap中內容的變化
SettingsObserver settingsObserver = new SettingsObserver();
mSettings.addObserver(settingsObserver);
settingsObserver.update(mSettings, null);
//註冊接收通知的BroadcastReceiver
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_BATTERY_CHANGED);
mContext.registerReceiver(new BatteryReceiver(), filter);
filter =new IntentFilter();
filter.addAction(Intent.ACTION_BOOT_COMPLETED);
mContext.registerReceiver(new BootCompletedReceiver(), filter);
filter =new IntentFilter();
filter.addAction(Intent.ACTION_DOCK_EVENT);
mContext.registerReceiver(new DockReceiver(), filter);
//監視Settings資料中secure表的變化
mContext.getContentResolver().registerContentObserver(
Settings.Secure.CONTENT_URI, true,
new ContentObserver(new Handler()) {
public void onChange(boolean selfChange) {
updateSettingsValues();
}
});
updateSettingsValues();
......//通知其他執行緒
}
在上述程式碼中,很大一部分用於獲取配置引數。同時,對於資料庫中的配置值,還需要建立監測機制,細節部分請讀者自己閱讀相關程式碼,這裡總結一下常用的配置引數,如表5-2所示。
表5-2 PMS使用的配置引數
引數名:型別 | 來源 | 備註 |
mAnimateScreenLights:bool | config.xml[①] | 關屏時螢幕光是否漸暗,預設為true |
mUnplugTurnsOnScreen:bool | config.xml | 拔掉USB線,是否點亮螢幕 |
mScreenBrightnessDim:int | config.xml | PMS可設定的螢幕亮度的最小值,預設20(單位lx) |
mUseSoftwareAutoBrightness:bool | config.xml | 是否啟用Setting中的亮度自動調節,如果硬體不支援該功能,則可由軟體控制。預設為false |
mAutoBrightnessLevels:int[] mLcdBacklightValues:int[] ...... | config.xml,具體值由硬體廠商定義 | 當使用軟體自動亮度調節時,需配置不同亮度時對應的引數 |
STAY_ON_WHILE_PLUGGED_IN:int | Settings.db | 插入USB時是否保持喚醒狀態 |
SCREEN_OFF_TIMEOUT:int | Settings.db | 螢幕超時時間 |
DIM_SCREEN:int | Settings.db | 是否變暗(dim)螢幕 |
SCREEN_BRIGHTNESS_MODE:int | Settings.db | 螢幕亮度模式(自動還是手動調節) |
除了獲取配置引數外,initInThread還建立了好幾個UnsynchronizedWakeLock物件,它的作用是:在Android系統中,為了搶佔電力資源,客戶端要使用WakeLock物件。PMS自己也不例外,所以為了保證在工作中不至於突然掉電(當其他客戶端都不使用WakeLock的時候,這種情況理論上是有可能發生的),PMS需要定義供自己使用的WakeLock。由於執行緒同步方面的原因,PMS封裝了一個UnsynchronizedWakeLock結構,它的呼叫已經處於鎖保護下,所以在內部無需再做同步處理。UnsynchronizedWakeLock比較簡單,因此不再贅述。
下面來看init第三階段的工作。
3. init分析之三
[-->PowerManagerService.java::init函式]
nativeInit();//不知道此處為何還要呼叫一次nativeInit,筆者懷疑此處為bug
synchronized (mLocks) {
updateNativePowerStateLocked();//更新native層power狀態,以後分析
forceUserActivityLocked();//強制觸發一次使用者事件
mInitialized = true;
}//init函式完畢
forceUserActivityLocked表示強制觸發一次使用者事件。這個解釋是否會讓讀者丈二和尚摸不著頭?先來看它的程式碼:
[-->PowerManagerService.java:: forceUserActivityLocked]
private void forceUserActivityLocked() {
if(isScreenTurningOffLocked()) {
mScreenBrightness.animating = false;
}
boolean savedActivityAllowed =mUserActivityAllowed;
mUserActivityAllowed = true;
//下面這個函式以後會分析, SDK中有對應的API
userActivity(SystemClock.uptimeMillis(), false);
mUserActivityAllowed= savedActivityAllowed;
}
forceUserActivityLocked內部就是為呼叫userActivity掃清一切障礙。對於SDK中PowerManager.userActivity的說明文件“User activity happened.Turnsthe device from whatever state it's in to full on, and resets the auto-offtimer.”簡單翻譯過來是:呼叫此函式後,手機將被喚醒。螢幕超時時間將重新計算。
userActivity是PMS中很重要的一個函式,本章後面將對其進行詳細分析。
4. init函式總結
PMS的init函式比較簡單,但是其眾多的成員變數讓人感到有點頭暈。讀者自行閱讀程式碼時,不妨參考表5-1和表5-2。
5.2.3 systemReady分析
下面來分析PMS第三階段的工作。此時系統中大部分服務都已建立好,即將進入就緒階段。就緒階段的工作在systemReady中完成,程式碼如下:
[-->PowerManagerService.java::systemReady]
void systemReady() {
/*
建立一個SensorManager,用於和系統中的感測器系統互動,由於該部分涉及較多的native層
程式碼,因此將相關內容放到本書後續章節進行討論
*/
mSensorManager = new SensorManager(mHandlerThread.getLooper());
mProximitySensor = mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
if(mUseSoftwareAutoBrightness) {
mLightSensor =mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
}
if(mUseSoftwareAutoBrightness) {
setPowerState(SCREEN_BRIGHT);
} else {//不考慮軟體自動亮度調節,所以執行下面這個分支
setPowerState(ALL_BRIGHT);//設定手機電源狀態為ALL_BRIGHT,即螢幕、按鍵燈都開啟
}
synchronized (mLocks) {
mDoneBooting = true;
//根據情況啟用LightSensor
enableLightSensorLocked(mUseSoftwareAutoBrightness&&mAutoBrightessEnabled);
longidentity = Binder.clearCallingIdentity();
try {//通知BatteryStatsService,它將統計相關的電量使用情況,後續再分析它
mBatteryStats.noteScreenBrightness(getPreferredBrightness());
mBatteryStats.noteScreenOn();
}......
}
systemReady主要工作為:
· PMS建立SensorManager,通過它可與對應的感測器互動。關於Android感測器系統,將放到本書後續章節討論。PMS僅僅啟用或禁止特定的感測器,而來自感測器的資料將通過回撥的方式通知PMS,PMS根據接收到的感測器事件做相應處理。
· 通過setPowerState函式設定電源狀態為ALL_BRIGHT(不考慮UseSoftwareAutoBrightness的情況)。此時螢幕及鍵盤燈都會點亮。關於setPowrState函式,後文再做詳細分析。
· 呼叫BatteryStatsService提供的函式,以通知螢幕開啟事件,在BatteryStatsService內部將處理該事件。稍後,本章將詳細討論BatteryStatsService的功能。
當系統中的服務都在systemReady中進行處理後,系統會廣播一次ACTION_BOOT_COMPLETED訊息,而PMS也將處理該廣播,下面來分析。
5.2.4 BootComplete處理
[-->PowerManagerService.java::BootCompletedReceiver]
private final class BootCompletedReceiver extendsBroadcastReceiver {
publicvoid onReceive(Context context, Intent intent) {
bootCompleted();//呼叫PMS的bootCompleted函式
}
}
[-->PowerManagerService.java::bootCompleted函式]
void bootCompleted() {
synchronized (mLocks) {
mBootCompleted = true;
//再次碰見userActivity,根據前面的描述,此時將重新計算螢幕超時時間
userActivity(SystemClock.uptimeMillis(), false, BUTTON_EVENT, true);
updateWakeLockLocked();//此處先分析這個函式
mLocks.notifyAll();
}
}
在以上程式碼中,再一次遇見了userActivity,暫且對其置之不理。先分析updateWakeLockLocked函式,其程式碼如下:
private void updateWakeLockLocked() {
/*
mStayOnConditions用於控制當插上USB時,手機是否保持喚醒狀態。
mBatteryService的isPowered用於判斷當前是否處於USB充電狀態。
如果滿足下面的if條件滿,則PMS需要使用wakeLock來確保系統不會掉電
*/
if(mStayOnConditions != 0 &&mBatteryService.isPowered(mStayOnConditions)) {
mStayOnWhilePluggedInScreenDimLock.acquire();
mStayOnWhilePluggedInPartialLock.acquire();
} else {
//如果不滿足if條件,則釋放對應的wakeLock,這樣系統就可以進入休眠狀態
mStayOnWhilePluggedInScreenDimLock.release();
mStayOnWhilePluggedInPartialLock.release();
}
}
mStayOnWhilePluggedInScreenDimLock和mStayOnWhilePluggedInPartialLock都為UnsynchronizedWakeLock型別,它們封裝了WakeLock,可幫助PMS在使用它們時免遭執行緒同步之苦。
5.2.5 初識PowerManagerService總結
這一節向讀者展示了PMS的大體面貌,包括:
· 主要的成員變數及它們的作用和來歷。如有需要,可查閱表5-1和5-2。
· 見識了PMS中幾個主要的函式,其中有一些將留到後文進行深入分析,現在只需要瞭解其大概作用即可。
5.3 PMS WakeLock分析
WakeLock是Android提供給應用程式獲取電力資源的唯一方法。只要還有地方在使用WakeLock,系統就不會進入休眠狀態。
WakeLock的一般使用方法如下:
PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
//①建立一個WakeLock,注意它的引數
PowerManager.WakeLock wl =pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK,
"MyTag");
wl.acquire();//②獲取該鎖
......//工作
wl.release();//③釋放該鎖
以上程式碼中共列出三個關鍵點,本章將分析前兩個(在此基礎上,讀者可自行分析release函式)。
這3個函式都由PMS的Binder客戶端的PowerManager使用,所以將本次分析劃分為客戶端和服務端兩大部分。
5.3.1 WakeLock客戶端分析
1. newWakeLock分析
通過PowerManager(以後簡稱PM)的newWakeLock將建立一個WakeLock,程式碼如下:
public WakeLock newWakeLock(int flags, String tag)
{
//tag不能為null,否則拋異常
return new WakeLock(flags, tag);//WakeLock為PM的內部類,第一個引數flags很關鍵
}
WakeLock的第一個引數flags很關鍵,它用於控制CPU/Screen/Keyboard的休眠狀態。flags的可選值如表5-3所示。
表5-3 WakeLock 的flags引數說明
flags值 | CPU | Screen | Keyboard | 備註 |
PARTIAL_WAKE_LOCK | On | Off | Off | 不受電源鍵影響 |
SCREEN_DIM_WAKE_LOCK | On | Dim | Off | 按下電源鍵後,系統還是會進入休眠狀態 |
SCREEN_BRIGHT_WAKE_LOCK | On | Bright | Off | |
FULL_WAKE_LOCK | On | Bright | On | |
ACQUIRE_CAUSES_WAKEUP | 說明:在正常情況下,獲取WakeLock並不會喚醒機器(例如acquire之前機器處於關屏狀態,則無法喚醒)。加上該標誌後,acquire WakeLock同時也能喚醒機器(即點亮螢幕等)。該標誌常用於提示框、來電提醒等應用場景 | |||
ON_AFTER_RELEASE | 說明:和使用者體驗有關,當WakeLock釋放後,如沒有該標誌,系統會立即黑屏。有了該標誌,系統會延時一段時間再黑屏 |
由表5-3可知:
· WakeLock只控制CPU、螢幕和鍵盤三大部分。
· 表中最後兩項是附加標誌,和前面的其他WAKE_LOCK標誌組合使用。注意, PARTIAL_WAKE_LOCK比較特殊,附加標誌不能影響它。
· PARTIAL_WAKE_LOCK不受電源鍵控制,即按電源鍵不能使PARTIAL_WAKE_LOCK系統進入休眠狀態(螢幕可以關閉,但CPU不會休眠)。
瞭解了上述知識後,再來看如下程式碼:
[-->PowerManager.java::WakeLock]
WakeLock(int flags, String tag)
{
//檢查flags引數是否非法
mFlags =flags;
mTag =tag;
//建立一個Binder物件,除了做Token外,PMS需要監視客戶端的生死狀況,否則有可能導致
//WakeLock不能被釋放
mToken= new Binder();
}
客戶端建立WakeLock後,需要呼叫acquire以確保電力資源供應正常。下面對acquire程式碼進行分析。
2. acquire分析
[-->PowerManager.java::WakeLock.acquire函式]
public void acquire()
{
synchronized (mToken) {
acquireLocked();//呼叫acquireLocked函式
}
}
//acquireLoced函式
private void acquireLocked() {
if(!mRefCounted || mCount++ == 0) {
mHandler.removeCallbacks(mReleaser);//引用計數控制
try {
//呼叫PMS的acquirewakeLock,注意這裡傳遞的引數,其中mWorkSource為空
mService.acquireWakeLock(mFlags, mToken, mTag, mWorkSource);
}......
mHeld =true;
}
}
上邊程式碼中呼叫PMS的acquireWakeLock函式與PMS互動,該函式最後一個引數為WorkSource類。這個類從Android 2.2開始就存在,但一直沒有明確的作用,下面是關於它的一段說明。
/** 見WorkSoure.java
* Describesthe source of some work that may be done by someone else.
* Currentlythe public representation of what a work source is is not
* defined;this is an opaque container.
*/
由以上註釋可知,WorkSource本意用來描述某些任務的Source。傳遞此Source給其他人,這些人就可以執行該Source對應的工作。目前使用WorkSource的地方僅是ContentService中的SynManager。讀者暫時可不理會WorkSource。
客戶端的功能比較簡單,和PMS僅通過acquireWakeLock函式互動。下面來分析服務端的工作。
5.3.2 PMSacquireWakeLock分析
[-->PowerManagerService.java::acquireWakeLock]
public void acquireWakeLock(int flags, IBinderlock, String tag, WorkSource ws) {
intuid = Binder.getCallingUid();
intpid = Binder.getCallingPid();
if(uid != Process.myUid()) {
mContext.enforceCallingOrSelfPermission(//檢查WAKE_LOCK許可權
android.Manifest.permission.WAKE_LOCK,null);
}
if(ws != null) {
//如果ws不為空,需要檢查呼叫程式是否有UPDATE_DEVICE_STATS的許可權
enforceWakeSourcePermission(uid, pid);
}
longident = Binder.clearCallingIdentity();
try{
synchronized (mLocks) {呼叫acquireWakeLockLocked函式
acquireWakeLockLocked(flags, lock, uid, pid, tag, ws);
}
} ......
}
接下來分析acquireWakeLockLocked函式。由於此段程式碼較長,宜分段來看。
1. acquireWakeLockLocked分析之一
開始分析之前,有必要先介紹另外一個資料結構,它為PowerManagerService的內部類,名字也為WakeLock。其定義如下:
[-->PowerManagerService.java]
class WakeLock implements IBinder.DeathRecipient
PMS的WakeLock實現了DeathRecipient介面。根據前面Binder系統的知識可知,當Binder服務端死亡後,Binder系統會向註冊了訃告接收的Binder客戶端傳送訃告通知,因此客戶端可以做一些資源清理工作。在本例中,PM.WakeLock是Binder服務端,而PMS.WakeLock是Binder客戶端。假如PM.WakeLock所在程式在release喚醒鎖(即WakeLock)之前死亡,PMS.WakeLock的binderDied函式則會被呼叫,這樣,PMS也能及時進行釋放(release)工作。對於系統的重要資源來說,採用這種安全保護措施尤其必要。
回到acquireWakeLockLocked函式,先看第一段程式碼:
[-->PowerManagerService.java::acquireWakeLockLocked]
public void acquireWakeLockLocked(int flags,IBinder lock, int uid,
int pid, Stringtag,WorkSource ws) {
......
//mLocks是一個ArrayList,儲存PMS.WakeLock物件
int index= mLocks.getIndex(lock);
WakeLockwl;
booleannewlock;
booleandiffsource;
WorkSourceoldsource;
if (index< 0) {
//建立一個PMS.WakeLock物件,儲存客戶端acquire傳來的引數
wl = new WakeLock(flags, lock, tag, uid, pid);
switch(wl.flags & LOCK_MASK)
{ //將flags轉換成對應的minState
casePowerManager.FULL_WAKE_LOCK:
if(mUseSoftwareAutoBrightness) {
wl.minState = SCREEN_BRIGHT;
}else {
wl.minState = (mKeyboardVisible ? ALL_BRIGHT: SCREEN_BUTTON_BRIGHT);
}
break;
casePowerManager.SCREEN_BRIGHT_WAKE_LOCK:
wl.minState = SCREEN_BRIGHT;
break;
casePowerManager.SCREEN_DIM_WAKE_LOCK:
wl.minState = SCREEN_DIM;
break;
case PowerManager.PARTIAL_WAKE_LOCK:
//PROXIMITY_SCREEN_OFF_WAKE_LOCK在SDK中並未輸出,原因是有部分手機並沒有接近
//感測器
casePowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK:
break;
default:
return;
}
mLocks.addLock(wl);//將PMS.WakeLock物件儲存到mLocks中
if (ws!= null) {
wl.ws = new WorkSource(ws);
}
newlock= true; //設定幾個引數資訊,newlock表示新建立了一個PMS.WakeLock物件
diffsource = false;
oldsource = null;
}else{
//如果之前儲存有PMS.WakeLock,則要判斷新傳入的WorkSource和之前儲存的WorkSource
//是否一樣。此處不討論這種情況
......
}
在上面程式碼中,很重要一部分是將前面flags資訊轉成PMS.WakeLock的成員變數minState,下面是對轉換關係的總結。
· FULL_WAKE_LOCK:當啟用mUseSoftwareAutoBrightness時,minState為SCREEN_BRIGHT(表示螢幕全亮),否則為ALL_BRIGHT(螢幕、鍵盤、按鍵全亮。注意,只有在開啟鍵盤時才能選擇此項)或SCREEN_BUTTON_BRIGHT(螢幕、按鍵全亮)。
· SCREEN_BRIGHT_WAKE_LOCK:minState為SCREEN_BRIGHT,表示螢幕全亮。
· SCREEN_DIM_WAKE_LOCK:minState為SCREEN_DIM,表示螢幕Dim。
· 對PARTIAL_WAKE_LOCK和PROXIMITY_SCREEN_OFF_WAKE_LOCK情況不做處理。
該做的準備工作都做了,下面來看第二階段的工作是什麼。
2. acquireWakeLockLocked分析之二
程式碼如下:
//isScreenLock用於判斷flags是否和螢幕有關,除PARTIAL_WAKE_LOCK外,其他WAKE_LOCK
//都和螢幕有關
if (isScreenLock(flags)) {
if ((flags& LOCK_MASK) == PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK) {
mProximityWakeLockCount++;//引用計數控制
if(mProximityWakeLockCount == 1) {
enableProximityLockLocked();//使能Proximity感測器
}
} else {
if((wl.flags & PowerManager.ACQUIRE_CAUSES_WAKEUP) != 0) {
......//ACQUIRE_CAUSES_WAKEUP標誌處理
} else {
//①gatherState返回一個狀態,稍後分析該函式
mWakeLockState = (mUserState | mWakeLockState) &mLocks.gatherState();
}
//②設定電源狀態,
setPowerState(mWakeLockState | mUserState);
}
}
以上程式碼列出了兩個關鍵函式,一個是gatherState,另外一個是setPowerState,下面來分析它們。
(1) gatherState分析
gatherState函式的程式碼如下:
[-->PowerManagerService.java::gatherState]
int gatherState()
{
intresult = 0;
int N =this.size();
for (inti=0; i<N; i++) {
WakeLock wl = this.get(i);
if(wl.activated)
if(isScreenLock(wl.flags))
result |= wl.minState;//對系統中所有活躍PMS.WakeLock的狀態進行或操作
}
returnresult;
}
由以上程式碼可知,gatherState將統計當前系統內部活躍WakeLock的minState。這裡為什麼要“使用”或“操作”呢?舉個例子,假如WakeLock A的minState為SCREEN_DIM,而WakeLock B的minState為SCREEN_BRIGHT,二者共同作用,最終的螢幕狀態顯然應該是SCREEN_BRIGHT。
提示讀者也可參考PowerManagerService中SCREEN_DIM等變數的定義。
下面來看setPowerState,本章前面曾兩次對該函式避而不談,現在該見識見識它了。
(2) setPowerState分析
setPowerState用於設定電源狀態,先來看其在程式碼中的呼叫:
setPowerState(mWakeLockState | mUserState);
在以上程式碼中除了mWakeLockState外,還有一個mUserState。根據前面對gatherState函式的介紹可知,mWakeLockState的值來源於系統當前活躍WakeLock的minState。那麼mUserState代表什麼呢?
mUserState代表使用者觸發事件導致的電源狀態。例如,按Home鍵後,將該值設定為SCREEN_BUTTON_BRIGHT(假設手機沒有鍵盤)。很顯然,此時系統的電源狀態應該是mUserState和mWakeLockState的組合。
提示 “一個小小的變數背後代表了一個很重要的case”,讀者能體會到嗎?
下面來看setPowerState的程式碼,這段程式碼較長,也適合分段來看。第一段程式碼如下:
[-->PowerManagerService.java::setPowerState]
private void setPowerState(int state)
{//呼叫另外一個同名函式
setPowerState(state, false,WindowManagerPolicy.OFF_BECAUSE_OF_TIMEOUT);
}
//setPowerState
private void setPowerState(int newState, booleannoChangeLights, int reason)
{
synchronized (mLocks) {
int err;
if (noChangeLights)//在這種情況中,noChangeLights為false
newState = (newState & ~LIGHTS_MASK) | (mPowerState &LIGHTS_MASK);
if(mProximitySensorActive)//如果開啟了接近感應器,就不需要在這裡點亮螢幕了
newState = (newState & ~SCREEN_BRIGHT);
if(batteryIsLow())//判斷是否處於低電狀態
newState |= BATTERY_LOW_BIT;
else
newState &= ~BATTERY_LOW_BIT;
......
//如果還沒啟動完成,則需要將newState置為ALL_BRIGHT。細心的讀者有沒有發現,在手機開機過程中
//鍵盤、螢幕、按鍵等都會全部點亮一會兒呢?
if(!mBootCompleted && !mUseSoftwareAutoBrightness)
newState |= ALL_BRIGHT;
booleanoldScreenOn = (mPowerState & SCREEN_ON_BIT) != 0;
boolean newScreenOn = (newState &SCREEN_ON_BIT) != 0;
finalboolean stateChanged = mPowerState != newState;
第一段程式碼主要用於得到一些狀態值,例如在新狀態下螢幕是否需要點亮(newScreenOn)等。再來看第二段程式碼,它將根據第一段的狀態值完成對應的工作。
[-->PowerManagerService::setPowerState]
if(oldScreenOn != newScreenOn) {
if(newScreenOn) {
if(mStillNeedSleepNotification) {
//對sendNotificationLocked函式的分析見後文
sendNotificationLocked(false,
WindowManagerPolicy.OFF_BECAUSE_OF_USER);
}// mStillNeedSleepNotification判斷
booleanreallyTurnScreenOn = true;
if(mPreventScreenOn)// mPreventScreenOn是何方神聖?
reallyTurnScreenOn= false;
if(reallyTurnScreenOn) {
err = setScreenStateLocked(true);//點亮螢幕
......//通知mBatteryStats做電量統計
mBatteryStats.noteScreenBrightness(getPreferredBrightness());
mBatteryStats.noteScreenOn();
} else {//reallyTurnScreenOn為false
setScreenStateLocked(false);//關閉螢幕
err =0;
}
if (err == 0) {
sendNotificationLocked(true, -1);
if(stateChanged)
updateLightsLocked(newState, 0);//點亮按鍵燈或者鍵盤燈
mPowerState |= SCREEN_ON_BIT;
}
}
以上程式碼看起來比較簡單,就是根據情況點亮或關閉螢幕。事實果真的如此嗎?的還記得前面所說“一個小小的變數背後代表一個很重要的case”這句話嗎?是的,這裡也有一個很重要的case,由mPreventScreenOn表達。這是什麼意思呢?
PMS提供了一個函式叫preventScreenOn,該函式(在SDK中未公開)使應用程式可以阻止螢幕點亮。為什麼會有這種操作呢?難道是因為該應用很醜,以至於不想讓別人看見?根據該函式的解釋,在兩個應用之間進行切換時(尤其是正在啟動一個Activity卻又接到來電通知時),很容易出現閃屏現象,會嚴重影響使用者體驗。因此提供了此函式,由應用來呼叫並處理它。
注意閃屏的問題似乎解決了,但事情還沒完,這個解決方案還引入了另外一個問題:假設應用忘記重新使螢幕點亮,手機豈不是一直就黑屏了?為此,在程式碼中增加了一段處理邏輯,即如果5秒鐘後應用還沒有使螢幕點亮,PMS將自己設定mPreventScreenOn為false。
Google怎麼會寫這種程式碼?還好,程式碼開發者也意識到這是一個很難看的方法,只是目前還沒有一個比較完美的解決方案而已。
繼續看setPowerState最後的程式碼:
else {//newScreenOn為false的情況
......//更新鍵盤燈、按鍵燈的狀態
//從mHandler中移除mAutoBrightnessTask,這和光感測器有關。此處不討論
mHandler.removeCallbacks(mAutoBrightnessTask);
mBatteryStats.noteScreenOff();//通知BatteryStatsService,螢幕已關
mPowerState = (mPowerState & ~LIGHTS_MASK) | (newState & LIGHTS_MASK);
updateNativePowerStateLocked();
}
}//if(oldScreenOn != newScreenOn)判斷結束
else if(stateChanged) {//螢幕的狀態不變,但是light的狀態有可能變化,所以
updateLightsLocked(newState, 0);//單獨更新light的狀態
}
mPowerState= (mPowerState & ~LIGHTS_MASK) | (newState & LIGHTS_MASK);
updateNativePowerStateLocked();
}//setPowerState完畢
setPowerState函式是在PMS中真正設定螢幕及Light狀態的地方,其內部將通過Power類與這些硬體互動。相關內容見5.3.3節。
(3) sendNotificationLocked函式分析
sendNotificationLocked函式用於觸發SCREEN_ON/OFF廣播的傳送,來看以下程式碼:
[-->PowerManagerService.java::sendNotificationLocked]
private void sendNotificationLocked(boolean on,int why) {
......
if (!on) {
mStillNeedSleepNotification = false;
}
int index= 0;
while(mBroadcastQueue[index] != -1) {
index++;
}
// mBroadcastQueue和mBroadcastWhy均定義為int陣列,成員個數為3,它們有什麼作用呢
mBroadcastQueue[index] = on ? 1 : 0;
mBroadcastWhy[index] = why;
/* mBroadcastQueue陣列一共有3個元素,根據程式碼中的註釋,其作用如下:
當取得的index為2時,即0,1元素已經有值,由於螢幕ON/OFF請求是配對的,所以在這種情況
下只需要處理最後一次的請求。例如0元素為ON,1元素為OFF,2元素為ON,則可以去掉0,
1的請求,而直接處理2的請求,即螢幕ON。對於那種頻繁按Power鍵的操作,通過這種方式可以
節省一次切換操作
*/
if (index== 2) {
if (!on&& mBroadcastWhy[0] > why) mBroadcastWhy[0] = why;
//處理index為2的情況,見上文的說明
mBroadcastQueue[0] = on ? 1 : 0;
mBroadcastQueue[1] = -1;
mBroadcastQueue[2] = -1;
mBroadcastWakeLock.release();
index =0;
}
/*
如果index為1,on為false,即螢幕發出關閉請求,則無需處理。根據註釋中的說明,
在此種情況,螢幕已經處於OFF狀態,所以無需處理。為什麼在此種情況下螢幕已經關閉了呢?
*/
if (index== 1 && !on) {
mBroadcastQueue[0] = -1;
mBroadcastQueue[1] = -1;
index = -1;
mBroadcastWakeLock.release();
}
if(mSkippedScreenOn) {
updateLightsLocked(mPowerState, SCREEN_ON_BIT);
}
//如果index不為負數,則拋送mNotificationTask給mHandler處理
if (index>= 0) {
mBroadcastWakeLock.acquire();
mHandler.post(mNotificationTask);
}
}
sendNotificationLocked函式相當詭異,主要是mBroadcastQueue陣列的使用讓人感到困惑。其目的在於減少不必要的螢幕切換和廣播傳送,但是為什麼index為1時,螢幕處於OFF狀態呢?下面來分析mNotificationTask,希望它能回答這個問題。
[-->PowerManagerService.java::mNotificationTask]
private Runnable mNotificationTask = newRunnable()
{
publicvoid run()
{
while(true) {//此處是一個while迴圈
intvalue;
int why;
WindowManagerPolicy policy;
synchronized (mLocks) {
value =mBroadcastQueue[0];//取mBroadcastQueue第一個元素
why= mBroadcastWhy[0];
for(int i=0; i<2; i++) {//將後面的元素往前挪一位
mBroadcastQueue[i] = mBroadcastQueue[i+1];
mBroadcastWhy[i] = mBroadcastWhy[i+1];
}
policy = getPolicyLocked();//policy指向PhoneWindowManager
if(value == 1 && !mPreparingForScreenOn) {
mPreparingForScreenOn = true;
mBroadcastWakeLock.acquire();
}
}// synchronized結束
if(value == 1) {//value為1,表示發出螢幕ON請求
mScreenOnStart = SystemClock.uptimeMillis();
//和WindowManagerService互動,和鎖屏介面有關
//mScreenOnListener為回撥通知物件
policy.screenTurningOn(mScreenOnListener);
ActivityManagerNative.getDefault().wakingUp();//和AMS互動
if (mContext != null &&ActivityManagerNative.isSystemReady()) {
//傳送SCREEN_ON廣播
mContext.sendOrderedBroadcast(mScreenOnIntent,null,
mScreenOnBroadcastDone, mHandler, 0, null, null);
}......
}elseif (value == 0) {
mScreenOffStart = SystemClock.uptimeMillis();
policy.screenTurnedOff(why);//通知WindowManagerService
ActivityManagerNative.getDefault().goingToSleep();//和AMS互動
if(mContext != null && ActivityManagerNative.isSystemReady()) {
//傳送螢幕OFF廣播
mContext.sendOrderedBroadcast(mScreenOffIntent, null,
mScreenOffBroadcastDone, mHandler, 0, null,null);
}
}elsebreak;
}
};
mNotificationTask比較複雜,但是它對mBroadcastQueue的處理比較有意思,每次取出第一個元素值後,將後續元素往前挪一位。這種處理方式能解決之前提出的那個問題嗎?
說實話,目前筆者也沒找到能解釋index為1時,螢幕一定處於OFF的證據。如果有哪位讀者找到證據,不妨分享一下。
另外,mNotificationTask和ActivityManagerService及WindowManagerService都有互動。因為這兩個服務內部也使用了WakeLock,所以需要通知它們釋放WakeLock,否則會導致不必要的電力資源消耗。具體內容只能留待以後分析相關服務時再來討論了。
(4) acquireWakeLocked第二階段工作總結
acquireWakeLocked第二階段工作是處理和螢幕相關的WAKE_LOCK方面的工作(isScreenLock返回為true的情況)。其中一個重要的函式就是setPowerState,該函式將根據不同的狀態設定螢幕光、鍵盤燈等硬體裝置。注意,和硬體互動相關的工作是通過Power類提供的介面完成的。
3. acquireWakeLocked分析之三
acquireWakeLocked處理WAKE_LOCK為PARTIAL_WAKE_LOCK的情況。來看以下程式碼:
[-->PowerManagerService.java::acquiredWakeLockLocked]
else if ((flags & LOCK_MASK) == PowerManager.PARTIAL_WAKE_LOCK){
if(newlock) {
mPartialCount++;
}
//獲取kernel層的PARTIAL_WAKE_LOCK,該函式後續再分析
Power.acquireWakeLock(Power.PARTIAL_WAKE_LOCK,PARTIAL_NAME);
}//else if判斷結束
if(diffsource) {
noteStopWakeLocked(wl, oldsource);
}
if(newlock || diffsource) {
noteStartWakeLocked(wl, ws);//通知BatteryStatsService做電量統計
}
當客戶端使用PARTIAL_WAKE_LOCK時,PMS會呼叫Power.acquireWakeLock申請一個核心的WakeLock。
4. acquireWakeLock總結
acquireWakeLock有三個階段的工作,總結如下:
· 如果對應的WakeLock不存在,則建立一個WakeLock物件,同時將WAKE_LOCK標誌轉換成對應的minState;否則,從mLocks中查詢對應的WakeLock物件,然後更新其中的資訊。
· 當WAKE_LOCK標誌和螢幕有關時,需要做相應的處理,例如點亮螢幕、開啟按鍵燈等。實際上這些工作不僅影響電源管理,還會影響到使用者感受,所以其中還穿插了一些和使用者體驗有關的處理邏輯(如上面註釋的mPreventScreenOn變數)。
· 當WAKE_LOCK和PARTIAL_WAKE_LOCK有關時,僅簡單呼叫Power的acquireWakeLock即可,其中涉及和Linux Kernel電源管理系統的互動。
5.3.3 Power類及LightService類介紹
根據前面的分析,PMS有時需要進行點亮螢幕,開啟鍵盤燈等操作,為此Android提供了Power類及LightService滿足PMS的要求。這兩個類比較簡單,但是其背後的Kernel層相對複雜一些。本章僅分析使用者空間的內容,有興趣的讀者不妨以此為入口,深入研究Kernel層的實現。
1. Power類介紹
Power類提供了6個函式,如下所示:
[-->Power.java]
int setScreenState(boolean on);//開啟或關閉螢幕光
int setLastUserActivityTimeout(long ms);//設定超時時間
void reboot(String reason);//用於手機重啟,內部呼叫rebootNative
void shutdown();//已作廢,建議不要呼叫
void acquireWakeLock(int lock, String id);//獲取Kernel層的WakeLock
void releaseWakeLock(String id);//釋放Kernel層的WakeLock
這些函式固有的實現程式碼如下:
[-->android_os_Power.cpp]
static void acquireWakeLock(JNIEnv *env, jobjectclazz, jint lock, jstring idObj)
{
......
constchar *id = env->GetStringUTFChars(idObj, NULL);
acquire_wake_lock(lock, id);//呼叫此函式和Kernel層互動
env->ReleaseStringUTFChars(idObj, id);
}
static void releaseWakeLock(JNIEnv *env, jobjectclazz, jstring idObj)
{
constchar *id = env->GetStringUTFChars(idObj, NULL);
release_wake_lock(id);//釋放Kernel層的WakeLock
env->ReleaseStringUTFChars(idObj,id);
}
static int setLastUserActivityTimeout(JNIEnv *env,jobject clazz, jlong timeMS)
{
returnset_last_user_activity_timeout(timeMS/1000);//設定超時時間
}
static int setScreenState(JNIEnv *env, jobjectclazz, jboolean on)
{
return set_screen_state(on);//開啟或關閉螢幕光
}
static void android_os_Power_shutdown(JNIEnv *env,jobject clazz)
{
android_reboot(ANDROID_RB_POWEROFF, 0, 0);//關機
}
static void android_os_Power_reboot(JNIEnv *env,jobject clazz, jstring reason)
{
if (reason== NULL) {
android_reboot(ANDROID_RB_RESTART, 0, 0);//重啟
} else {
const char *chars = env->GetStringUTFChars(reason, NULL);
android_reboot(ANDROID_RB_RESTART2, 0, (char *) chars);//重啟
env->ReleaseStringUTFChars(reason, chars);
}
jniThrowIOException(env, errno);
}
Power類提供了和核心互動的通道,讀者僅作了解即可。
2. LightService介紹
LightService.java比較簡單,這裡直接介紹Native層的實現,主要關注HAL層的初始化函式init_native及操作函式setLight_native。
首先來看初始化函式init_native,其程式碼如下:
[com_android_server_LightService.cpp::init_native]
static jint init_native(JNIEnv *env, jobjectclazz)
{
int err;
hw_module_t* module;
Devices*devices;
devices= (Devices*)malloc(sizeof(Devices));
//初始化硬體相關的模組,模組名為“lights”
err =hw_get_module(LIGHTS_HARDWARE_MODULE_ID,
(hw_module_tconst**)&module);
if (err== 0) {
devices->lights[LIGHT_INDEX_BACKLIGHT]//背光
= get_device(module, LIGHT_ID_BACKLIGHT);
devices->lights[LIGHT_INDEX_KEYBOARD]//鍵盤燈
= get_device(module, LIGHT_ID_KEYBOARD);
devices->lights[LIGHT_INDEX_BUTTONS]//按鍵燈
= get_device(module, LIGHT_ID_BUTTONS);
devices->lights[LIGHT_INDEX_BATTERY]//電源指示燈
= get_device(module, LIGHT_ID_BATTERY);
devices->lights[LIGHT_INDEX_NOTIFICATIONS] //通知燈
= get_device(module, LIGHT_ID_NOTIFICATIONS);
devices->lights[LIGHT_INDEX_ATTENTION] //警示燈
= get_device(module, LIGHT_ID_ATTENTION);
devices->lights[LIGHT_INDEX_BLUETOOTH] //藍芽提示燈
= get_device(module, LIGHT_ID_BLUETOOTH);
devices->lights[LIGHT_INDEX_WIFI] //WIFI提示燈
= get_device(module, LIGHT_ID_WIFI);
} else {
memset(devices, 0, sizeof(Devices));
}
return(jint)devices;
}
Android系統想得很周到,提供了多達8種不同型別的燈。可是有多少手機包含了所有的燈呢?
PMS點亮或關閉燈時,將呼叫setLight_native函式,其程式碼如下:
[com_android_server_LightService.cpp::setLight_native]
static void setLight_native(JNIEnv *env, jobjectclazz, int ptr,
intlight, int colorARGB, int flashMode, int onMS, int offMS,
intbrightnessMode)
{
Devices*devices = (Devices*)ptr;
light_state_t state;
......
memset(&state, 0, sizeof(light_state_t));
state.color = colorARGB; //設定顏色
state.flashMode = flashMode; //設定閃光模式
state.flashOnMS = onMS; //和閃光模式有關,例如亮2秒,滅2秒
state.flashOffMS = offMS;
state.brightnessMode = brightnessMode;//
//傳遞給HAL層模組進行處理
devices->lights[light]->set_light(devices->lights[light],&state);
}
5.3.4 WakeLock總結
相信讀者此時已經對WakeLock機制有了比較清晰的認識,此處以flags標籤為出發點,對WakeLock的知識點進行總結。
· 如果flags和螢幕有關(即除PARTIAL_WAKE_LOCK外),則需要更新螢幕、燈光狀態。其中,螢幕操作通過Power類完來成,燈光操作則通過LightService類來完成。
· 如果FLAGS是PARTIAL_WAKE_LOCK,則需要通過Power提供的介面獲取Kernel層的WakeLock。
· 在WakeLock工作流程中還混雜了使用者體驗、光感測器、接近感測器方面的處理邏輯。這部分程式碼集中體現在setPowerState函式中。感興趣的讀者可進行深入研究。
· WakeLock還要通知BatteryStatsService,以幫助其統計電量使用情況。這方面內容放到本章最後再做分析。
另外,PMS在JNI層也儲存了當前螢幕狀態資訊,這是通過updateNativePowerStateLocked完成的,其程式碼如下:
private void updateNativePowerStateLocked() {
nativeSetPowerState(//呼叫native函式,傳入兩個引數
(mPowerState & SCREEN_ON_BIT) != 0,
(mPowerState & SCREEN_BRIGHT) == SCREEN_BRIGHT);
}
//jni層實現程式碼如下
static void android_server_PowerManagerService_nativeSetPowerState(
JNIEnv* env,jobject serviceObj, jboolean screenOn, jbooleanscreenBright) {
AutoMutex _l(gPowerManagerLock);
gScreenOn = screenOn;//螢幕是否開啟
gScreenBright = screenBright; //螢幕光是否全亮
}
PMS的updateNativePowerStateLocked函式曾一度讓筆者感到非常困惑,主要原因是初看此函式名,感覺它極可能會和Kernel層的電源管理系統互動。等深入JNI層程式碼後發現,其功能僅是儲存兩個全域性變數,和Kernel壓根兒沒有關係。其實,和Kernel層電源管理系統互動的主要是Power類。此處的兩個變數是為了方便Native層程式碼查詢當前螢幕狀態而設定的,以後分析Andorid輸入系統時就會搞清楚它們的作用了。
5.4 userActivity及Power按鍵處理分析
本節介紹userActivity函式及PMS對Power按鍵的處理流程。
5.4.1 userActivity分析
前面曾經提到過userActivity的作用,此處舉一個例子加深讀者對它的印象:
開啟手機,並解鎖進入桌面。如果在規定時間內不操作手機,那麼螢幕將變暗,最後關閉。在此過程中,如果觸動螢幕,螢幕又會重新變亮。這個觸動螢幕的操作將導致userActivity函式被呼叫。
在上述例子中實際上包含了兩方面的內容:
· 不操作手機,螢幕將變暗,最後關閉。在PMS中,這是一個狀態切換的過程。
· 操作手機,將觸發userActivity,此後螢幕的狀態將重置。
來看以下程式碼:
[-->PowerManagerService.java::userActivity]
public voiduserActivity(long time, boolean noChangeLights) {
......//檢查呼叫程式是否有DEVICE_POWER的許可權
userActivity(time, -1, noChangeLights, OTHER_EVENT, false);
}
此處將呼叫另外一個同名函式。注意第三個引數的值OTHER_EVENT。系統一共定義了三種事件,分別是OTHER_EVENT(除按鍵、觸控式螢幕外的事件)、BUTTON_EVENT(按鍵事件)和TOUCH_EVENT(觸控式螢幕事件)。它們主要為BatteryStatsService進行電量統計時使用,例如觸控式螢幕事件的耗電量和按鍵事件的耗電量等。
[-->PowerManagerService.java::userActivity]
private void userActivity(long time, long timeoutOverride,
boolean noChangeLights,inteventType, boolean force) {
if(((mPokey & POKE_LOCK_IGNORE_TOUCH_EVENTS) != 0) &&
(eventType == TOUCH_EVENT)) {
//mPokey和輸入事件的處理策略有關。如果此處的if判斷得到滿足,表示忽略TOUCH_EVENT
return;
}
synchronized (mLocks) {
if(isScreenTurningOffLocked()) {
return;
}
if(mProximitySensorActive && mProximityWakeLockCount == 0)
mProximitySensorActive = false;//控制接近感測器
if(mLastEventTime <= time || force) {
mLastEventTime = time;
if((mUserActivityAllowed && !mProximitySensorActive) || force) {
if (eventType == BUTTON_EVENT && !mUseSoftwareAutoBrightness) {
mUserState =(mKeyboardVisible ? ALL_BRIGHT :
SCREEN_BUTTON_BRIGHT);
} else {
mUserState |=SCREEN_BRIGHT;//設定使用者事件導致的mUserState
}
......//通知BatteryStatsService進行電量統計
mBatteryStats.noteUserActivity(uid,eventType);
//重新計算WakeLock狀態
mWakeLockState = mLocks.reactivateScreenLocksLocked();
setPowerState(mUserState | mWakeLockState, noChangeLights,
WindowManagerPolicy.OFF_BECAUSE_OF_USER);
//重新開始螢幕計時
setTimeoutLocked(time, timeoutOverride, SCREEN_BRIGHT);
}
}
}
//mPolicy指向PhoneWindowManager,用於和WindowManagerService互動
if(mPolicy != null) {
mPolicy.userActivity();
}
}
有了前面分析的基礎,相信很多讀者都會覺得userActivity函式很簡單。在前面的程式碼中,通過setPowerState點亮了螢幕,那麼經過一段時間後發生的螢幕狀態切換在哪兒進行呢?來看setTimeoutLocked函式的程式碼:
[-->PowerManagerService.java::setTimeoutLocked]
private void setTimeoutLocked(long now, final longoriginalTimeoutOverride,
intnextState) {
//在本例中,nextState為SCREEN_BRIGHT,originalTimeoutOverride為-1
longtimeoutOverride = originalTimeoutOverride;
if(mBootCompleted) {
synchronized (mLocks) {
long when = 0;
if(timeoutOverride <= 0) {
switch (nextState)
{
case SCREEN_BRIGHT:
when = now + mKeylightDelay;//得到一個超時時間
break;
case SCREEN_DIM:
if (mDimDelay >= 0) {
when = now + mDimDelay;
break;
} ......
case SCREEN_OFF:
synchronized (mLocks) {
when = now +mScreenOffDelay;
}
break;
default:
when = now;
break;
}
}......//處理timeoutOverride大於零的情況,無非就是設定狀態和超時時間
mHandler.removeCallbacks(mTimeoutTask);
mTimeoutTask.nextState = nextState;
mTimeoutTask.remainingTimeoutOverride = timeoutOverride > 0
? (originalTimeoutOverride- timeoutOverride)
: -1;
//拋送一個mTimeoutTask交給mHandler執行,執行時間為when秒後
mHandler.postAtTime(mTimeoutTask, when);
mNextTimeout = when; //除錯用
}
}
}
接下來看mTimeOutTask的程式碼:
private class TimeoutTask implements Runnable
{
intnextState;
longremainingTimeoutOverride;
publicvoid run()
{
synchronized (mLocks) {
if(nextState == -1)return;
mUserState = this.nextState;
//呼叫setPowerState去真正改變螢幕狀態
setPowerState(this.nextState| mWakeLockState);
long now = SystemClock.uptimeMillis();
switch (this.nextState)
{
case SCREEN_BRIGHT:
if (mDimDelay >= 0) {//設定下一個狀態為SCREEN_DIM
setTimeoutLocked(now,remainingTimeoutOverride, SCREEN_DIM);
break;
}
case SCREEN_DIM://設定下一個狀態為SCREEN_OFF
setTimeoutLocked(now, remainingTimeoutOverride, SCREEN_OFF);
break;
}......//省略花括號
}
TimeoutTask就是用來切換螢幕狀態的,相信不少讀者已經在網路上見過一個和PMS螢幕狀態切換相關的圖(其實就是TimeoutTask的工作流程解釋),對此,本章就不再介紹了,希望讀者能通過直接閱讀原始碼加深理解。
5.4.2 Power按鍵處理分析
按鍵處理屬於本書後續將會分析的輸入系統的範圍,此處摘出和Power鍵相關的程式碼進行分析,程式碼如下:
[-->com_android_server_InputManager.cpp::handleInterceptActions]
voidNativeInputManager::handleInterceptActions(jint wmActions, nsecs_t when,
uint32_t& policyFlags) {
//按下Power鍵並鬆開後,將設定wmActions為WM_ACTION_GO_TO_SLEEP,表示需要休眠
if(wmActions & WM_ACTION_GO_TO_SLEEP) {
//利用JNI呼叫PMS的goToSleep函式
android_server_PowerManagerService_goToSleep(when);
}
//一般的輸入事件將觸發userActivity函式被呼叫,此時將喚醒手機
if(wmActions & WM_ACTION_POKE_USER_ACTIVITY) {
//利用JNI呼叫PMS的userActivity函式。相關內容在前一節已經分析過了
android_server_PowerManagerService_userActivity(when,
POWER_MANAGER_BUTTON_EVENT);
}
......//其他處理
}
由以上程式碼中的註釋可知,當按下Power鍵並鬆開時[②],將觸發PMS的goToSleep函式被呼叫。下面來看goToSleep函式的程式碼:
[-->PowerManagerService.java::goToSleep]
public void goToSleep(long time)
{
goToSleepWithReason(time,WindowManagerPolicy.OFF_BECAUSE_OF_USER);
}
public void goToSleepWithReason(long time, intreason)
{
mContext.enforceCallingOrSelfPermission(//檢查呼叫程式是否有DEVICE_POWER許可權
android.Manifest.permission.DEVICE_POWER,null);
synchronized (mLocks) {
goToSleepLocked(time, reason);//呼叫goToSleepLocked函式
}
}
[-->PowerManagerService.java::goToSleepLocked]
private void goToSleepLocked(long time, intreason) {
if(mLastEventTime <= time) {
mLastEventTime = time;
mWakeLockState = SCREEN_OFF;
int N= mLocks.size();
intnumCleared = 0;
boolean proxLock = false;
for(int i=0; i<N; i++) {
WakeLock wl = mLocks.get(i);
if(isScreenLock(wl.flags)) {
if(((wl.flags & LOCK_MASK) ==
PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK)
&& reason == WindowManagerPolicy.OFF_BECAUSE_OF_PROX_SENSOR) {
proxLock = true;//判斷goToSleep的原因是否與接近感測器有關
} else{
mLocks.get(i).activated = false;//禁止和螢幕相關的WakeLock
numCleared++;
}
}// isScreenLock判斷結束
}//for迴圈結束
if(!proxLock) {
mProxIgnoredBecauseScreenTurnedOff = true;
}
mStillNeedSleepNotification = true;
mUserState = SCREEN_OFF;
setPowerState(SCREEN_OFF, false, reason);//關閉螢幕
cancelTimerLocked();//從mHandler中撤銷mTimeoutTask任務
}
}
掌握了前面的基礎知識就會感到Power鍵的處理流程真的是很簡單,讀者是否也有同感呢?
5.5 BatteryService及BatteryStatsServic分析
從前面介紹PMS的程式碼中發現,PMS和系統中其他兩個服務BatterService及BatteryStatsService均有互動,其中:
· BatteryService提供介面用於獲取電池資訊,充電狀態等。
· BatteryStatsService主要用做用電統計,通過它可知誰是系統中的耗電大戶。
下面先來介紹稍簡單的BatteryService。
5.5.1 BatteryService分析
BatteryService由SystemServer建立,程式碼如下:
battery = new BatteryService(context, lights);
ServiceManager.addService("battery",battery);
下面來看BatteryService的建構函式:
[-->BatteryService.java]
public BatteryService(Context context,LightsService lights) {
mContext =context;
mLed = newLed(context, lights);//提示燈控制,感興趣的讀者可自行閱讀相關程式碼
//BatteryService也需要和BatteryStatsService互動
mBatteryStats = BatteryStatsService.getService();
//獲取一些配置引數
mCriticalBatteryLevel = mContext.getResources().getInteger(
com.android.internal.R.integer.config_criticalBatteryWarningLevel);
mLowBatteryWarningLevel = mContext.getResources().getInteger(
com.android.internal.R.integer.config_lowBatteryWarningLevel);
mLowBatteryCloseWarningLevel = mContext.getResources().getInteger(
com.android.internal.R.integer.config_lowBatteryCloseWarningLevel);
//啟動uevent監聽物件,監視power_supply資訊
mPowerSupplyObserver.startObserving("SUBSYSTEM=power_supply");
//如果下列檔案存在,那麼啟動另一個uevent監聽物件。該uevent事件來自invalid charger
//switch裝置(即不匹配的充電裝置)
if (newFile("/sys/devices/virtual/switch/invalid_charger/state").exists()) {
mInvalidChargerObserver.startObserving(
"DEVPATH=/devices/virtual/switch/invalid_charger");
}
update();//①查詢HAL層,獲取此時的電池資訊
}
BatteryService定義了3個非常重要的閾值,分別是:
· mCriticalBatteryLevel表示嚴重低電,其值為4。當電量低於該值時會強制關機。該值由config.xml中的config_criticalBatteryWarningLevel控制。
· mLowBatteryWarningLevel表示低電,值為15,當電量低於該值時,系統會報警,例如閃爍LED燈。該值由config.xml中的config_lowBatteryWarningLevel控制。
· mLowBatteryCloseWarningLevel表示一旦電量大於此值,就脫離低電狀態,即可停止警示燈。該值為20,表示由config.xml中的config_lowBatteryCloseWarningLevel控制。
在BatteryService建構函式的最後呼叫了update函式,該函式將查詢系統電池資訊,以更新BatteryService內部的成員變數。此函式程式碼如下:
[-->BatteryService.java::update]
private synchronized final void update() {
native_update();//到Native層查詢並更新內部變數的值
processValues();//處理更新後的狀態
}
1. native_update函式分析
native_update的實現程式碼如下:
[-->com_android_server_BatteryService.cpp]
static voidandroid_server_BatteryService_update(JNIEnv* env, jobject obj)
{
setBooleanField(env, obj, gPaths.acOnlinePath, gFieldIds.mAcOnline);
......//獲取電池資訊,並通過JNI設定到Java層對應的變數中
setIntField(env, obj, gPaths.batteryTemperaturePath,
gFieldIds.mBatteryTemperature);
constint SIZE = 128;
charbuf[SIZE];
//獲取資訊,以下引數並不是所有手機都支援的
if(readFromFile(gPaths.batteryStatusPath, buf, SIZE) > 0)
env->SetIntField(obj, gFieldIds.mBatteryStatus,getBatteryStatus(buf));
else
env->SetIntField(obj, gFieldIds.mBatteryStatus,
gConstants.statusUnknown);
......
}
一共有哪些電池資訊呢?如表5-4所示。
表5-4 Android系統中的電池資訊
變數名 | 功能 | 備註 |
mAcOnline | 是否用外接充電器充電 | 即用交流電充電 |
mUsbOnline | 是否用USB供電 | 即用USB供電 |
mBatteryStatus | 電池狀態 | 共有5個狀態,詳細內容可參考com_android_server_BatteryService.cpp中BatteryManagerConstants的定義
|
mBatteryHealth | 電池健康狀態 | 共7個狀態,詳細內容可參考com_android_server_BatteryService.cpp中BatteryManagerConstants的定義
|
mBatteryPresent | 是否使用電池 | 有些手機在沒有電池的情況下可直接利用USB/交流供電 |
mBatteryLevel | 電池電量 |
|
mBatteryVoltage | 電池電壓 |
|
mBatteryTemperature | 電池溫度 |
|
mBatteryTechnology | 電池製造技術 | 一般為“Li-poly”即鋰電池技術 |
mBatteryStatus和mBatteryHealth均有幾種不同狀態,詳細資訊可檢視getBatteryStatus和getBatteryHealth函式的實現。
上述資訊均通過從/sys/class/power_supply目錄讀取對應檔案得到。和以往使用固定路徑(可能是Android 2.2版本之前)不同的是,先讀取power_supply目錄中各個子目錄中的type檔案,然後根據type檔案的內容,再做對應處理:
· 如果type檔案的內容為“Mains”:則讀取對應子目錄中的online檔案,可判斷是否為AC充電。
· 如果type檔案的內容為“Battery”:則從對應子目錄中其他的檔案中讀取電池相關的資訊,例如從temp檔案獲取電池溫度,從technology檔案讀取電池製造技術等。
· 如果type檔案的內容為“USB”:讀取該子目錄中的online檔案內容,可判斷是否為USB充電。
提示 讀者可通過dumpsys battery檢視自己手機的電池資訊。
2. processValues分析
獲取了電池資訊後,BatteryService就要做一些處理,此項工作通過processValues完成,其程式碼如下:
[-->BatteryService.java::processValues]
private void processValues() {
longdischargeDuration = 0;
mBatteryLevelCritical = mBatteryLevel <= mCriticalBatteryLevel;
if (mAcOnline) {
mPlugType = BatteryManager.BATTERY_PLUGGED_AC;
} elseif (mUsbOnline) {
mPlugType = BatteryManager.BATTERY_PLUGGED_USB;
} else {
mPlugType = BATTERY_PLUGGED_NONE;
}
//通知BatteryStatsService,該函式以後再分析
mBatteryStats.setBatteryState(mBatteryStatus, mBatteryHealth,
mPlugType, mBatteryLevel, mBatteryTemperature, mBatteryVoltage
);
shutdownIfNoPower();//如果電量不夠,彈出關機對話方塊
shutdownIfOverTemp();//如果電池過熱,彈出關機對話方塊
......//根據當前電池資訊與上次電池資訊比較,判斷是否需要傳送廣播等
if (比較前後兩次電池資訊是否發生變化) {
......//記錄資訊到日誌檔案
Intent statusIntent = new Intent();
statusIntent.setFlags(
Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
if (mPlugType != 0 && mLastPlugType ==0) {
statusIntent.setAction(Intent.ACTION_POWER_CONNECTED);
mContext.sendBroadcast(statusIntent);
}......
if(sendBatteryLow) {
mSentLowBatteryBroadcast = true;//傳送低電提醒
statusIntent.setAction(Intent.ACTION_BATTERY_LOW);
mContext.sendBroadcast(statusIntent);
} ......
mLed.updateLightsLocked();//更新LED燈狀態
mLastBatteryStatus= mBatteryStatus;//儲存新的電池資訊
......
}
processValues函式非常簡單,此處不再詳述。另外,當電池資訊發生改變時,系統會傳送uevent事件給BatteryService,此時BatteryService只要重新呼叫update即可完成工作。
5.5.2 BatteryStatsService分析
BatteryStatsService(為書寫方便,以後簡稱BSS)主要功能是收集系統中各模組和應用程式用電量情況。抽象地說,BSS就是一塊電錶,不過這塊電錶不只是顯示總的耗電量,而是分門別類地顯示耗電量,力圖做到更為精準。
和其他服務不太一樣的是,BSS的建立和註冊是在ActivityManagerService中進行的,相關程式碼如下:
[-->ActivityManagerService.java::ActivityManagerService建構函式]
private ActivityManagerService() {
......//建立BSS物件,傳遞一個File物件,指向/data/system/batterystats.bin
mBatteryStatsService= new BatteryStatsService(new File(
systemDir, "batterystats.bin").toString());
}
[-->ActivityManagerService.java::main]
//呼叫BSS的publish函式,在內部將其註冊到ServiceManager
m.mBatteryStatsService.publish(context);
下面來分析BSS的建構函式,見識一下這塊電錶的樣子。
1. BatteryStatsService介紹
讓人大跌眼鏡的是,BSS其實只是一個殼,具體功能委託BatteryStatsImpl(以後簡稱BSImpl)來實現,程式碼如下:
[-->BatteryStatsService.java::BatteryStatsService建構函式]
BatteryStatsService(String filename) {
mStats = new BatteryStatsImpl(filename);
}
圖5-2展示了BSS及BSImpl的家族圖譜。
圖5-2 BSS及BSImpl家族圖譜
由圖5-2可知:
· BSS通過成員變數mStats指向一個BSImpl型別的物件。
· BSImpl從BatteryStats類派生。更重要的是,該類實現了Parcelable介面,由此可知,BSImpl物件的資訊可以寫到Parcel包中,從而可通過Binder在程式間傳遞。實際上,在Android手機的設定中查到的用電資訊就是來自BSImpl的。
BSS的getStatistics函式提供了查詢系統用電資訊的介面,程式碼如下:
public byte[] getStatistics() {
mContext.enforceCallingPermission(//檢查呼叫程式是否有BATTERY_STATS許可權
android.Manifest.permission.BATTERY_STATS, null);
Parcel out= Parcel.obtain();
mStats.writeToParcel(out, 0);//將BSImpl資訊寫到資料包中
byte[]data = out.marshall();//序列化為一個buffer,然後通過Binder傳遞
out.recycle();
returndata;
}
由此可以看出,電量統計的核心類是BSImpl,下面就來分析它。
2. 初識BSImpl
BSImpl功能是進行電量統計,那麼是否存在計量工具呢?答案是肯定的,並且BSImpl使用了不止一種的計量工具。
(1) 計量工具和統計物件介紹
BSImpl一共使用了4種計量工具,如圖5-3所示。
圖5-3 計量工具圖例
由圖5-3可知:
· 一共有兩大類計量工具,Counter用於計數,Timer用於計時。
· BSImpl實現了StopwatchTimer(即所謂的秒錶)、SamplingTimer(抽樣計時)、Counter和SamplingCounter(抽樣計數)等4個具體的計量工具。
· BSImpl中定義了一個Unpluggable介面。當手機插上USB線充電(不論是由AC還是由USB供電)時,該介面的plug函式被呼叫。反之,當拔去USB線時,該介面的unplug函式被呼叫。設定這個介面的目的是為了滿足BSImpl對各種情況下系統用電量的統計要求。關於Unpluggable介面的作用,在後續內容中可以能見到。
雖然只有4種計量工具(筆者覺得已經相當多了),但是可以在很多地方使用它們。下面先來認識部分被掛牌要求統計用電量的物件,如表5-5所示。
表5-5 用電量統計項
成員變數名 | 型別 | 備註 |
mScreenOnTimer | StopwatchTimer | 統計螢幕開啟耗電量 |
mScreenBrightnessTimer[] | StopwatchTimer | 統計各級螢幕亮度(共5級)情況下的耗電量 |
mInputEventCounter | Counter | 統計輸入事件耗電量 |
mPhoneOnTimer | StopwatchTimer | 統計通話耗電量 |
mPhoneSignalStrengthsTimer[] | StopwatchTimer | 統計手機訊號各級強度耗電量,共5級 |
mPhoneSignalScanningTimer | StopwatchTimer | 統計搜尋手機訊號耗電量 |
mPhoneDataConnectionsTimer[] | StopwatchTimer | 統計手機使用各種資料通訊方式(如GPRS、CDMA等)的用電量,一共15級 |
mWifiOnTimer | StopwatchTimer | Wifi用電量(包括使用網路和開啟Wifi功能卻沒有使用網路的情況) |
mGlobalWifiRunningTimer | StopwatchTimer | 使用Wifi的用電量 |
mAudioOnTimer | StopwatchTimer | 使用Audio的耗電量 |
mVideoOnTimer | StopwatchTimer | 使用Video的耗電量 |
表5-5中的電量統計項已經夠多了吧?還不止這些,為了做到更精確,Android還希望能統計每個程式在各種情況下的耗電量。這是一項龐大的工程,怎麼做到的呢?來看下一節的內容。
(2) BatteryStats.Uid介紹
在Android 4.0中,和程式相關的用電量統計並非以單個PID為劃分單元,而是以Uid為組,相關類結構如圖5-4所示。
圖5-4 BatteryStats.Uid家族
由圖5-4可知:
· Wakelock用於統計該Uid對應程式使用wakeLock的情況。
· Proc用於統計Uid中某個程式的電量使用情況。
· Pkg用於統計某個特定Package的使用情況,其內部類Serv用於統計該Pkg中Service的用電情況。
· Sensor用於統計感測器用電情況。
基於以上的瞭解,以後分析將會輕鬆很多,下面來分析它的程式碼。
3. BSImpl流程分析
(1) 建構函式分析
先分析建構函式,程式碼如下:
[-->BatteryStatsImpl.java::BatteryStatsImpl建構函式]
public BatteryStatsImpl(String filename) {
//JournaledFile為日誌檔案物件,內部包含兩個檔案,原始檔案和臨時檔案。目的是雙備份,
//以防止在讀寫過程中檔案資訊丟失或出錯
mFile =new JournaledFile(new File(filename), new File(filename + ".tmp"));
mHandler= new MyHandler();//建立一個Handler物件
mStartCount++;
//建立表5-5中的用電統計項物件
mScreenOnTimer = new StopwatchTimer(null, -1, null, mUnpluggables);
for (inti=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
mScreenBrightnessTimer[i] = new StopwatchTimer(null, -100-i, null,
mUnpluggables);
}
mInputEventCounter = new Counter(mUnpluggables);
......
mOnBattery= mOnBatteryInternal = false;//設定這兩位成員變數為false
initTimes();//①初始化統計時間
mTrackBatteryPastUptime = 0;
mTrackBatteryPastRealtime = 0;
mUptimeStart= mTrackBatteryUptimeStart =
SystemClock.uptimeMillis()* 1000;
mRealtimeStart= mTrackBatteryRealtimeStart =
SystemClock.elapsedRealtime()* 1000;
mUnpluggedBatteryUptime = getBatteryUptimeLocked(mUptimeStart);
mUnpluggedBatteryRealtime = getBatteryRealtimeLocked(mRealtimeStart);
mDischargeStartLevel = 0;
mDischargeUnplugLevel = 0;
mDischargeCurrentLevel = 0;
initDischarge(); //②初始化和電池level有關的成員變數
clearHistoryLocked();//③刪除用電統計的歷史記錄
}
要看懂這段程式碼比較困難,主要原因是變數太多,並且沒有註釋說明。只能根據名字來推測了。在以上程式碼中除了計量工具外,還出現了三大類變數:
· 用於統計時間的變數,例如mUptimeStart、mTrackBatteryPastUptime等。這些引數的初始化函式為initTimes。注意,系統時間分為uptime和realtime。uptime和realtime的時間起點都從系統啟動開始算(since the system was booted),但是uptime不包括系統休眠時間,而realtime包括系統休眠時間[③]。
· 用於記錄各種情況下電池電量的變數,如mDischargeStartLevel、mDischargeCurrentLevel等,這些成員變數的初始化函式為initDischarge。
· 用於儲存歷史記錄的HistroryItem,在clearHistoryLocked函式中初始化,主要有mHistory、mHistoryEnd等成員變數(這些成員在clearHistoryLocked函式中出現)。
上述這些成員變數的具體作用,只有通過後文的分析才能弄清楚。這裡先介紹StopwacherTimer。
//呼叫方式
mPhoneSignalScanningTimer = newStopwatchTimer(null, -200+1,
null,mUnpluggables);
//mUnpluggables型別為ArrayList<Unpluggable>,用於儲存插拔USB線時需要對應更新用電
//資訊的統計物件
// StopwatchTimer的建構函式
StopwatchTimer(Uid uid, int type,ArrayList<StopwatchTimer> timerPool,
ArrayList<Unpluggable>unpluggables) {
//在本例中,uid為0,type為負數,timerPool為空,unpluggables為mUnpluggables
super(type, unpluggables);
mUid =uid;
mTimerPool = timerPool;
}
// Timer的建構函式
Timer(int type, ArrayList<Unpluggable>unpluggables) {
mType =type;
mUnpluggables = unpluggables;
unpluggables.add(this);
}
在StopwatchTimer中比較難理解的就是unpluggables,根據註釋說明,當拔插USB線時,需要更新用電統計的物件,應該將其加入到mUnpluggables陣列中。
在啟動秒錶時,呼叫它的startRunningLocked函式,並傳入BSImpl例項,程式碼如下:
void startRunningLocked(BatteryStatsImpl stats) {
if(mNesting++ == 0) {//巢狀呼叫控制
// getBatteryRealtimeLocked函式返回總的電池使用時間
mUpdateTime = stats.getBatteryRealtimeLocked(
SystemClock.elapsedRealtime()* 1000);
if (mTimerPool != null) {//不討論這種情況
}
mCount++;
mAcquireTime = mTotalTime;//計數控制,請讀者閱讀相關注釋說明
}
}
當停用秒錶時,呼叫它的stopRunningLocked函式,程式碼如下:
void stopRunningLocked(BatteryStatsImpl stats) {
if (mNesting == 0) {
return; //巢狀控制
}
if(--mNesting == 0) {
if(mTimerPool != null) {//不討論這種情況
}else {
final long realtime = SystemClock.elapsedRealtime() * 1000;
//計算此次啟動/停止週期的時間
final long batteryRealtime = stats.getBatteryRealtimeLocked(realtime);
mNesting = 1;
//mTotalTime代表從啟動開始該秒停表一共記錄的時間
mTotalTime = computeRunTimeLocked(batteryRealtime);
mNesting = 0;
}
if (mTotalTime == mAcquireTime) mCount--;
}
}
在StopwatchTimer中定義了很多的時間引數,無非就是用於記錄各種時間,例如總耗時、最近一次工作週期的耗時等。如果不是工作需要(例如研究Settings應用中和BatteryInfo相關的內容),讀者僅需瞭解它的作用即可。
(2) ActivityManagerService和BSS互動
ActivityManagerService建立BSS後,還要進行幾項操作,具體程式碼分別如下:
[-->ActivityManagerService.java::ActivityManagerService建構函式]
mBatteryStatsService = new BatteryStatsService(newFile(
systemDir, "batterystats.bin").toString());
//操作通過BSImpl建立的JournaledFile檔案
mBatteryStatsService.getActiveStatistics().readLocked();
mBatteryStatsService.getActiveStatistics().writeAsyncLocked();
//BSImpl的getIsOnBattery返回mOnBattery變數,初始化值為false
mOnBattery= DEBUG_POWER ? true
: mBatteryStatsService.getActiveStatistics().getIsOnBattery();
//設定回撥,該回撥也是用於資訊統計,只能留到介紹ActivityManagerService時再來分析了
mBatteryStatsService.getActiveStatistics().setCallback(this);
[-->ActivityManagerService.java::main函式]
m.mBatteryStatsService.publish(context);
[-->BatteryStatsService.java::publish]
public void publish(Context context) {
mContext =context;
//注意,BSS服務叫做batteryinfo,而BatteryService服務叫做battery
ServiceManager.addService("batteryinfo", asBinder());
//PowerProfile見下文解釋
mStats.setNumSpeedSteps(new PowerProfile(mContext).getNumSpeedSteps());
//設定通訊訊號掃描超時時間
mStats.setRadioScanningTimeout(mContext.getResources().getInteger(
com.android.internal.R.integer.config_radioScanningTimeout)
* 1000L);
}
在以上程式碼中,比較有意思的是PowerProfile類,它將解析Android 4.0原始碼/frameworks/base/core/res/res/xml/power_profile.xml檔案。此XML檔案儲存的是各種操作(和硬體相關)的耗電情況,如圖5-5所示。
圖5-5 PowerProfile檔案示例
由圖5-5可知,該檔案儲存了各種操作的耗電情況,以mAh(毫安)為單位。PowerProfile的getNumSpeedSteps將返回CPU支援的頻率值,目前在該XML中只定義了一個值,即400MHz。
注意在編譯時,各廠家會將特定硬體平臺的power_profile.xml複製到輸出目錄。此處展示的power_profile.xml和硬體平臺無關。
(3) BatteryService和BSS互動
BatteryService在它的processValues函式中和BSS互動,程式碼如下:
[-->BatteryService.java]
private void processValues() {
......
mBatteryStats.setBatteryState(mBatteryStatus,mBatteryHealth, mPlugType,
mBatteryLevel, mBatteryTemperature,mBatteryVoltage);
}
BSS的工作由BSImpl來完成,所以直接setBatteryState函式的程式碼:
[-->BatteryStatsImpl.java::setBatteryState]
public void setBatteryState(int status, inthealth, int plugType, int level,
int temp, int volt) {
synchronized(this) {
boolean onBattery = plugType == BATTERY_PLUGGED_NONE;//判斷是否為電池供電
intoldStatus = mHistoryCur.batteryStatus;
......
if(onBattery) {
//mDischargeCurrentLevel記錄當前使用電池供電時的電池電量
mDischargeCurrentLevel = level;
mRecordingHistory = true;//mRecordingHistory表示需要記錄一次歷史值
}
//此時,onBattery為當前狀態,mOnBattery為歷史狀態
if(onBattery != mOnBattery) {
mHistoryCur.batteryLevel = (byte)level;
mHistoryCur.batteryStatus = (byte)status;
mHistoryCur.batteryHealth = (byte)health;
......//更新mHistoryCur中的電池資訊
setOnBatteryLocked(onBattery, oldStatus, level);
} else {
boolean changed = false;
if (mHistoryCur.batteryLevel != level) {
mHistoryCur.batteryLevel = (byte)level;
changed = true;
}
......//判斷電池資訊是否發生變化
if (changed) {//如果發生變化,則需要增加一次歷史記錄
addHistoryRecordLocked(SystemClock.elapsedRealtime());
}
}
if (!onBattery && status == BatteryManager.BATTERY_STATUS_FULL){
mRecordingHistory = false;
}
}
}
setBatteryState函式的工作主要有兩項:
· 判斷當前供電狀態是否發生變化,由onBattery和mOnBattery進行比較。其中onBattery用於判斷當前是否為電池供電,mOnBattery為上次呼叫該函式時得到的判斷值。如果供電狀態發生變化(其實就是經歷一次USB拔插過程),則呼叫setOnBatteryLocked函式。
· 如果供電狀態未發生變化,則需要判斷電池資訊是否發生變化,例如電量和電壓等。如果發生變化,則呼叫addHistoryRecordLocked。該函式用於記錄一次歷史資訊。
接下來看setOnBatteryLocked函式的程式碼:
[-->BatteryStatsImpl.java::setOnBatteryLocked]
void setOnBatteryLocked(boolean onBattery, intoldStatus, int level) {
boolean doWrite = false;
//傳送一個訊息給mHandler,將在內部呼叫ActivityManagerService設定的回撥函式
Message m= mHandler.obtainMessage(MSG_REPORT_POWER_CHANGE);
m.arg1 =onBattery ? 1 : 0;
mHandler.sendMessage(m);
mOnBattery = mOnBatteryInternal = onBattery;
longuptime = SystemClock.uptimeMillis() * 1000;
longmSecRealtime = SystemClock.elapsedRealtime();
longrealtime = mSecRealtime * 1000;
if(onBattery) {
//關於電量資訊統計,有一個值得注意的地方:當oldStatus為滿電狀態,或當前電量
//大於90,或mDischargeCurrentLevel小於20並且當前電量大於80時,要清空統計
//資訊,以開始新的統計。也就是說在滿足特定條件的情況下,電量使用統計資訊會清零並重
//新開始。讀者不妨用自己手機一試
if(oldStatus == BatteryManager.BATTERY_STATUS_FULL || level >= 90
|| (mDischargeCurrentLevel < 20 && level >= 80)) {
doWrite = true;
resetAllStatsLocked();
mDischargeStartLevel = level;
}
//讀取/proc/wakelock檔案,該檔案反映了系統wakelock的使用狀態,
//感興趣的讀者可自行研究
updateKernelWakelocksLocked();
mHistoryCur.batteryLevel = (byte)level;
mHistoryCur.states &= ~HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
//新增一條歷史記錄
addHistoryRecordLocked(mSecRealtime);
//mTrackBatteryUptimeStart表示使用電池的開始時間,由uptime表示
mTrackBatteryUptimeStart = uptime;
// mTrackBatteryRealtimeStart表示使用電池的開始時間,由realtime表示
mTrackBatteryRealtimeStart = realtime;
//mUnpluggedBatteryUptime記錄總的電池使用時間(不論中間插拔多少次)
mUnpluggedBatteryUptime = getBatteryUptimeLocked(uptime);
// mUnpluggedBatteryRealtime記錄總的電池使用時間
mUnpluggedBatteryRealtime = getBatteryRealtimeLocked(realtime);
//記錄電量
mDischargeCurrentLevel =mDischargeUnplugLevel = level;
if(mScreenOn) {
mDischargeScreenOnUnplugLevel = level;
mDischargeScreenOffUnplugLevel = 0;
}else {
mDischargeScreenOnUnplugLevel = 0;
mDischargeScreenOffUnplugLevel = level;
}
mDischargeAmountScreenOn = 0;
mDischargeAmountScreenOff = 0;
//呼叫doUnplugLocked函式
doUnplugLocked(mUnpluggedBatteryUptime, mUnpluggedBatteryRealtime);
}else {
......//處理使用USB充電的情況,請讀者在上面討論的基礎上自行分析
}
......//記錄資訊到檔案
}
}
doUnplugLocked函式將更新對應資訊,該函式比較簡單,無須贅述。另外,addHistoryRecordLocked函式用於增加一條歷史記錄(由HistoryItem表示),讀者也可自行研究。
從本節的分析可知,Android將電量統計分得非常細,例如由電池供電的情況需要統計,由USB/AC充電的情況也要統計,因此有setBatteryState函式的存在。
(4) PowerManagerService和BSS互動
PMS和BSS互動是最多的,此處以noteScreenOn和noteUserActivity為例,來介紹BSS到底是如何統計電量的。
先來看noteScreenOn函式。當開啟螢幕時,PMS會呼叫BSS的noteScreenOn以通知螢幕開啟,該函式在內部呼叫BSImpl的noteScreenOnLocked,其程式碼如下:
[-->BatteryStatsImpl.java::noteScreenOnLocked]
public void noteScreenOnLocked() {
if(!mScreenOn) {
mHistoryCur.states |= HistoryItem.STATE_SCREEN_ON_FLAG;
//增加一條歷史記錄
addHistoryRecordLocked(SystemClock.elapsedRealtime());
mScreenOn = true;
//啟動mScreenOnTime秒停表,內部就是記錄時間,讀者可自行研究
mScreenOnTimer.startRunningLocked(this);
if(mScreenBrightnessBin >= 0)//啟動對應螢幕亮度的秒停表(參考表5-5)
mScreenBrightnessTimer[mScreenBrightnessBin].startRunningLocked(this);
//螢幕開啟也和核心WakeLock有關,所以這裡一樣要更新WakeLock的用電統計
noteStartWakeLocked(-1, -1, "dummy", WAKE_TYPE_PARTIAL);
if(mOnBatteryInternal)
updateDischargeScreenLevelsLocked(false, true);
}
}
再來看noteUserActivity,當有輸入事件觸發PMS的userActivity時,該函式被呼叫,程式碼如下,:
[-->BatteryStatsImpl.java::noteUserActivityLocked]
//BSS的noteUserActivity將呼叫BSImpl的noteUserActivityLocked
public void noteUserActivityLocked(int uid, intevent) {
getUidStatsLocked(uid).noteUserActivityLocked(event);
}
先是呼叫getUidStatsLocked以獲取一個Uid物件,如果該Uid是首次出現的,則要在內部建立一個Uid物件。直接來了解Uid的noteUserActivityLocked函式:
public void noteUserActivityLocked(int type) {
if(mUserActivityCounters == null) {
initUserActivityLocked();
}
if (type< 0) type = 0;
else if(type >= NUM_USER_ACTIVITY_TYPES)
type= NUM_USER_ACTIVITY_TYPES-1;
// noteUserActivityLocked只是呼叫對應type的Counter的stepAtomic函式
//每個Counter內部都有個計數器,stepAtomic使該計數器增1
mUserActivityCounters[type].stepAtomic();
}
mUserActivityCounters為一個7元Counter陣列,該陣列對應7種不同的輸入事件型別,在程式碼中,由BSImpl的成員變數USER_ACTIVITY_TYPES表示,如下所示:
static final String[] USER_ACTIVITY_TYPES = {
"other", "cheek", "touch","long_touch", "touch_up", "button", "unknown"
};
另外,在LocalPowerManager中,也定義了相關的type值,如下所示:
[-->LocalPowerManager.java]
public interface LocalPowerManager {
publicstatic final int OTHER_EVENT = 0;
publicstatic final int BUTTON_EVENT = 1;
publicstatic final int TOUCH_EVENT = 2; //目前只使用這三種事件
......
}
5.5.3 BatteryService及BatteryStatsService總結
本節重點討論了BatteryService和BatteryStatsService。其中,BatteryService和系統中的供電系統互動,通過它可獲取電池狀態等資訊。而BatteryStatsService用於統計系統用電量的情況。就難度而言,BSS較為複雜,原因是Android試圖對系統耗電量作非常細緻的統計,導致統計項非常繁雜。另外,電量統計大多采用被動通知的方式(即需要其他服務主動呼叫BSS提供的noteXXXOn/noteXXXOff函式),這種實現方法一方面加重了其他服務的負擔,另一方面影響了這些服務未來的功能擴充套件。
注意雖然Google費盡心血來完善電量統計,但這並不是解決耗電量大的根本途徑。另外,讀者可分析Settings程式中電量統計圖的繪製以加深對各種統計物件的理解。Settings中和電量相關的檔案在Android 4.0原始碼的/packages/apps/Settings/src/com/android/settings/fuelgauge/目錄中。
5.6 本章學習指導
本章的難度其實在BSS中,而PMS和BatteryService相對較簡單。在這三項服務中, PMS是核心。讀者在研究PMS時,要注意把握以下幾個方面:
· PMS的初期工作流程,即建構函式、init函式、systemReady函式和BootCompleted函式等。
· PMS功能在於根據當前系統狀態(包括mUserState和mWakeLockState)去操作螢幕和燈光。而觸發狀態改變的有WakeLock的獲取和釋放,userActivity函式的呼叫,因此讀者也要搞清楚PMS在這兩個方面的工作原理。
· PMS還有一部分功能和感測器有關,其功能無非還是根據狀態操作螢幕和燈光。除非工作需要,否則只需要簡單瞭解這部分的工作流程即可。
對BSS來說,複雜之處在於它定義了很多成員變數和資料型別,並且沒有一份電量統計標準的說明文件,因此筆者認為,讀者只要搞清楚那幾個計量工具和各個統計項的作用即可,如果在其他服務的程式碼中看到和BSS互動的函式,那麼只需知道原因和目的即可。
另外,電源管理需要HAL層和Linux核心提供支援,感興趣的讀者不妨以本章知識為切入點,對底層技術進行一番深入剖析。
5.7 本章小結
電源管理系統的核心是PowerManagerService,還包括BatteryService和BatteryStatsService。本章對Android平臺中的電源管理系統進行了較詳細的分析,其中:
· 對於PMS,本章分析了它的初始化流程、WakeLock獲取流程、userActivity函式的工作流程及Power按鍵處理流程。
· BatteryService功能較為簡單,讀者大概瞭解即可。
· 對於BatteryStatsService,本章對它內部的資料結構、統計物件等進行了較詳細的介紹,並對其工作流程展開了分析。建議讀者結合Settings應用中的相關程式碼,加深對其中各種計量工具及統計物件的理解。
相關文章
- [深入理解Android卷二 全文-第六章]深入理解ActivityManagerServiceAndroid
- [深入理解Android卷二 全文-第四章]深入理解PackageManagerServiceAndroidPackage
- [深入理解Android卷二 全文-第三章]深入理解SystemServerAndroidServer
- [深入理解Android卷二 全文-第二章]深入理解Java Binder和MessageQueueAndroidJava
- [深入理解Android卷一全文-第十章]深入理解MediaScannerAndroid
- [深入理解Android卷一全文-第八章]深入理解Surface系統Android
- [深入理解Android卷一全文-第七章]深入理解Audio系統Android
- [深入理解Android卷一全文-第九章]深入理解Vold和RildAndroid
- [深入理解Android卷二 全文-第一章]開發環境部署Android開發環境
- 深入理解AndroidAndroid
- 深入理解 GCD(二)GC
- 《深入理解Android:卷IIIA》一一2.4本章小結Android
- 非同步(二):Generator深入理解非同步
- Android 深入理解 Notification 機制Android
- 深入理解 Android 中的 MatrixAndroid
- 深入理解Java中的鎖(二)Java
- 深入理解hashmap(二)理論篇HashMap
- 深入理解Plasma(二)Plasma 細節ASM
- 機器學習:深入理解LSTM網路 (二)機器學習
- 圖解 Android 系列(二)深入理解 init 與 zygote 程式圖解AndroidGo
- 深入理解Isolate
- 深入理解HashMapHashMap
- 深入理解TransformORM
- 深入理解KVO
- 深入理解 JVMJVM
- 深入理解 GitGit
- 深入理解AQSAQS
- 深入理解JVMJVM
- 深入理解 TypeScriptTypeScript
- 深入理解JavaScriptCoreJavaScript
- 深入理解MVCMVC
- 深入理解 PWA
- 深入理解margin
- 深入理解ReactReact
- 深入理解BFC
- 深入理解reduxRedux
- BFC深入理解
- 深入理解 GCDGC