4

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

 3 years ago
source link: https://blog.csdn.net/Innost/article/details/47254353
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

由于《深入理解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值

Screen

Keyboard

PARTIAL_WAKE_LOCK

不受电源键影响

SCREEN_DIM_WAKE_LOCK

按下电源键后,系统还是会进入休眠状态

SCREEN_BRIGHT_WAKE_LOCK

Bright

FULL_WAKE_LOCK

Bright

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);//引用计数控制

      //调用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();

           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;

     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)

       //抛送一个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));

       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类的说明。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK