分享

powermanager wakelock

 未央界 2013-08-16

PowerManager.WakeLock 有加锁和解锁两种状态,加锁的方式有两种,一种是永久的锁住,这样的锁除非显式的放开,是不会解锁的,所以这种锁用起来要非常的小心。第二种锁是超时锁,这种锁会在锁住后一段时间解锁。

在创建了 PowerManager.WakeLock 后,有两种机制,第一种是不计数锁机制,另一种是计数锁机制。可以通过 setReferenceCounted( boolean value) 来指定,一般默认为计数机制。这两种机制的区别在于,前者无论 acquire() 了多少次,只要通过一次 release() 即可解锁。而后者正真解锁是在( --count == 0 )的时候,同样当 (count == 0) 的时候才会去申请加锁,其他情况 isHeld 状态是不会改变的。所以 PowerManager.WakeLock 的计数机制并不是正真意义上的对每次请求进行申请/释放每一把锁,它只是对同一把锁被申请/释放的次数进行了统计再正真意义上的去操作。

接下来,我们来分析一下 PowerManager.WakeLock 超时锁的机制。我们可以通过 acquire( long timeout) 来使用,在源码中我们可以看到

public void acquire( long timeout) {

acquire();

mHandler .postDelayed( mReleaser , timeout);

}

在申请一把锁的同时发送了 release() 锁的延时消息。在时间到达后自動释放。在来看看源码中 acquire release 代码:

public void acquire(){

synchronized ( mToken ) {

if (! mRefCounted || mCount ++ == 0) {

try {

mService .acquireWakeLock( mFlags , mToken , mTag );

} catch (RemoteException e) {

}

mHeld = true ;

}

}

}

 

public void release( int flags){

synchronized ( mToken ) {

if (! mRefCounted || -- mCount == 0) {

try {

mService .releaseWakeLock( mToken , flags);

} catch (RemoteException e) {

}

Held = false ;

}

if ( mCount < 0) {

throw new RuntimeException("WakeLock under-locked "+mTag);

}

}

}

在源码中我们可以看出计数机制在达到一定条件下才会去改变锁的状态,而不计数机制就每次请求改变一次。在 release( int flags) 中我们还可以看到那么一段代码

if ( mCount < 0) {

throw new RuntimeException("WakeLock under-locked "+mTag);

}

这段代码起什么效果呢?为什么要判断并抛出异常呢?其实对于非计数机制来讲,这代码的用处相当于 0 ,因为 mRefCounte 状态是 false (非计数锁),永远不会去判断 mCount 标志去进行操作的。但是对于计数锁机制来讲,这句话至关重要,一旦系统中的 mCount < 0 时,再去申请锁的时候将达不到申请锁的条件 mCount ++ == 0 这将导致系统申请不到需要的锁,所以 google 工程师在这里手动抛出了异常,让系统重置。

但是为什么会出现 mCount < 0 的情况呢?原因在于使用计数锁的时候 release 次数大于 acquire 次数的时候,将会导致系统再次申请锁失效。这种情况是工测师逻辑不严谨导致的,但是还有一种特殊情况就是使用计数锁和超时锁的时候,这种情况是由于时间戳导致的,我们来看看一段代码(计数锁情况):

private void wakeUpScreen() {

synchronized ( this ) {

if ( mWakeLock .isHeld()) {

mWakeLock .release();

}

mWakeLock .acquire(3000);

}

}

假设系统在 3s 里调用了两次以上方法,第一次成功调用后,系统锁计数为 1 ,在第一次释放没有到时的情况下,我们再此调用此方法,这时判断到锁的状态为 held ,这时会去释放一次, count 变为 0 。在第二次 acquire 前,系统解锁到第一次 release 锁的消息,这时会再释放一次,但是这时候, count 已经是 0 了,导致系统 count 变为负数,抛出异常,系统重启重置,保证锁的安全性。

PowerManager.WakeLock源码解读(By DADA) - Me.... - DaDaDuDu....

我们可能会问为什么不在手动 release 的时候把存在的 Delayed 消息 remove 掉,这样做的坏处是不能保证 Delayed 消息是同一个地方发出的,将会把其他地方调用的消息给处理掉,这并不符合逻辑要求。

 

但是又要使用超时锁和计数机制的发开者该怎样避免这种情况呢?一是保证你在 Delayed 消息时间呢不会被调用多次;二是不依赖超时锁的机制,在自己代码中定义延时消息,这样可以保证在同一个地方发送消息和销毁消息,保证不会去释放空锁;

 

PS :为了避免释放空锁的情况,导致下次申请不到锁的情况,为什么要抛出异常,中断系统呢?为什么不在 release 的时候加以判断,判断现在 count 是否为 0 ,而直接返回不操作呢?为什么不在发现 count 为负数的时候就把 count 重置为 0 ,保证下次申请锁成功呢?(不知道 google 工程师有什么后续的考虑) , 这个疑问留给读者一起进行思考。

学习android一段时间了,为了进一步了解android的应用是如何设计开发的,决定详细研究几个开源的android应用。从一些开源应用中吸收点东西,一边进行量的积累,一边探索android的学习研究方向。这里我首先选择了jwood的 Standup Timer  项目。本文将把研究的内容笔记整理,建立一个索引列表。

PowerManager.WakeLock

  PowerManager.WakerLock是我分析Standup Timer源代码时发现的一个小知识点,Standup Timer 用WakeLock保证程序运行时保持手机屏幕的恒亮(程序虽小但也做得相当的细心,考虑的很周到)。PowerManager 和PowerManager.WakerLock用于对Android设备的电源进行管理。
  PowerManager :This class gives you control of the power state of the device.
  PowerManager.WakeLock : lets you say that you need to have the device on.
  Android中通过各种Lock锁对电源进行控制,需要注意的是加锁和解锁必须成对出现。先上一段Standup Timer里的代码然后进行说明。

  1. private   void  acquireWakeLock() {  
  2.      if  (wakeLock ==  null ) {  
  3.             Logger.d("Acquiring wake lock" );  
  4.             PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);  
  5.             wakeLock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, this .getClass().getCanonicalName());  
  6.             wakeLock.acquire();  
  7.         }  
  8.       
  9. }  
  10.   
  11.   
  12. private   void  releaseWakeLock() {  
  13.     if  (wakeLock !=  null  && wakeLock.isHeld()) {  
  14.         wakeLock.release();  
  15.         wakeLock = null ;  
  16.     }  
  17.   
  18. }  

acquireWakeLock()方法中获取了 SCREEN_DIM_WAKE_LOCK锁,该锁使 CPU 保持运转,屏幕保持亮度(可以变灰)。这个函数在Activity的 onResume中被调用。releaseWakeLock()方法则是释放该锁。它在Activity的 onPause中被调用。利用Activiy的生命周期,巧妙的让 acquire()和release()成对出现。

    @Override
    protected void onResume()
    {
        super.onResume();
                //获取锁,保持屏幕亮度
        acquireWakeLock();
        startTimer();
    }

    @Override
    protected void onPause()
    {
        super.onPause();
         synchronized(this) {
                cancelTimer();
                releaseWakeLock();
                if (finished) {
                    clearState();
                } else {
                    saveState();
                }
            }
    }
 
PowerManager和WakeLock的操作步骤
  1. PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);通过 Context.getSystemService() .方法获取PowerManager实例。
  2. 然后通过PowerManager的newWakeLock ((int flags, String  tag)来生成WakeLock实例。int Flags指示要获取哪种WakeLock,不同的Lock对cpu 、屏幕、键盘灯有不同影响。
  3. 获取WakeLock实例后通过acquire()获取相应的锁,然后进行其他业务逻辑的操作,最后使用release()释放(释放是必须的)。
 
//JBP_MAIN/Cinnamon/frameworks/base/core/java/android/os/PowerManager.


关于int flags

各种锁的类型对CPU 、屏幕、键盘的影响:

    PARTIAL_WAKE_LOCK :保持CPU 运转,要求屏幕开启(可以dim),键盘背光允许关闭。

    SCREEN_DIM_WAKE_LOCK :保持CPU 运转,允许保持屏幕显示但有可能是灰的,允许关闭键盘背光

    SCREEN_BRIGHT_WAKE_LOCK :保持CPU 运转,允许保持屏幕高亮显示,允许关闭键盘灯

    FULL_WAKE_LOCK :保持CPU 运转,保持屏幕高亮显示,键盘灯也保持亮度

    ACQUIRE_CAUSES_WAKEUP :Normal wake locks don't actually turn on the illumination. Instead, they cause the illumination to remain on once it turns on (e.g. from user activity). This flag will force the screen and/or keyboard to turn on immediately, when the WakeLock is acquired. A typical use would be for notifications which are important for the user to see immediately.

    ON_AFTER_RELEASE :f this flag is set, the user activity timer will be reset when the WakeLock is released, causing the illumination to remain on a bit longer. This can be used to reduce flicker if you are cycling between wake lock conditions.

权限获取

要进行电源的操作需要在AndroidManifest.xml中声明该应用有设置电源管理的权限。
< uses-permission android:name ="android.permission.WAKE_LOCK" /> 你可能还需要 < uses-permission android:name ="android.permission.DEVICE_POWER" />
在前四种锁中,只有PARTIAL_WAKE_LOCK 是可以在用户power键之后不进入sleep状态而继续运行的,同时可以
忽略超时及屏幕状态。 后两种锁是可以和前两种结合使用的,前四种锁允许之前的某些状态不变,但持有
ACQUIRE_CAUSES_WAKEUP 可以立刻点亮键盘和屏幕,其典型应用是通知栏。ON_AFTER_RELEASE 是在释放锁后
设置一个定时器,保证在一段时间内保持照明状态,典型应用是在循环wake锁时不会频繁闪动。在用户按压电源键时,
屏幕关闭,按键背光关闭,但CPU会工作到所有的PARTIAL_WAKE_LOCK 都被释放。
 
在PowerManager中有对屏幕最大亮度,最小亮度,发生事件的定义(按键事件等),用户设置屏幕亮度的最大、最小、默认值,
同时制定level(dim)和flag(ON_AFTER_RELEASE)。
      * PowerManager pm = (PowerManager)mContext.getSystemService(
     *                                          Context.POWER_SERVICE);
     * PowerManager.WakeLock wl = pm.newWakeLock(
     *                                      PowerManager.SCREEN_DIM_WAKE_LOCK
     *                                      | PowerManager.ON_AFTER_RELEASE,
     *                                      TAG);
     * wl.acquire();
     * // ... do work...
     * wl.release();
     
如果仅仅是要控制屏幕的点亮,可以使用android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON代替,这个可以更好地被平台所控制 ,当用户在应用间切换并且不需要特定的权限(link android.Manifest.permission#WAKE_LOCK)。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多