Search code examples
androidservicebroadcastreceiverwakelock

Correct pattern to acquire a WakeLock in a BroadcastReceiver and release it in a Service


Even after a lot of research I am still not completely sure if the way how I implement a WakeLock for a Service started by a BroadcastReceiver is correct - even though it seems to work fine. The broadcast receiver gets intents sent to it from an alarm, so to start with, from the API docs of AlarmManager:

If your alarm receiver called Context.startService(), it is possible that the phone will sleep before the requested service is launched. To prevent this, your BroadcastReceiver and Service will need to implement a separate wake lock policy to ensure that the phone continues running until the service becomes available.

So, in onReceive() I do:

    Intent serviceIntent = new Intent(context, SomeService.class);
    context.startService(serviceIntent);

    if(SomeService.wakeLock == null) {
        PowerManager powerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
        SomeService.wakeLock = powerManager.newWakeLock(
                PowerManager.PARTIAL_WAKE_LOCK, 
                SomeService.WAKE_LOCK_TAG);
    }
    if(! SomeService.wakeLock.isHeld()) {
        SomeService.wakeLock.acquire();
    }

and in the service I do:

    try {
        // Do some work
    } finally {
        if(wakeLock != null) {
            if(wakeLock.isHeld()) {
                wakeLock.release();
            }
            wakeLock = null;
        }
    }

The SomeService.wakeLockfield is package private, static and volatile.

What I am unsure about is the check using isHeld() - does it really tell me if a WakeLock is acquired or not, and do I need to do this check at all?


Solution

  • What I am unsure about is the check using isHeld() - does it really tell me if a WakeLock is acquired or not, and do I need to do this check at all?

    Actually slightly tricky to answer. Looking at the source for PowerManager and PowerManager.WakeLock here the WakeLock.acquire() and WakeLock.acquireLocked() methods are as follows...

    public void acquire(long timeout) {
        synchronized (mToken) {
            acquireLocked();
            mHandler.postDelayed(mReleaser, timeout);
        }
    }
    
    private void acquireLocked() {
        if (!mRefCounted || mCount++ == 0) {
            // Do this even if the wake lock is already thought to be held (mHeld == true)
            // because non-reference counted wake locks are not always properly released.
            // For example, the keyguard's wake lock might be forcibly released by the
            // power manager without the keyguard knowing.  A subsequent call to acquire
            // should immediately acquire the wake lock once again despite never having
            // been explicitly released by the keyguard.
            mHandler.removeCallbacks(mReleaser);
            try {
                mService.acquireWakeLock(mToken, mFlags, mTag, mWorkSource);
            } catch (RemoteException e) {
            }
            mHeld = true;
        }
    }
    

    ...mService is an IPowerManager interface and the source for it isn't available so it's hard to tell what may or may not go wrong when attempting to call acquireWakeLock(...).

    In any case, the only exception that can be caught is RemoteException and the catch block does nothing. Immediately after the try/catch, mHeld is set true regardless.

    In short, if you call isHeld() immediately after acquire() the result will always be true.

    Looking further into the source for PowerManager.WakeLock shows similar behaviour for release() which calls release(int flags) where the mHeld member is always set to false regardless of what happens.

    In conclusion I'd suggest it is always a good idea to check isHeld() just as a best practice in case later versions of Android change this behaviour of the WakeLock methods.