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 变为负数,抛出异常,系统重启重置,保证锁的安全性。
我们可能会问为什么不在手动 release 的时候把存在的 Delayed 消息 remove 掉,这样做的坏处是不能保证 Delayed 消息是同一个地方发出的,将会把其他地方调用的消息给处理掉,这并不符合逻辑要求。
但是又要使用超时锁和计数机制的发开者该怎样避免这种情况呢?一是保证你在 Delayed 消息时间呢不会被调用多次;二是不依赖超时锁的机制,在自己代码中定义延时消息,这样可以保证在同一个地方发送消息和销毁消息,保证不会去释放空锁;
PS :为了避免释放空锁的情况,导致下次申请不到锁的情况,为什么要抛出异常,中断系统呢?为什么不在 release 的时候加以判断,判断现在 count 是否为 0 ,而直接返回不操作呢?为什么不在发现 count 为负数的时候就把 count 重置为 0 ,保证下次申请锁成功呢?(不知道 google 工程师有什么后续的考虑) , 这个疑问留给读者一起进行思考。
学习android一段时间了,为了进一步了解android的应用是如何设计开发的,决定详细研究几个开源的android应用。从一些开源应用中吸收点东西,一边进行量的积累,一边探索android的学习研究方向。这里我首先选择了jwood的 Standup Timer 项目。本文将把研究的内容笔记整理,建立一个索引列表。
- private void acquireWakeLock() {
- if (wakeLock == null ) {
- Logger.d("Acquiring wake lock" );
- PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
- wakeLock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, this .getClass().getCanonicalName());
- wakeLock.acquire();
- }
- }
- private void releaseWakeLock() {
- if (wakeLock != null && wakeLock.isHeld()) {
- wakeLock.release();
- wakeLock = null ;
- }
- }
private void acquireWakeLock() { if (wakeLock == null) { Logger.d("Acquiring wake lock"); PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); wakeLock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, this.getClass().getCanonicalName()); wakeLock.acquire(); } } private void releaseWakeLock() { if (wakeLock != null && wakeLock.isHeld()) { wakeLock.release(); wakeLock = null; } }
- private void acquireWakeLock() {
- if (wakeLock == null) {
- Logger.d("Acquiring wake lock");
- PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
- wakeLock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, this.getClass().getCanonicalName());
- wakeLock.acquire();
- }
- }
- private void releaseWakeLock() {
- if (wakeLock != null && wakeLock.isHeld()) {
- wakeLock.release();
- wakeLock = null;
- }
- }
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();
- }
- }
- }
@Override protected void onResume() { super.onResume(); //获取锁,保持屏幕亮度 acquireWakeLock(); startTimer(); } @Override protected void onPause() { super.onPause(); synchronized(this) { cancelTimer(); releaseWakeLock(); if (finished) { clearState(); } else { saveState(); } } }
- @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的操作步骤
Context.getSystemService()
.方法获取PowerManager实例。 PARTIAL_WAKE_LOCK :保持CPU 运转,屏幕和键盘灯有可能是关闭的。
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.
< uses-permission android:name ="android.permission.WAKE_LOCK" /> 你可能还需要 < uses-permission android:name ="android.permission.DEVICE_POWER" />