1. 程式人生 > >[深入理解Android卷二 全文-第五章]深入理解PowerManagerService

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