Reputation: 1562
There are a number of similar questions on SO, but they are all pre-O, where IntentService works just fine. I tried to do it as a JobIntentService, but the delay is unacceptable. The network exchange for my widget is supposed to be really quick, or not at all, the socketTimeout is set to 100ms, so several seconds of delay is frustrating.
I want to try several solutions here. First, to create a foregroundService from the context. As far as I understood, I have 5 seconds before the service is killed, so if my exchange takes just a fraction of that, I should be good. Is this assumption correct? Is this a good use for a foregroundService?
Next, what if I just do it by starting manually a new thread in onReceive of my AppWidgetProvider? As I said, the network exchange should take less than the quarter of a second. What can possibly happen to the context during that time?
What is the best way to make a quick network request from an appWidget, that should happen immediately after it sends the broadcast?
Upvotes: 2
Views: 1449
Reputation: 325
There are several things to point out in answering this. We have appWidgets that use a service to handle transactions because jobService is not an option for us. Sharing a few things we have done which will hopefully help you.
On your first question:
The service is called from the appWidget provider and does all the work, and it must be called as a foreground service on Oreo+, if that foreground service is not started within 5 seconds of that call you will get an exception: "Context.startForegroundService did not then call Service.startForeground". There is an unresolved issue in the Google tracker that doesn't seem to have been resolved yet about this issue, so you should make sure you read this as well. Some have suggested putting the following code in both onCreate() and onStart() of the service. The issue is https://issuetracker.google.com/issues/76112072.
I would suggest doing all the work you need in the Service after calling it from the provider. (Also remember to use a notification channel for Oreo+) For example:
In appWidget provider onUpdate():
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(new Intent(serviceIntent));
} else {
context.startService(new Intent(serviceIntent));
}
Then in your service class- put this in onStartCommand() and onCreate() per the issue link I've posted above.
savedIntent = intent;
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
//Google Issue tracker https://issuetracker.google.com/issues/76112072 as of 9/26/2018. StartForeground notification must be in both onCreate and onStartCommand to minimize
//crashes in event service was already started and not yet stopped, in which case onCreate is not called again
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(PRIMARY_NOTIF_CHANNEL, "YOUR NOTIFICATION CHANNEL", NotificationManager.IMPORTANCE_DEFAULT);
channel.setSound(null, null);
channel.enableVibration(false);
if (notificationManager != null) {
notificationManager.createNotificationChannel(channel);
}
Notification notification = new NotificationCompat.Builder(this, PRIMARY_NOTIF_CHANNEL)
.setSmallIcon(R.drawable.your_drawable)
.setBadgeIconType(NotificationCompat.BADGE_ICON_SMALL)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setContentTitle(getText(R.string.app_name))
.setStyle(new NotificationCompat.InboxStyle()
.addLine("Needs to sync periodically. Long press to hide.")
.addLine(""))
.build();
startForeground(PRIMARY_FOREGROUND_NOTIF_SERVICE_ID, notification);
}
On your second question:
AppWidget Provider's onReceive() method is triggered by broadcasts with intents, which can be used to do different tasks, partially update the appWidget, or restart the service. So based on the information you've provided, you should be able do your network call in the onReceive area of the provider, but you may need to specify a custom intent for it to trigger onReceive(), and make sure you update all instances of the appWidget as well to update the remote views. As an example, we have several custom intents which trigger the onReceive() and partially update our appWidget:
In appWidget provider:
// sample custom intent name
public static final String ACTION_CLICK_ADVANCE_STATUS = "widget_action_click_advance_status";
...
@Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
if (ACTION_CLICK_ADVANCE_STATUS.equals(intent.getAction())) {
// update all instances of appWidgets
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, WidgetJobProvider.class));
// Do your work here
}
Upvotes: 1