Service not starting on Oreo in app widget using PendingIntent
You can no longer start a service in background in 8.0, but you can use JobScheduler
to achieve similar results. There is also a JobIntentService
helper class that allows you to switch to JobScheduler
from service without much refatoring. And you cannot use PendingIntent
pointing to a service, but you can use one pointing to an Activity
or BroadcastReceiver
.
If you had a working widget pre 8.0, and now you need to make it work on android 8.0, just perform this simple steps:
- Change your
IntentService
class toJobIntentService
- Rename service
onHandleIntent
method toonHandleWork
(same parameters) - Add
BIND_JOB_SERVICE
permission to your service in the manifest:
<service android:name=".widget.MyWidgetService"
android:permission="android.permission.BIND_JOB_SERVICE">
</service>
- To start this service, you must no longer use context.startService. Instead use
enqueueWork
static method (where JOB_ID is just an unique integer constant, must be the same value for all work enqueued for the same class):
enqueueWork(context, MyWidgetService.class, JOB_ID, intent);
- For clicks, replace your pendingIntent that was pointing to service with a pendingIntent that points to a BroadcastReceiver. Since your subclass of AppWidgetProvider is a BroadcastReceiver itself, you might as well use it:
Intent myIntent = new Intent(context, MyAppWidgetProvider.class);
myIntent .setAction("SOME_UNIQUE_ACTION");
pendingIntent = PendingIntent.getBroadcast(context, 0, myIntent, PendingIntent.FLAG_UPDATE_CURRENT);
- In onReceive start the service using enqueueWork (If your
PendingIntent
was starting theactivity
, just leave it be - it'll work just fine on android 8.0+):
@Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
if (intent.getAction().equals("SOME_UNIQUE_ACTION")) {
MyWidgetService.enqueueWork(.....);
}
}
- To ensure that the
widget
will work on old devices, make sure you have WAKE_LOCK permission in your manifest (used byJobIntentService
on old devices).
That's it. This widget will now work properly on both new and old devices. The only real difference will be that if your 8.0 device is in doze mode it may not update widget all that often, but that shouldn't be a problem because if it is dozing, that means that user can't see your widget right now anyways.
Could goasyc be an option? You could change your PendingIntent to fire a BroadcastReceiver, then in the OnRecieve method, you can call goasync() Then you should be able to use the PendingResult it generates to create an async call. Still don't think you can start a service.
How to use "goAsync" for broadcastReceiver?
As you note, an app widget does not count for the PendingIntent
background whitelist. I do not know why — it would seem to be about on par with a PendingIntent
started by a Notification
. Perhaps it's an issue that the Notification
is a system thing, whereas the app widget is a home screen thing. Regardless, you could:
Use
getForegroundService()
, as you have, orTry
getBroadcast()
with an explicitIntent
, and with theBroadcastReceiver
starting aJobIntentService
, if you do not want to raise aNotification
(as a foreground service requires)
Based on your symptoms, you appear to have tripped over what I would consider to be a bug: there should be a way to switch from getService()
to getForegroundService()
without having to reboot. :-) I'll try to run some experiments and will file an issue if I can reproduce the problem.