PowerManagerService浅析

一、前言
       PowerManagerService服务是Android系统的上层的电源管理服务,主要负责系统待机、屏幕背光、按键背光、键盘背光以及用户事件的处理。通过锁的申请与释放以及默认的待机时间来控制系统的待机状态;通过系统默认灭屏时间以及用户操作的事件状态控制背光亮、暗。该服务还包括了光线、距离传感器上层查询与控制,LCD亮度的调节最终也是由该服务完成。
二、开机启动及处理
       PowerManagerService服务代码分布在frameworks/base/services/java/com/android/server/PowerManagerService.java中,应用层的相关调用是通过/frameworks/base/core/java/android/os/PowerManager.java实施的,最终接口的实现都在PowerManagerService.java中。
       当系统开启时,在SystemServer.java的run接口中通过将power服务加入到系统服务中:
       power = new PowerManagerService();
       ServiceManager.addService(Context.POWER_SERVICE, power);
当光感服务与电池管理服务都开始后进行power服务的初始化:
       power.init(context, lights, ActivityManagerService.self(), battery);
init()接口实现一些基本的初始化工作,包括将lights和battery两个服务实例传入到power服务中,这两个服务将与power进行交互。另外,开启了两线程,其中有一个比较重要的线程后期用于处理亮度动画的:
       mScreenBrightnessAnimator.start( );
mScreenBrightnessAnimator为PowerManagerService子类ScreenBrightnessAnimator的实例。
    mHandlerThread = new HandlerThread(" PowerManagerService ")
    mHandlerThread.start( );另外一个PowerManagerService线程完成接下来的部分初始化工作。start后,会调用到run接口,并在其中回调到子类中的protected void onLooperPrepared()中,该接口又调用到initInThread( ),主要实现一些值的初始化,并标识mInitComplete = true;这时,mHandlerThread.notifyAll();通知mHandlerThread的Looper实例创建,该实例在systemReady( )接口中被SystemSensorManager(mHandlerThread.getLooper());使用。还有一个地方需要注意一个,在initThread中还实现了一个设置的监听:mSettings.addObserver(settingsObserver);,如果用户在系统中变更了关于背光时间或是否启用光感等,PowerManagerService能获取到最新的状态值,通过update()进行更新。
       最后在init( )中调用forceUserActivityLocked( )接口,并标志初始化工作完成。
        forceUserActivityLocked();  //实现动画播放完成后屏幕以及按键是亮的,最终是通过调用userActivity()实现,userActivity()这的接口将在后面进行分析,如果没有该调用系统将一直停留在动画插入界面。
        mInitialized = true;
    此外,在系统准备就绪后,systemservice.java中会调用power.systemReady( );该接口主要进行一些配置值的读取,如Lcd背光、键盘背光等数组配值。
       当系统完全启动,即AMS中的finishBooting()发出bootCompleted广播后:
              broadcastIntentLocked(null, null, new Intent(Intent.ACTION_BOOT_COMPLETED, null),null, null, 0, null, null, android.Manifest.permission.RECEIVE_BOOT_COMPLETED, false, false, MY_PID, Process.SYSTEM_UID, Binder.getOrigCallingUser());
此时,PMS中会收到该广播执行bootCompleted(),该接口中主要时行以下操作:
       //后续操作依赖与该值;
       mBootCompleted = true;
       //模拟触发一次按键事件
       userActivity(SystemClock.uptimeMillis(), false, BUTTON_EVENT, true);
    if ( (mPowerState & SCREEN_ON_BIT) != 0 ) {
           sendNotificationLocked(true, -1);
    }
    updateWakeLockLocked();  //更新锁状态
     mLocks.notifyAll();    // 通知唤醒当前等待线程
二、关键方法
2.1.userActivity( );
       该方法在前面已经提到,很多地方都有调用到这个方法。直接的看,如要桌面你桌面点击或者滑动一次就会调用一次该接口。该接口会更新一些状态,其中有一个比较重要的,mUserState,该值决定了系统对lcd、按键以及键盘等亮和灭。如:mUserState = 0X7表示lcd和按键背光亮。另外这个接口的实现调用 到两个关键的接口:
       A.setPowerState(mUserState | mWakeLockState, noChangeLights, WindowManagerPolicy.OFF_BECAUSE_OF_USER);
       B. setTimeoutLocked(time, timeoutOverride, SCREEN_BRIGHT);
2.1.1setPowerState ()
       该接口主要通过检查当前的系统状态,完成当前power状态设置。
       通过updateLightsLocked()进行更新。关于updateLightsLocked()接口,该方法中主要进行两方面的操作,对lcd亮度以及按键背光的控制。
       mScreenBrightnessAnimator.animateTo(brightness, SCREEN_BRIGHT_BIT, dt);该方法用于以动画形式控制lcd背光亮光亮度,如渐亮、渐暗。注意其中的dt值,该值可以控制背光渐变的频率。dt = steps * NOMINAL_FRAME_TIME_MS;通过查看代码可以知道dt的最终决定值由三个变量决定:ANIM_STEPS、NOMINAL_FRAME_TIME_MS、IMMEDIATE_ANIM_STEPS,其中IMMEDIATE_ANIM_STEPS这个在WaitingForFirstLightSensor时才用到,绝大部分可以通过调节ANIM_STEPS、NOMINAL_FRAME_TIME_MS中的一个值进行控制渐变亮度值的跨度大小。值越大,渐变就平滑,需要的时间也越长。对于animateTo()接口将在后面进行分析。
       另外一个在updateLightsLocked()中可能会调用到的接口为mButtonLightState.setTargetLocked(brightness);用于设置按键背光亮度,这个值在正常的操作时为100,定义在PowerManager.java中public final static int BRIGHTNESS_DEFAULT = 100;按键背光默认持续时间为public final static long TIMEOUT_DEFAULT = 6000; //ms。setTargetLocked()通过mScreenBrightnessHandler.postAtTime(button_light_delay,now+50);调用背光设置线程完成设置,如下:
private Runnable button_light_delay = new Runnable() {
       public void run() {
              Slog.d(TAG,"button_light_delay");
              setLightBrightness(Mask, button_light_value);
       }
};
setLightBrightness(Mask, button_light_value);最终通过mScreenBrightnessAnimator.animateTo(value, mask, 0);实现按键背光设置。
2.1.2 setTimeoutLocked()
       该接口功能比较简单,通过传入的nextState值计算下一个状态发生的时间,下一个状态指进入dim或者直接灭屏等 。对下一个状态的控制通过Handle一个定时器任务:mHandler.postAtTime(mTimeoutTask, when);时间到达后完成下一个状态的控制。在TimeoutTask中通过setPowerState()完成本次状态设定并再次调用setTimeoutLocked()设置一个新的定时任务。
2.2 animateTo()
       该接口定义在PowerManagerService.java的内部类ScreenBrightnessAnimator中。public void animateTo(int target, int sensorTarget, int mask, int animationDuration)这里的animationDuration即上文中提到的dt。亮度渐变动画主要的业务逻通过animateInternal(mask, turningOff, 0);实现。以lcd设置为例,其整个调用如下:
       1.animateTo()-->2.animateInternal()-->3.mScreenBrightnessHandler.obtainMessage(ANIMATE_POWER_OFF, mode, 0).sendToTarget();-->4.mScreenBrightnessHandler.sendMessageDelayed(msg, delay)--> 5. mScreenBrightnessHandler-->6.mLcdLight.setBrightness(value, brightnessMode);-->7. animateInternal(mask, false, delay);
       其中3和4不会同时执行,5会根据输入的值为ANIMATE_POWER_OFF还是ANIMATE_LIGHTS进行区分处理。在前面的分析中可知mScreenBrightnessHandler在PWS初始化时已经注册。该handle中有两个关键的操作,一个是通过setBrightness设置当前lcd亮度值,另一个操作是再次调用7(与2中同接口)形成环,直到animateInternal ()中的currentValue != endValue时,这个环被解除,本次背光动画完成。
       这里需要注意第二次开始调用animateInternal ()传入的delay,这个值决定了亮度渐变过程的频率,该值的大小也会影响到整个动画的快慢,与上文中提到的dt值共同左右动画的播放时间,只是两个值影响到的方面不一样,一个是刷新频率,而另一个是跨度。该值在5中定义:int delay = elapsed < NOMINAL_FRAME_TIME_MS ? NOMINAL_FRAME_TIME_MS : 1,最终与NOMINAL_FRAME_TIME_MS的值有关。
       结合上面的分析,当用户在亮屏状态下滑动一次触屏屏Lcd整个调用状态是这样的:
userActivity()--->setPowerState(0x3)
                      ----> 1.setTimeoutLocked(SCREEN_BRIGHT) --->时间到达(9.5s)后状态为0x3--->2.TimeoutTask--->3.setPowerState(0x3)--->4. setTimeoutLocked(SCREEN_DIM)--->时间到达(0.5s)后---> 5.TimeoutTask---> 6.setPowerState(0x1)---> 7.setTimeoutLocked(SCREEN_OFF)--->5s后---> 8.setPowerState(0x0)
       这个流程中不知道为需要一次0.5s的设置,其实就是一直在对SCREEN_BRIGHT状态进行反复设置,按我的理解可以将234直接合并,将1中的定时器时间设置为10s,然后直接进入dim状态。
虚拟按键流程:
       1.userActivity()--->2.mButtonLightState.noteUserActivity(eventType)---> 3.setTargetLocked(brightness)--->4. animateTo()--->5.mButtonLight.setBrightness(target)--->6.mScreenBrightnessHandler.postDelayed(this, Timeout > 0 ? Timeout : PowerManager.ButtonLight.TIMEOUT_DEFAULT);
步骤6设置了一个延时消息,在6s后发送。this表示再次执行本线程一次,即ButtonLightState中的run接口:
        @Override
        public void run() {
            setTargetLocked(PowerManager.BRIGHTNESS_OFF);
此时button的值设置为off状态。
三、相关内部类
       PowerManagerService中有几个内部类,根据其命名可以很容易了解其功能:
A. ButtonLightState用于背光设置,包括背光使能、亮度等接口;
B. LightSensorPolicy用于与光线传感器相关功能控制;
C. UnsynchronizedWakeLock、LockList这个类与操作过程中的锁申请与释放紧密相关。由于A相关简单,且在上文已经使用,下面将对B、C进行相关分析。
3.1 LightSensorPolicy
LightSensorPolicy实际上为一个策略,在打开光线传感器的时候,通过接口enableLightSensorLocked注册监听:mSensorManager.registerListener(mLightSensorPolicy, mLightSensor, SensorManager.SENSOR_DELAY_NORMAL);当光感值发生变化时,会调用该策略中的updateSensorValue()接口,而该接口主要是通过mLightListener.onSensorChanged(event);实现的。流程大概如下:
       updateSensorValue()-->mLightListener.onSensorChanged(event)--> handleLightSensorValue()-->lightSensorChangedLocked()-->lightSensorChangedLocked_android()-->getAutoBrightnessValue ()-->getAutoBrightnessValue ()-->mScreenBrightnessAnimator.animateTo()(或者mButtonLight.setBrightness(buttonValue)或者mKeyboardLight.setBrightness(keyboardValue);),handleLightSensorValue()传入参数event.values[0]是从sensor中读取出的值。整个过程相对简单,这里不再细说,如果在需要增加相关定制或者修正效果可以通过这个流程进行相关跟踪。这里需要注意一下光线传感器的变化区间,getAutoBrightnessValue (int sensorValue, int[] values),其中的传入参数values为一数组,该组值可以根据项目实际需求进行修改,如想增加光线传感器感光区间数量,从4组改成6组等。这些值最终的定义在frameworks\base\core\res\values\Config.xml中(mtk平台有所差异,在oppo自定义文件framewoks\base\core\res_xx\values\Oppo_config.xml),在initThread接口中读取出来,如:
       //光线传感器原始值,高通上默认为4组
       mAutoBrightnessLevels = resources.getIntArray(
         com.android.internal.R.array.config_autoBrightnessLevels);
       //Lcd对应光线传感器变化值,默认为4组
       mLcdBacklightValues = resources.getIntArray(
        com.android.internal.R.array.config_autoBrightnessLcdBacklightValues);
3.2 锁申请及管理
3.2.1锁的申请以及释放
       外部通过创建PowerManager.java中的内部类WakeLock实例,并通过实例调用acquire()、release()申请锁与释放锁。
       WakeLock(int flags, String tag)
    {
        switch (flags & LOCK_MASK) {
        case PARTIAL_WAKE_LOCK:
        case SCREEN_DIM_WAKE_LOCK:
        case SCREEN_BRIGHT_WAKE_LOCK:
        case FULL_WAKE_LOCK:
        case PROXIMITY_SCREEN_OFF_WAKE_LOCK:
           break;
        default:
              throw new IllegalArgumentException();
        }
        mFlags = flags;
        mTag = tag;
        mToken = new Binder();
    }
       以acquire()为例,最终实现需要通过mService.acquireWakeLock(mFlags, mToken, mTag, mWorkSource);其中mFlags与mTag是在创建PowerManager内部类WakeLock实例时传入mFlags表示申请什么类型的锁,mTag是为了方便查找自己申请而打上的标签,当PowerManagerService.java中打开相关log时,可以用这个mTag做为关键字,查找自己锁的申请以及释放是否正常。在实例WakeLock的时候new了一个mToken的Binder对象,该Binder实例将传入最终被PowerManagerService.java的acquireWakeLockLocked()接口中调用,该接口通过查询当前的所有申请锁列表中查询---int index = mLocks.getIndex(lock);,如果返回值小于0,表示该锁从未申请,通过wl = new WakeLock(flags, lock, tag, uid, pid);在PowermManagerService.java实例一个wakcelock对像,并通过mLocks.addLock(wl);将该实例加入到锁列表中,方便下次查询使用。反之,如果该锁已经存在,通过查询本次申请锁工作源是否改变,如果改变则进行更新:
       if (diffsource) {
          wl.ws = new WorkSource(ws);
   }
       通常该工作源是不会改变的,如果有需要,通过调用PowerManager.java中的setWorkSource()接口即可。
       此外,在申请锁的时候还需要注意一下PowerManager.java中的setReferenceCounted(boolean value);如果需要使用引用计数,则将value设置为true,默认设置为true,表示当用户申请多少次锁时,就必须释放多少次。在申请锁时,如果申请的不是定时锁,且锁类型为PowerManager.PARTIAL_WAKE_LOCK:一定要显示的release,不然系统不能正常待机--即使可以通过强按power键灭屏,但不能正常待机。如果为非PowerManager.PARTIAL_WAKE_LOCK类型锁,在强按power键或灭屏时间到来后,系统会通过gotosleep()接口释放系统层(相对kernel层)所有申请的锁---mLocks.get(i).activated = false;。由于PowerManager.PARTIAL_WAKE_LOCK类型锁直接通过PowerManagerService中调用nativeAcquireWakeLock(PARTIAL_WAKE_LOCK_ID, PARTIAL_NAME); JNI层再到kernel层,所以必须在release()接口中显示释放。插入USB充电时,系统申请的锁应不是通过PowerManagerService服务得到的,通过dumpsys power可以看到在灭屏后两种锁(mLocks.size=0,mPokeLocks.size=0)都为0,但由于在底层申请了相关锁,所以在灭屏的时候系统仍然是没有待机的。注意,如果在设置中,将usb相关设置为“保持唤醒状态”那系统将持有两把PARTIAL_WAKE_LOCK锁,在不按power键的情况下屏幕不会完全灭下去,但按power键后还是能灭屏的,只是还是能通过dumpsys power看到申请的PARTIAL_WAKE_LOCK锁没有被强制释放。
       PS:在Mtk平台4.0中,如果申请了FULL_WAKE_LOCK类型的锁,当待机时间到达后,虚拟机会通过调用 finalize() 接口强制释放申请的锁--对应Log为:GC_FOR_ALLOC freed xxxx。但在4.1(12067)中是会一直亮屏的。这个需要确认一下是否我们改了该平台回收程序。高通平台中4.0及4.1都不会强制回收这一部分锁。其中finalize()是Object对象方法,android系统在PowerManager.java的内部类WakeLock中重写了该方法。
3.2.2 mLocks
       LockList实现上为一个WakeLock类型的列表,所有外部申请的锁都将记录在这张列表中--注意上文中提到的PARTIAL_WAKE_LOCK类型锁除外。mLocks为LockList的实例,在上文中提到的锁申请、释放用到该实例addLock()、removeLock。注意列表成员WakeLock中调用的b.linkToDeath(this, 0);表示将监控传入的IBinder b是否死亡,如果远程binder(或者说客户端)被杀,将抛出异常后调用binderDied()接口,该接口释放已经申请的锁。例如:在4.1中申请了一个FULL_WAKE_LOCK类型锁,正常情况下,如果不主动释放系统会一直亮屏,但申请锁的应用被杀,申请的锁将通过binderDied()接口释放。
       此外,在使用到LockList相关的变量时,一定要加mLocks的同步,不然会造成线程不安全。
3.2.3 异步锁
       大家可能注意到了PowerManagerService.java中有一个 UnsynchronizedWakeLock 内部类。根据注释可以知道这个类实现的是异步获取唤醒锁。当 PowerManager.WakeLock 与 synchronizing on mLocks 之间发生死锁的时候,这个类可以重新实现 PowerManager.WakeLock 。由于它没有外部同步块,所以只有当拥有锁的时候才调用这个类。该类实现了5个对象,但有的也是在mLocks的同步块中调用的,如果真的发生死锁,在 mLocks 同步块中调用的异步锁同样会被锁死,个人理解主要还在为了方便Power服务线程内部调用。
       异步锁与同步锁使用时的协调方式,PowerManagerService.java中这两种锁都要对 mLocks 进行读写操作;这就存在一个问题,异步锁的存在怎么样才能避免对mLocks的add与remove不会导致数组越界?其实异步锁的调用大部分都间接在mLock同步块中被调用;
3.2.3距离传感器相关
       距离传感器放在这个部分是因为它没有单独的内部内,而且打开的时候也是在锁申请的时候。当用户通过锁的申请调用PowerManagerService.java中的acquireWakeLockLocked()方法的时候,通过传入的flag参数确认是否打开距离传感器:
       if ((flags & LOCK_MASK) == PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK) {
       mProximityWakeLockCount++;
       if (mProximityWakeLockCount == 1) {
              enableProximityLockLocked();
       }
       通过打开传感器在phone app中。enableProximityLockLocked方法中最主要的工作就是建立了一个监听,当距离传感器的值发生变化的时候,这个监听实现亮屏和灭屏的功能 。
       SensorEventListener mProximityListener = new SensorEventListener() {
         //通常走的是else逻辑,操作速度低于1s,否则走if
       }
       此处有一个mHandler.postDelayed(mProximityTask, PROXIMITY_SENSOR_DELAY - timeSinceLastEvent);,mProximityTask为一个定时任务,实质为一个线程;真正实现灭屏的方法是proximityChangedLocked()中的goToSleepLocked(SystemClock.uptimeMillis(),WindowManagerPolicy.OFF_BECAUSE_OF_PROX_SENSOR);该方法在mProximityTask任务中也有调用。proximityChangedLocked()还包括了亮屏的实现,当远离距离传感器(大小5cm)的时候,会调用forceUserActivityLocked()方法。mProximityPendingValue==1时,表示有靠近的任务需要执行。
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值