The Problem
My AppWidget
isn't updating when it should. I can't figure out why.
Relevant Code
Here is my app_widget_info.xml
file. updatePeriodMillis
is set to zero because I update the widget manually using the AlarmManager
:
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="250dp" android:minHeight="110dp" android:updatePeriodMillis="0"
android:initialLayout="@layout/app_widget"
android:widgetCategory="home_screen|keyguard"
android:initialKeyguardLayout="@layout/app_widget"
android:previewImage="@drawable/preview_image" />
Here are the relevant portions of my AppWidgetProvider
class:
public class MyAppWidget extends AppWidgetProvider {
private static PendingIntent service1 = null, service2 = null, update_service = null;
public static void nullifyService( String service_name ) {
if ( service_name.equals( "service1" ) ) {
service1 = null;
} else {
service2 = null;
}
}
public static void updateMyWidget(final Context context, AppWidgetManager appWidgetManager, int appWidgetId) {
/* ... code to update my widget ... */
/* ... this code is tested and I know it works ... */
// Instruct the widget manager to update the widget
appWidgetManager.updateAppWidget(appWidgetId, views);
}
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
// set alarms if necessary for the beginning and end of the next void
if ( null == update_service || ( null == service1 && null == service2 ) ) {
final AlarmManager m = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
// set alarms for the beginning and end of the upcoming/current event window
if ( null == service1 && null == service1 ) {
final Calendar s1_c = Calendar.getInstance(), // service1 alarm time
s2_c = Calendar.getInstance(); // service2 alarm time
final Intent s1_i = new Intent(context,MyService.class).putExtra( "serviceName", "service1" ),
s2_i = new Intent(context,MyService.class).putExtra( "serviceName", "service2" );
final long s1_millis, s2_millis;
// get the current VOC info
if ( db == null ) db = new MyDataBase(context);
alarm_info = db.getCurrentAlarms();
// alarm times for service1 and service2 are UNIX timestamps
// pulled from a sqlite database (tested and working fine)
s1_c.setTime(new Date(alarm_info.getLong(4) * 1000 ));
s2_c.setTime(new Date(alarm_info.getLong(1) * 1000 ));
// if it is still before the next service1 alarm time
// set an alarm for the exact time
if ( s1_c.getTimeInMillis() > Calendar.getInstance().getTimeInMillis() ) {
s1_c.set(Calendar.MILLISECOND, 0);
s1_c.set(Calendar.SECOND, 0);
service1 = PendingIntent.getService(context, 0, s1_i, PendingIntent.FLAG_ONE_SHOT );
s1_millis = SystemClock.elapsedRealtime() + s1_c.getTime().getTime() - System.currentTimeMillis();
m.setExact(AlarmManager.ELAPSED_REALTIME, s1_millis, service1);
}
// set the alarm for the service2 alarm time
s2_c.set(Calendar.MILLISECOND, 0);
s2_c.set(Calendar.SECOND, 0);
service2 = PendingIntent.getService(context, 1, s2_i, PendingIntent.FLAG_ONE_SHOT );
s2_millis = SystemClock.elapsedRealtime() + s2_c.getTime().getTime() - System.currentTimeMillis();
m.setExact(AlarmManager.ELAPSED_REALTIME, s2_millis, service2);
}
// set the widget to update every 15 minutes regardless
if ( null == update_service ) {
final Intent up_i = new Intent(context,MyService.class).putExtra( "serviceName", "update" );
update_service = PendingIntent.getService(context, 2, up_i, PendingIntent.FLAG_CANCEL_CURRENT);
m.setRepeating(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime(), 15 * 60 * 1000, update_service);
}
}
// There may be multiple widgets active, so update all of them
for( int appWidgetId : appWidgetIds) {
updateMyWidget(context, appWidgetManager, appWidgetId);
}
}
@Override
public void onDisabled(Context context) {
// Enter relevant functionality for when the last widget is disabled
final AlarmManager m = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
m.cancel(service1);
m.cancel(service2);
m.cancel(update_service);
}
}
And here are the relevant portions of my Service
class:
public class MyService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId){
// update all of the widgets
ComponentName cn = new ComponentName(this, MyAppWidget.class);
AppWidgetManager m = AppWidgetManager.getInstance(this);
for ( int awid : m.getAppWidgetIds(cn) ) {
MyAppWidget.updateMyWidget(this,m,awid);
}
String service_name = intent.getStringExtra( "serviceName" );
if ( "service1" == service_name || "service2" == service_name ) {
MyAppWidget.nullifyService(service_name);
}
return START_NOT_STICKY;
}
}
What I Know and What I've Tried
- The
update_service
works fine and updates the widget dependably every 15 minutes - The alarms for
service1
andservice2
are being set.
Here is a snippet from running adb shell dumpsys alarm
showing one of the repeating alarms (that fires every 15 minutes, or 900000ms):
ELAPSED #0: Alarm{41a9e1b8 type 3 com.mycompany.myappwidget}
type=3 whenElapsed=231265839 when=+11m52s295ms window=-1 repeatInterval=900000 count=0
operation=PendingIntent{41dbf158: PendingIntentRecord{41cf7920 com.mycompany.myappwidget startService}}
And here is another showing one of the alarms that is set to fire at an exact time:
ELAPSED #4: Alarm{433d1560 type 3 com.mycompany.myappwidget}
type=3 whenElapsed=90066209 when=+4h34m36s121ms window=0 repeatInterval=0 count=0
operation=PendingIntent{41f0ec28: PendingIntentRecord{41f1e690 com.mycompany.myappwidget startService}}
And here are the stats for the alarms that have already fired where d
and c
refer to the services that got started:
com.mycompany.myappwidget +1s857ms running, 0 wakeups:
+1s817ms 0 wakes 83 alarms: cmp={com.mycompany.myappwidget/com.mycompany.myappwidget.d}
+40ms 0 wakes 1 alarms: cmp={com.mycompany.myappwidget/com.mycompany.myappwidget.c}
As you can see, the alarms are being triggered, but the interface is not updating.
- Since the repeating alarm fires reliably every 15 minutes, I don't think that the OS is killing them based on the restriction of only updating widgets every 30 minutes
- I have tried creating a different
Service
class for each alarm in order to avoid the alarms canceling each other as is suggested by this post - I have used
Log
statements to confirm that theupdateMyWidget()
function is actually being called when each alarm is triggered. It appears that the function is called, but doesn't always update the widgets. - I have tried setting different unique
requestCode
s for eachPendingIntent
so that when a new alarm is set, it doesn't inadvertently cancel thePendingIntent
s set by other alarms
Questions and Ideas
Am I doing this wrong? On average, updates only seem to take about 200ms, so perhaps I should be using PendingIntent.getBroadcast()
and calling android.appwidget.action.APPWIDGET_UPDATE
instead of creating a Service
to handle the updates. This is driving me nuts, so any hints or clues that might point me in the right direction would be GREATLY appreciated.
UPDATE
@Karakuri's answer taught me how to differentiate PendingIntent
objects for the purposes of not having them overwrite each other when setting alarms. Here's how I fixed this code:
- I changed the
putExtra()
calls tosetAction()
and set the action as one of the predefined constants, e.g.Intent.ACTION_MAIN
, that seemed semantically similar to what the service was doing - I removed all of the services from the class-level scope, as it turns out it was unnecessary to keep them around in between calls to
onUpdate()
- I got rid of all of the conditional logic inside of
onUpdate()
- I went back to using the built-in android update mechanism, i.e. changed the
android:updatePeriodMillis
inapp_widget_info.xml
to1800000
(30 minutes) and got rid of the repeating alarm I'd set withsetRepeating()
- I discovered that there was a problem with the boundary conditions for my alarms that was preventing the UI from updating when I thought it should--I wouldn't have discovered this, though, if I hadn't started using
setAction()
Thank you very much!!!