[深入理解Android卷二 全文-第五章]深入理解PowerManagerService

阿拉神農發表於2015-08-03

 

由於《深入理解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應用中的相關程式碼,加深對其中各種計量工具及統計物件的理解。

 



[①] config.xml檔案的全路徑是4.0原始碼/frameworks/base/core/res/res/values/config.xml。

[②]必須在一定時間內完成按下和鬆開Power鍵的操作,否則系統會認為是關機操作。詳情將在卷Ⅲ輸入系統一章的分析。

[③]讀者可閱讀SDK文件中關於SystemClock類的說明。

相關文章