[深入理解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
//④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) {