23

The pm and keepScreenOn variables are globally defined.

I grab the PowerManager.WakeLock in my OnCreate method:

pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
keepScreenOn = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_LOCK,"tpd");

in my onStart, onResume, and onRestart I grab the lock with

if (keepScreenOn == null) {
    keepScreenOn = pm.newakeLock(PowerManager,SCREEN_BRIGHT_LOCK,"tpd");
}
keepScreenOn.acquire();

in my onDestroy, onPause, and onStop I release the lock with:

if (keepScreenOn != null) {
  keepScreenOn.release();
  keepScreenOn = null
}

After my app exits I get a failure screen and adb complains that

java.lang.Exception: WakeLock finalized while still held: tpd

Tracing shows that I released the lock before exit. What have I missed?

There is no way out of the app without crossing at least one of onPause, onStop, or onDestroy. I can see that the app called release() as often as it called acquire() so even though the wakelock is reference counted it should still have zero refs.

Mathew Thompson
  • 55,877
  • 15
  • 127
  • 148
Tim Daly
  • 231
  • 1
  • 2
  • 3
  • May be it is not related to your problem, but why you are creating and releasing lock in so many places? Why not to do it only at onResume and onPause? – Olegas May 07 '11 at 14:23
  • I tried that. Then I traced every onXXX routine. Then I added it – Tim Daly May 07 '11 at 15:42
  • Is your `keepScreenOn` variable is declared only once in a class? Is there some other declarations with lesser scope (i.e. declared in method). – Olegas May 07 '11 at 17:47
  • 1
    I tend to use `WakeLock.acquire(5000)`, where 5,000 is a timeout, or another timeout that you wish. This method seemed to work consistently. Whereas `WakeLock.acquire()` without a timeout unexpectedly can lead to **WakeLock finalized while still held**. It's a hit-and-miss affair. If you don't want to write your own WakeLock implementation, take a look at CommonsWare's WakeLock [library](https://github.com/commonsguy/cwac-wakeful/tree/master/wakeful). It's not bad. – ChuongPham Dec 29 '13 at 13:04

3 Answers3

32

Ok I believe I found the problem.

The WakeLock is reference counted. That means that if a second acquire() happens it will just bump the reference count. Every call to acquire() needs to be protected by a call to isHeld() as in:

if ((keepScreenOn != null) &&           // we have a WakeLock
    (keepScreenOn.isHeld() == false)) {  // but we don't hold it 
  keepScreenOn.acquire();
}

I had assumed that acquire() on a lock I held did nothing so multiple acquire() calls caused the problem. Since the reference count is not zero the GC throws an error.

Kurt Wagner
  • 3,295
  • 13
  • 44
  • 71
Tim Daly
  • 321
  • 2
  • 2
4

I know this question is old, but keep in mind that WakeLocks are 'reference counted' by default. You can turn off reference counting using setReferenceCounted(boolean), see http://developer.android.com/reference/android/os/PowerManager.WakeLock.html#setReferenceCounted(boolean)

jmroyalty
  • 2,527
  • 1
  • 17
  • 21
0

No, there is only one declaration at the global scope and all calls to the acquire() and release() occur in that scope. I println when they happen and the acquire() occurs once and the release occurs once.

Tim Daly
  • 321
  • 2
  • 2