22

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?

Torsten Römer
  • 3,834
  • 4
  • 40
  • 53

4 Answers4

4

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.

Squonk
  • 48,735
  • 19
  • 103
  • 135
  • I should also just have a look at the Android sources when the documentation isn't sufficient (which is quite rare, I think) - thanks! – Torsten Römer Aug 25 '11 at 18:52
  • 1
    You suggest calling `isHeld()` before calling `release()` in case future versions of Android change the behaviour. Don't you think that if a future version changed its behaviour, it would (for backwards-compatibility reasons and to prevent a lot of market apps from crashing) need to deal properly with calls to `release()` where the lock is not actually held? Not only that, but the example code which is in the source code for `PowerManager` and in the documentation explicitly calls `acquire()` and `release()` unconditionally. Also, since `WakeLock`s are reference counted, it is required that... – David Wasser Oct 06 '15 at 14:23
  • ...you call `release()` the same number of times you call `acquire()`. Of course, by all means call `isHeld()` before calling `release()` but I wouldn't brand this a "best practice". – David Wasser Oct 06 '15 at 14:24
4

Manage you wakeLock inside a singleton (unique instance accessible through all your context and object)

Use a singleton instance of a custom class, then you may get wakelock object reference from call to call ,



here an singleton example

class MyData {
   private static MyData mMydata= null; // unique reference ( singleton objet container)

   private PowerManager.Wakelock myobject = null; // inside the unique object container we have the unique working object to be use  by the application
   // can't make instance from outside... we want to have single instance
   // we want that outside use method "getInstance" to be able to use the object
   private MyData() {
   }

   // retrieve and/or create new unique instance
   public static MyData getInstance() {
     if (mMydata ==  null) mMyData = new MyData();
     return   mMyData;
   }

   // Works with your memory stored object
   // get...
   public PowerManager.WakeLock getMyWakelock() {
   return myobject;
   }
   // set ...
   public void setMyWakeLock(PowerManager.WakeLock obj) {
    myobject = obj;
   }
}


in your application to handle your "wakelock" object your may access it like

// set a created wakelock
MyData.getInstance().setMyWakeLock(wl);
// get the saved wakelock object
PowerManager.WakeLock obj =  MyData.getInstance().getMyWakeLock();
Emmanuel Devaux
  • 3,327
  • 4
  • 25
  • 30
  • Right, static singletons seem to be quite common in Android. I am not sure about the advantages and disadvantages compared to use a static member, also thinking about unit testing. – Torsten Römer Aug 25 '11 at 18:55
  • it's mainly because it is a largely accepted designed pattern commonly used in most OO languages... But if classic static is right for you, you're free to use it :) – Emmanuel Devaux Aug 25 '11 at 20:13
  • I don't really agree - I think a static singleton is a bit of an antipattern, especially in JEE (what I mainly work with), and I'd use it only if there is no other way. But that holds true for any static stuff so I try to avoid it as much as I can. And, BTW, the preferred pattern for a singleton is the enum one (see Joshua Blochs "Effective Java"). – Torsten Römer Aug 26 '11 at 11:02
  • @Torsten : You're right, that why most of implementation of this pattern use not simple static but "Thread" singleton or "Context Class Loader" singleton etc... – Emmanuel Devaux Aug 26 '11 at 11:07
3

All this job can be done by a helper and native class called WakefulBroadcastReceiver

CelinHC
  • 1,857
  • 2
  • 27
  • 36
0

I think android.os.Messenger may be a better way

for the receiver:

public class MessengerReceiver extends BroadcastReceiver {


    private static final String TAG = "MessengerReceiver";

    private final MessengerHandler mHandler = new MessengerHandler();

    @Override
    public void onReceive(Context context, Intent intent) {
        // TODO: This method is called when the BroadcastReceiver is receiving
        // an Intent broadcast.
        mHandler.mWakeLock = ((PowerManager)context.getSystemService(Service.POWER_SERVICE)).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "myreceiver");
        mHandler.mWakeLock.acquire();
        Log.e(TAG, "onReceive:: mHandler.mWakeLock=" + mHandler.mWakeLock + ", intent=" + intent + ", this=" + this);
        context.startService(new Intent(context, MessengerService.class).putExtra("messenger", new Messenger(mHandler)));
    }

    static class MessengerHandler extends Handler {

        WakeLock mWakeLock;
        @Override
        public void handleMessage(Message msg) {
            // TODO Auto-generated method stub
            if(mWakeLock != null){
                mWakeLock.release();
                Log.e(TAG, "handleMessage:mWakeLock=" + mWakeLock);
            }
            super.handleMessage(msg);
        }

    }
}

for the service:

public class MessengerService extends Service {
    private static final String TAG = "MessengerService";
    public MessengerService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // TODO Auto-generated method stub      
        Log.e(TAG, "onStartCommand:: intent=" + intent);
        final Messenger messenger = intent.getParcelableExtra("messenger");
        try {
            messenger.send(Message.obtain());
        } catch (RemoteException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return super.onStartCommand(intent, flags, startId);
    }
}

This method work properly even if service & receiver run in different process.

Yessy
  • 1,172
  • 1
  • 8
  • 13