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

 

由于《深入理解Android 卷一》和《深入理解Android卷二》不再出版,而知识的传播不应该因为纸质媒介的问题而中断,所以我将在OSC博客中全文转发这两本书的全部内容

第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所示

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

图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的家族图谱。

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

图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所示。

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

图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所示。

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

图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所示。

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

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

版权声明:本文为博主原创文章,未经博主允许不得转载。

你可能感兴趣的:([深入理解Android卷二 全文-第五章]深入理解PowerManagerService)