PowerManager.WakeLock有加锁和解锁两种状态,加锁的方式有两种,一种是永久的锁住,这样的锁除非显式的放开,是不会解锁的,所以这种锁用起来要非常的小心。第二种锁是超时锁,这种锁会在锁住后一段时间解锁。
在创建了PowerManager.WakeLock后,有两种机制,第一种是不计数锁机制,另一种是计数锁机制。可以通过setReferenceCounted(booleanvalue)来指定,一般默认为计数机制。这两种机制的区别在于,前者无论acquire()了多少次,只要通过一次release()即可解锁。而后者正真解锁是在(--count== 0)的时候,同样当(count==0)的时候才会去申请加锁,其他情况isHeld状态是不会改变的。所以PowerManager.WakeLock的计数机制并不是正真意义上的对每次请求进行申请/释放每一把锁,它只是对同一把锁被申请/释放的次数进行了统计再正真意义上的去操作。一下进行了永久锁的测试:从测试我们可以看到使用计数和计数锁的区别。
接下来,我们来分析一下PowerManager.WakeLock超时锁的机制。我们可以通过acquire(longtimeout)来使用,在源码中我们可以看到
publicvoidacquire(longtimeout) {
acquire();
mHandler.postDelayed(mReleaser,timeout);
}
在申请一把锁的同时发送了release()锁的延时消息。在时间到达后自動释放。在来看看源码中acquire和release代码:
publicvoidacquire(){
synchronized(mToken) {
if(!mRefCounted|| mCount++== 0) {
try{
mService.acquireWakeLock(mFlags,mToken,mTag);
} catch(RemoteException e) {
}
mHeld= true;
}
}
}
publicvoidrelease(intflags){
synchronized(mToken) {
if(!mRefCounted|| --mCount== 0) {
try{
mService.releaseWakeLock(mToken,flags);
} catch(RemoteException e) {
}
Held= false;
}
if(mCount <0) {
thrownew RuntimeException("WakeLock under-locked "+mTag);
}
}
}
在源码中我们可以看出计数机制在达到一定条件下才会去改变锁的状态,而不计数机制就每次请求改变一次。在release(intflags) 中我们还可以看到那么一段代码
if(mCount <0) {
thrownew RuntimeException("WakeLock under-locked "+mTag);
}
这段代码起什么效果呢?为什么要判断并抛出异常呢?其实对于非计数机制来讲,这代码的用处相当于0,因为mRefCounte状态是false,永远不会去判断mCount标志去进行操作的。但是对于计数锁机制来讲,这句话至关重要,一旦系统中的mCount< 0时,再去申请锁的时候将达不到申请锁的条件mCount++== 0 这将导致系统申请不到需要的锁,所以google工程师在这里手动抛出了异常,让系统重置。
但是为什么会出现mCount< 0的情况呢?原因在于使用计数锁的时候release次数大于acquire次数的时候,将会导致系统再次申请锁失效。这种情况是工测师逻辑不严谨导致的,但是还有一种特殊情况就是使用计数锁和超时锁的时候,这种情况是由于时间戳导致的,我们来看看一段代码(计数锁情况):
privatevoidwakeUpScreen() {
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;
- }
- }
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();
- }
- }
- }
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" />