Reputation: 7945
My AppWidget
isn't updating when it should. I can't figure out why.
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;
}
}
update_service
works fine and updates the widget dependably every 15 minutesservice1
and service2
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.
Service
class for each alarm in order to avoid the alarms canceling each other as is suggested by this postLog
statements to confirm that the updateMyWidget()
function is actually being called when each alarm is triggered. It appears that the function is called, but doesn't always update the widgets.requestCode
s for each PendingIntent
so that when a new alarm is set, it doesn't inadvertently cancel the PendingIntent
s set by other alarmsAm 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.
@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:
putExtra()
calls to setAction()
and set the action as one of the predefined constants, e.g. Intent.ACTION_MAIN
, that seemed semantically similar to what the service was doingonUpdate()
onUpdate()
android:updatePeriodMillis
in app_widget_info.xml
to 1800000
(30 minutes) and got rid of the repeating alarm I'd set with setRepeating()
setAction()
Thank you very much!!!
Upvotes: 0
Views: 1566
Reputation: 38585
Here are my suggestions, hopefully somewhere in here is something that fixes your issue.
Don't use ==
to compare strings in your service (or anywhere). Use .equals()
with named constants.
Your PendingIntent
s only differ by extras. When you give the same Intent
to PendingIntent
, it only keeps one of them. For the purposes of comparing Intent
s, extras are not considered (see the documentation of PendingIntent
about this). Instead of using the extras, I would give each Intent
a different action and use intent.getAction()
in your Service.
Don't rely on static variables for keeping state. Your process can be shut down any time and you will lose that data. Your AppWidget is running in a different process, so unless you have something else running, Android may decide to clear your process. I would use some kind of persistent storage, probably SharedPreferences
if it's just for a handful of keys.
Upvotes: 3