android6.0系统 PowerManager深入分析(非常详细)
2019-07-14 00:41 发布
生成海报
概述
一直以来,电源管理是电子产品设计中非常重要的环节 ,也是 任何电子设备中最为重要的系统模块之一, 优秀的电源管理方案,能够提供持久的续航能力,良好的用户体验,更能提升电子产品的竞争力。
移动设备的电量主要由两种元件消耗:CPU和显示屏,因此设法降低这两种元件的耗电量就是电源管理的关键,为移动设备设计的CPU大多有两种工作频率,为了省电,大部分时间内cpu都工作在降低频率下,只有进行密集计算时,如视频解码才会切换到高频状态,而显示屏省电的方法是尽量减少亮屏时间,但是显示屏的开关和应用有很大的关系,因此系统中需要有一套机制来控制显示屏的开关和亮度,这也是电源管理的主要工作。
电源管理架构
Android 的电源管理主要是通过wakelock机制 来 管理 系统的状态,整个android电源管理,可以分为四个层次:应用接口层(PowerManager.Java ),Framework层(PowerManagerService.java),HAL层(Power.c),和内核层(kernel/Power)。
应用接口层:PowerManager中开放给应用一系列接口,应用可以调用PM的接口申请wakelock,唤醒系统,使系统进入睡眠等操作;
Framework层:应用调用PowerManager开放的接口,来对系统进行一些列的操作是在PowerManagerService中完成的,PowerManagerService计算系统中和Power相关的计算,是整个电源管理的决策系统。同时协调Power如何与系统其它模块的交互,比如亮屏,暗屏,系统睡眠,唤醒等等。
HAL层:该层只有一个power.c文件,该文件通过上层传下来的参数,向/sys/power/wake_lock或者/sys/power/wake_unlock文件节点写数据来与kernel进行通信,主要功能是申请/释放锁,维持屏幕亮灭
Kernel层:内核层实现电源管理的方案主要包含三个部分:
1、Kernel/power/:实现了系统电源管理框架机制。
2、Arch/arm(ormips or powerpc)/mach-XXX/pm.c:实现对特定板的处理器电源管理。
3、drivers/power:是设备电源管理的基础框架,为驱动提供了电源管理接口。
Android电源管理框架如下图 :
电源管理服务—— PowerManagerService
PowerManagerServcie是android系统电源管理的核心服务,它在Framework层建立起一个策略控制方案,向下决策HAL层以及kernel层来控制设备待机状态,控制显示屏,背光灯,距离传感器,光线传感器等硬件设备的状态。向上提供给应用程序相应的操作接口,比如听音乐时持续保持系统 唤醒 ,应用通知来临唤醒手机屏幕等场景
启动过程
SystemServer在系统启动的时候会启动三类服务:引导关键服务,核心服务,其他服务;PowerManagerService是在SystemServer中创建的,并将其作为一个系统服务加入到ServiceManager中:
mPowerManagerService = mSystemServiceManager.startService(PowerManagerService.class);
在启动引导关键服务调用 startBootstrapServices (),其中各种服务都是通过SystemServiceManager中的startService()函数来启动:
public extends SystemService> T startService(Class serviceClass) {
final String name = serviceClass.getName();
final T service;
Constructor constructor = serviceClass.getConstructor(Context. class );
service = constructor.newInstance( mContext );
mServices .add(service);//注册服务到服务列表中去
service.onStart();//启动服务
}
在启动PowerManagerService时,传入的参数类是PowerManagerService,在startService()中首先调用PowerManagerService的构造函数,然后调用其onStart()函数
PowerManagerServcie的构造函数:
mHandler = new PowerManagerHandler(mHandlerThread.getLooper());
synchronized (mLock) {
mWakeLockSuspendBlocker = createSuspendBlockerLocked( "PowerManagerService.WakeLocks" );
mDisplaySuspendBlocker = createSuspendBlockerLocked( "PowerManagerService.Display" );
mDisplaySuspendBlocker.acquire();
mWakefulness = WAKEFULNESS_AWAKE;
nativeInit();
创建一个处理消息和发送消息的线程mHandler,并且两种标志flag的wakelock锁 :PowerManagerService.WakeLocks 和PowerManagerService.Display, 前者是传入到底层是控制cpu唤醒状态,后者则是控制屏幕亮灭。在构造函数最后调用nativeInit();在native层初始化相关资源。将mWakefulness 置成WAKEFULNESS_AWAKE状态,mWakefulness 标识系统当前状态共有四种定义:
WAKEFULNESS_ASLEEP:表示系统当前处于休眠状态,只能被wakeUp()调用唤醒。
WAKEFULNESS_AWAKE:表示系统目前处于正常运行状态。
WAKEFULNESS_DREAMING:表示系统当前正处于屏保的状态。
WAKEFULNESS_DOZING:表示系统正处于“doze”状态。这种状态下只有低耗电的“屏保”可以运行,其他应用进程都被挂起。
在SystemServer中startService中调用到PowerManagerService构造函数做完初始化操作之后便会调用PowerManagerService的onStart()函数:
publishBinderService(Context.POWER_SERVICE, new BinderService());
publishLocalService(PowerManagerInternal. class , new LocalService());
Watchdog.getInstance().addMonitor( this );
Watchdog.getInstance().addThread(mHandler);
O nstart完成的工作就是将POWER_SERVICE作为Binder的服务端,注册到SystemService中去;将PowerManagerInternal注册到本地服务中,将自己加到watchdog的监控队列中去;将之前在构造函数中创建的mHandler对象加入到watchdog的中,用于监视mHandler的looper是否空闲;
系统准备工作
SystemServer在调用PowerManagerService之后还会调用其 SystemReady , 相当于在系统准备就绪后对PowerManagerService再进行一些初始化工作。SystemReady()方法代码如下:
mAppOps = appOps;
mDreamManager = getLocalService(DreamManagerInternal. class );
mDisplayManagerInternal = getLocalService(DisplayManagerInternal. class );
mPolicy = getLocalService(WindowManagerPolicy. class );
mBatteryManagerInternal = getLocalService(BatteryManagerInternal. class );
PowerManager pm = (PowerManager) mContext .getSystemService(Context. POWER_SERVICE );
mScreenBrightnessSettingMinimum = pm.getMinimumScreenBrightnessSetting();
mScreenBrightnessSettingMaximum = pm.getMaximumScreenBrightnessSetting();
mScreenBrightnessSettingDefault = pm.getDefaultScreenBrightnessSetting();
获取与PowerManager相关的本地服务,比如屏保( mDreamManager ),屏幕显示( mDisplayManagerInternal ),窗口策略( mPolicy ),电池电量( mBatteryManagerInternal )等服务,然后初始化屏幕最大亮度,最小亮度,和默认亮度;
SensorManager sensorManager = new SystemSensorManager( mContext , mHandler .getLooper());
mBatteryStats = BatteryStatsService. getService ();
mNotifier = new Notifier(Looper. getMainLooper (), mContext , mBatteryStats ,
mAppOps , createSuspendBlockerLocked( "PowerManagerService.Broadcasts" ),
mPolicy );
mWirelessChargerDetector = new WirelessChargerDetector(sensorManager,
createSuspendBlockerLocked( "PowerManagerService.WirelessChargerDetector" ), mHandler );
mSettingsObserver = new SettingsObserver( mHandler );
mLightsManager = getLocalService(LightsManager. class );
mAttentionLight = mLightsManager .getLight(LightsManager. LIGHT_ID_ATTENTION );
创建sensorManager 对象,用于与sensor交互,比如距离传感器,光线传感器,加速度传感器(doze上使用)。获取电池状态服务,和背光服务;
创建 mNotifier 对象,在通过 mNotifier 发送通知时候,会传入底层申请 PowerManagerService.Broadcasts 的wakelock锁。
创建 mSettingsObserver 监听系统设置变化,比如亮屏时间,自动背光,屏幕亮度,屏保,低电模式等等
总而言之在SystemReady方法中完成的主要工作如下:
获取与PowerManagerServcie相关的系统服务以及本地服务;
获取屏幕最大,最小以及默认亮度值;
创建SensorManager 对象,用于和SensorService交互;
创建Notifier对象,用于通知系统中电源状态的改变;
创建WirelessChargerDetector对象,用于检测无线充电的传感器(市面上支持的手机较少)
调用DisplayManagerService的initPowerManagement()方法来初始化Power显示模块。
注册SettingsObserver监听系统设置的变化
PowerManagerServcie的启动初始化过程如下:
相关接口
PowerManager向应用提供了相应的接口,以供应用程序调用,来改变系统待机状态,屏幕状态,屏幕亮度等,PowerManager是PowerManagerService的代理类,PowerManager向上层应用提供交互的接口,具体的处理工作在PowerManagerService中完成。下面介绍PowerManager中提供的相应接口作用:
Wakeup():强制系统从睡眠状态唤醒,此接口对应用是不开放的,应用想唤醒系统必须通过设置亮屏标志(后面即将讲到);
gotoSleep():强制系统进入到睡眠状态,此接口也是应用不开放。
userActivity():向PowerManagerService报告影响系统休眠的用户活动,重计算灭屏时间,背光亮度等,例如触屏,划屏,power键等用户活动;
Wakelock:wakelock是PowerManager的一个内部类,提供了相关的接口来操作wakelock锁,比如 newWakeLock ()方法来创建 wakelock 锁,acquire()和release()方法来申请和释放锁。
isDeviceIdleMode():返回设备当前的状态,如果处于Idle状态,则返回true,Idle状态是在手机长时间没有被使用以及没有运动的情况下,手机进入到一种Doze低功耗的模式下,这种状态下手机可能会关掉网络数据访问,可以通过监视DEVICE_IDLE_MODE_CHANGED这个广播信息,来监控手机状态的改变
唤醒——wakeup
PowerManager的wakeup接口属性是@hide的,所以对于上层应用是不可见的,上层应用要唤醒系统大都依靠两种方式:1.在应用启动Activity时候设置相应的window的flags,通过WMS来唤醒系统;2.在应用申请wakelock锁时附带ACQUIRE_CAUSES_WAKEUP标志;
Wakeup流程如下图所示
PowerManager的wakeup接口,可供应用程序调用,来强制唤醒系统,如果该设备处于睡眠状态,调用该接口会立即唤醒系统,比如按Power键,来电,闹钟等场景都会调用该接口。唤醒系统需要android.Manifest.permission#DEVICE_POWER的权限;
我们来看看PowerManagerServcie中wakeup接口的代码:
mContext .enforceCallingOrSelfPermission(android.Manifest.permission. DEVICE_POWER , null );
final int uid = Binder. getCallingUid ();
final long ident = Binder. clearCallingIdentity ();
wakeUpInternal(eventTime, reason, uid, opPackageName, uid);
Wakeup接口中仅仅是对调用者的权限进行检查;然后放到wakeUpInternal()中处理,wakeUpInternal()中没有做操作,只是调用wakeUpNoUpdateLocked()函数,然后更新调用updatePowerStateLocked()更新电源状态
wakeUpNoUpdateLocked()关键代码
mLastWakeTime = eventTime;
setWakefulnessLocked( WAKEFULNESS_AWAKE , 0 );
mNotifier .onWakeUp(reason, reasonUid, opPackageName, opUid);
userActivityNoUpdateLocked(
eventTime, PowerManager.USER_ACTIVITY_EVENT_OTHER, 0 , reasonUid);
return true ;
setWakefulnessLocked()函数将 mWakefulness 赋值为wakefulness和 mDirty |= DIRTY_WAKEFULNESS ; 这两个标志在后面更新电源状态时有重要作用,同时调用到mNotifier中的onWakefulnessChangeStarted调用到handleEarlyInteractiveChange调用到 PhoneWindowManager 的startedWakingUp函数,来通知到 PhoneWindowManager 屏幕开始启动;
调用mNotifier向系统中通知系统被唤醒;
更新用户活动,将 mDirty |= DIRTY_USER_ACTIVITY 置位;来重新计算亮屏时间。
在updatePowerStateLocked()中更新电源状态,updatePowerStateLocked为PowerManagerService的核心函数,后面会详细介绍,这里简单介绍在wakeup中的流程。在updatePowerStateLocked()中的updateDisplayPowerStateLocked()函数中将mDisplayPowerRequest.policy设置成 POLICY_BRIGHT; 然后调用:
mDisplayReady = mDisplayManagerInternal .requestPowerState( mDisplayPowerRequest , mRequestWaitForNegativeProximity );
通过调用到 DisplayManagerService 中对屏幕状态作出相应的改变,通过去与 WindowManagerService 执行亮屏之前的屏幕绘制过程,与LightServcie交互来点亮屏幕背光灯。其调用过程和具体逻辑在亮屏流程文档中有详细分析。
睡眠——goToSleep
PowerManager的gotoSleep()接口是@hide属性,因此对于上层应用是不开放的,设备强制进入睡眠状态,在处理一些灭屏按键事件时,会通过WMS来调用PowerManager的gotoSleep接口,一般在系统一段时间没有被操作时,系统将会自动调用gotoSleep函数,让其进入到睡眠模式;
与wakeup唤醒一样,PowerManager的gotoSleep()在PowerManagerService中处理,PMS中的gotoSleep()首先检查调用者是否拥有android.Manifest.permission.DEVICE_POWER权限。然后调用到goToSleepInternal()中处理:
if (goToSleepNoUpdateLocked(eventTime, reason, flags, uid)) {
updatePowerStateLocked();
在goToSleepNoUpdateLocked()中完成 发送了将要休眠的通知,然后修改了Wakefulness,将其 置成WAKEFULNESS_DOZING,将 mDirty |= DIRTY_WAKEFULNESS 置位,更多的实际工作在updatePowerStateLocked()中完成。在updateDreamLocked中完成真正进入睡眠的过程;
其调用过程大致如下:
在reallyGoToSleepNoUpdateLocked中将mWakefulness置成WAKEFULNESS_ASLEEP,在updateWakeLockSummaryLocked中有如下:
if ( mWakefulness == WAKEFULNESS_ASLEEP
|| ( mWakeLockSummary & WAKE_LOCK_DOZE ) != 0 ) {
mWakeLockSummary &= ~( WAKE_LOCK_SCREEN_BRIGHT | WAKE_LOCK_SCREEN_DIM
| WAKE_LOCK_BUTTON_BRIGHT );
将 mWakeLockSummary 列表中的 wakelock 锁所形成的集合变量 mWakeLockSummary 中将 WAKE_LOCK_SCREEN_BRIGHT,WAKE_LOCK_SCREEN_DIM,WAKE_LOCK_BUTTON_BRIGHT三种wakelock锁置为无效,再次调用updatePowerStateLocked更新电源状态时候,会在updateDisplayPowerStateLocked中做灭屏操作,其流程与wakeup唤醒系统亮屏操作流程大致一样。
用户活动——userActivity
userActivity()接口用于用户进程向PowerManagerService报告用户影响系统休眠的活动。例如, 用户点击屏幕时,系统会调用该方法来告诉PowerManagerService用户点击的时间,这样PowerManagerService将更新内部保存的时间值,从而推迟系统休眠的时间。userActivity()方法主要通过调用内部的userActivityInternal()方法来完成工作 。
在 userActivityInternal ()中并没有做任何操作,仅仅是将mLastUserActivityTime 更新为当前event的时间eventTime,mDirty |= DIRTY_USER_ACTIVITY;置位操作。具体操作仍然是在PowerManagerService中的核心函数 updatePowerStateLocked()中完成;
在updateUserActivitySummaryLocked()中
final int sleepTimeout = getSleepTimeoutLocked();
final int screenOffTimeout = getScreenOffTimeoutLocked(sleepTimeout);
final int screenDimDuration = getScreenDimDurationLocked(screenOffTimeout);
mUserActivitySummary = 0 ;
if ( mLastUserActivityTime >= mLastWakeTime ) {
nextTimeout = mLastUserActivityTime
+ screenOffTimeout - screenDimDuration;
if (now < nextTimeout) {
mUserActivitySummary = USER_ACTIVITY_SCREEN_BRIGHT ;
} else {
nextTimeout = mLastUserActivityTime + screenOffTimeout;
if (now < nextTimeout) {
mUserActivitySummary = USER_ACTIVITY_SCREEN_DIM ;
}
}
重新计算睡眠超时时间,灭屏超时时间,暗屏超时时间,将 mUserActivitySummary 置为0,通过计算上一次的用户事件时间与超时时间作对比,来判断将屏幕置为亮屏 ( USER_ACTIVITY_SCREEN_BRIGHT )还是暗屏( USER_ACTIVITY_SCREEN_DIM ),前提是手机处于非睡眠状态。
if ( mUserActivitySummary != 0 && nextTimeout >= 0 ) {
Message msg = mHandler .obtainMessage( MSG_USER_ACTIVITY_TIMEOUT );
msg.setAsynchronous( true );
mHandler .sendMessageAtTime(msg, nextTimeout);
}
如果时间还没到,则返回发送一个 MSG_USER_ACTIVITY_TIMEOUT 的定时消息,当处理时间到了,会在消息的处理方法 handleUserActivityTimeout中重新调用 updatePowerStateLocked()电源状态。再次调用
打开微信“扫一扫”,打开网页后点击屏幕右上角分享按钮