Update AppWidget Periodically from a service
This is what I want from my AppWidget:
- configuration activity comes up, when widget is added to the screen // good so far
- after configuration is saved, a service is started that updates the widget // good so far
- schedule an alarm periodically to run the service that updates the widget. // having troubles here
This is seriously giving me grey hair already, and I don't know what to do anymore. How do you set the update rate for an AppWidget from a service? I can update the widget from the service, but when I try to set the alarm, it does not get to the onReceive()
method on the AppWidget.
Here is the code for the service update:
Intent updateWidget = new Intent();
updateWidget.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
updateWidget.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[]{appWidgetId});
Uri data = Uri.withAppendedPath(Uri.parse(WeatWidgetProvider.URI_SCHEME +
"://widget/id/"), String.valueOf(appWidgetId));
updateWidget.setData(data);
PendingIntent updatePend = PendingIntent.getBroadcast(this, 0, updateWidget, PendingIntent.FLAG_UPDATE_CURRENT);
AlarmManager alarm = (AlarmManager)getSystemService(ALARM_SERVICE);
alarm.setRepeating(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime()+ updateRate, updateRate, updatePend);
And in the onreceive()
of WidgetProviderClass:
public void onReceive(Context context, Intent intent){
开发者_开发问答super.onReceive(context, intent);
Log.d("OnReceive", "OnReceive called");
String action = intent.getAction();
Log.d("Action", "OnReceive:Action: " + action);
if(AppWidgetManager.ACTION_APPWIDGET_DELETED.equals(action)){
int appwidgetId = intent.getExtras().getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
if(appwidgetId != AppWidgetManager.INVALID_APPWIDGET_ID){
this.onDeleted(context, new int[] {appwidgetId});
}
} else if(AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)){
if(!URI_SCHEME.equals(intent.getScheme())){
final int[] appWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
for(int appWidgetId:appWidgetIds){
SharedPreferences prefs = context.getSharedPreferences(WeatForecastConfigure.WEATHER_PREF_NAME, Context.MODE_PRIVATE);
update = prefs.getInt(WeatForecastConfigure.REFRESH_UPDATE, -1);
System.out.println(update);
if(update != -1){
Intent widgetUpdate = new Intent();
widgetUpdate.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
widgetUpdate.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] { appWidgetId });
// make this pending intent unique by adding a scheme to it
widgetUpdate.setData(Uri.withAppendedPath(Uri.parse(WeatWidgetProvider.URI_SCHEME +
"://widget/id/"), String.valueOf(appWidgetId)));
PendingIntent newPending = PendingIntent.getBroadcast(context.getApplicationContext(), 0, widgetUpdate, PendingIntent.FLAG_UPDATE_CURRENT);
// schedule the updating
AlarmManager alarms = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
alarms.setRepeating(AlarmManager.ELAPSED_REALTIME,
SystemClock.elapsedRealtime(), WeatForecastConfigure.convertToMillis(update), newPending);
}
}
}
super.onReceive(context, intent);
}else{
super.onReceive(context, intent);
}
}
For onUpdate()
commented lines are deliberate.
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds){
Log.d("OnUpdate", "OnUpdate called");
for (int appWidgetId : appWidgetIds) {
// context.startService(new Intent(context,WeatService.class));
}
//super.onUpdate(context, appWidgetManager, appWidgetIds);
}
Please help. I have no idea how to set the alarm to update. Whether I do it from the configuration class or from a service, it does not work. Thanks.
I think you need to use a standard URI scheme in your intent like this:
intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
I wrote all this, and realized it will probably just confused you more, oh well.
Here is the skeleton of how I update my widgets, it is loosely based on this code http://code.google.com/p/android-sky/source/browse/#svn%2Ftrunk%2FSky (particularly the UpdateService class.
The main difference is my code
- Starts the service to perform the update
- Sets the Alarm (a broadcast pending intent) to alert the widget about the next update
- Updates the widget and stops the service
- Receives update broadcast (in the providers onReceive) and starts the service again
You seem to be trying to tell the AppWidgetProvider to kickoff through the onUpdate, I'm not sure if this is even possible?
Here is how I do it in semi pseudo code:
UpdateService class:
//based on (an old) example http://code.google.com/p/android-sky/source/browse/#svn%2Ftrunk%2FSky however other methods I have tried have not been as reliable as this
public void run() {
//set the alarm for the next update
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
long millis = System.currentTimeMillis();
//one alarm to update them all, if you wanted to you could
//add an extra appWidgetId then you might need to encode the intent
//however so it is a unique PendingIntent
Intent updateIntent = new Intent(ACTION_UPDATE_ALL); //ACTION_UPDATE_ALL should be a constant somewhere that can be used to resolve this action com.packagename.ACTION_UPDATE_ALL
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, updateIntent, 0);
// Schedule alarm, and force the device awake for this update
alarmManager.set(AlarmManager.RTC, millis + updateFrequency * DateUtils.MINUTE_IN_MILLIS, pendingIntent);
Log.d(TAG, "Next update should be at: " + DateFormat.format("h:mm:ss", millis + updateFrequency * DateUtils.MINUTE_IN_MILLIS));
while (hasMoreUpdates()) {
int appWidgetId = getNextUpdate();
if (!(appWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID)) {
Uri appWidgetUri = ContentUris.withAppendedId(Agenda.getContentUri(context), appWidgetId);
AppWidgetProviderInfo info = manager.getAppWidgetInfo(appWidgetId);
String providerName = info.provider.getClassName();
//if the provider matches one of my widget classes, call the update method
if (providerName.equals(Widget_4_1.class.getName())) {
remoteViews = Widget_4_1.buildUpdate(context, appWidgetUri, dateRows);
}
//perform more actions such as sending remoteViews to the AppWidgetManager
}
}
}
Widget_4_1 class:
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
// If no specific widgets requested, collect list of all
if (appWidgetIds == null) {
appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(
context, AbstractWidget.class));
}
UpdateService.requestUpdate(appWidgetIds);
Intent i = new Intent();
i.setComponent(new ComponentName(context, UpdateService.class));
context.startService(i);
}
//called from service
public static RemoteViews buildUpdate(Context context, Uri appWidgetUri) {
int appWidgetId = (int) ContentUris.parseId(appWidgetUri);
RemoveViews remoteViews = someMethodToBuildView(appWidgetId );
//return view to service
return remoteViews;
}
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
//when the alarm triggers, just update all your widgets, why handle them separately?
if (action.equals(ACTION_UPDATE_ALL)) {
AppWidgetManager manager = AppWidgetManager.getInstance(context);
int[] appWidgetIds = manager.getAppWidgetIds(new ComponentName(context, getClass()));
if(appWidgetIds.length>0){
UpdateService.requestUpdate(appWidgetIds);
Intent updateIntent = new Intent(context, UpdateService.class);
updateIntent.setAction(action);
context.startService(updateIntent);
}
}
//else you could catch a specific action for updating a single widget and
//tell your update service to do it manually
}
AndroidManifest.xml:
<application>
...
<receiver android:name="com.mypackage.Widget_4_1"
android:label="@string/agenda_widget_name_4_1">
<intent-filter>
<!-- IMPORTANT, needs to match the ACTION_UPDATE_ALL string (e.g. a constant somewhere) -->
<action android:name="com.packagename.UPDATE_ALL" />
</intent-filter>
<meta-data android:name="android.appwidget.provider"
android:resource="@xml/widget_4_1" />
</receiver>
...
</application>
Hope I haven't confused you.
As this code is copy/pasted from various parts of my project excuse typo's etc, I will fix them as they are pointed out.
精彩评论