I need help with Widget and PendingIntents
I've asked here a question about Task Killers and widgets stop working (SO Question) but now, I have reports of user that they don't use any Task Killer and the widgets didn't work after a while. I have a Nexus One and I don't have this problem.
I don't know if this is a problem of memory or something. Based on the API:
A PendingIntent itself is simply a reference to a token maintained by the system describing the original data used to retrieve it. This means that, even if its owning application's process is killed, the PendingIntent itself will remain usable from other processes that have been given it.
So, I don't know why widget stop working, if Android doesn't kill the PendingIntent by itself, what's the problem?
This is my manifest code:
<receiver android:name=".widget.InstantWidget" android:label="@string/app_name">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data android:name="android.appwidget.provider"
android:resource="@xml/widget_provider" />
</receiver>
And the widget code:
public class InstantWidget extends AppWidgetProvider {
public static ArrayList<Integer> alWidgetsId = new ArrayList<Integer>();
private static final String PREFS_NAME = "com.cremagames.instant.InstantWidget";
private static final String PREF_PREFIX_NOM = "nom_";
private static final String PREF_PREFIX_RAW = "raw_";
/**
* Esto se llama cuando se crea el widget. Metemos en las preferencias los valores de nombre y raw para tenerlos en proximos reboot.
* @param context
* @param appWidgetManager
* @param appWidgetId
* @param nombreSound
* @param rawSound
*/
static void updateAppWidget(Context context, AppWidgetManager appWidgetManager,
int appWidgetId, String nombreSound, int rawSound){
//Guardamos en las prefs los valores
SharedPreferences.Editor prefs = context.getSharedPreferences(PREFS_NAME, 0).edit();
prefs.putString(PREF_PREFIX_NOM + appWidgetId, nombreSound);
prefs.putInt(PREF_PREFIX_RAW + appWidgetId, rawSound);
prefs.commit();
//Actualizamos la interfaz
updateWidgetGrafico(context, appWidgetManager, appWidgetId, nombreSound, rawSound);
}
/**
* Actualiza la interfaz gráfica del widget (pone el nombre y crea el intent con el raw)
* @param context
* @param appWidgetManager
* @param appWidgetId
* @param nombreSound
* @param rawSound
*/
private static void updateWidgetGrafico(Context context, AppWidgetManager appWidgetManager,
int appWidgetId, String nombreSound, int rawSound){
RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget);
//Nombre del Button
remoteViews.setTextViewText(R.id.tvWidget, nombreSound);
//Creamos el PendingIntent para el onclik del boton
Intent active = new Intent(context, InstantWidget.class);
active.setAction(String.valueOf(appWidgetId));
active.putExtra("sonido", rawSound);
PendingIntent actionPendingIntent = PendingIntent.getBroadcast(context, 0, active, 0);
actionPendingIntent.cancel();
actionPendingIntent = PendingIntent.getBroadcast(context, 0, active, 0);
remoteViews.setOnClickPendingIntent(R.id.btWidget, actionPendingIntent);
appWidgetManager.updateAppWidget(appWidgetId, remoteViews);
}
public void onReceive(Context context, Intent 开发者_StackOverflow社区intent) {
final String action = intent.getAction();
//Esto se usa en la 1.5 para que se borre bien el widget
if (AppWidgetManager.ACTION_APPWIDGET_DELETED.equals(action)) {
final 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 {
//Listener de los botones
for(int i=0; i<alWidgetsId.size(); i++){
if (intent.getAction().equals(String.valueOf(alWidgetsId.get(i)))) {
int sonidoRaw = 0;
try {
sonidoRaw = intent.getIntExtra("sonido", 0);
} catch (NullPointerException e) {
}
MediaPlayer mp = MediaPlayer.create(context, sonidoRaw);
mp.start();
mp.setOnCompletionListener(completionListener);
}
}
super.onReceive(context, intent);
}
}
/** Al borrar el widget, borramos también las preferencias **/
public void onDeleted(Context context, int[] appWidgetIds) {
for(int i=0; i<appWidgetIds.length; i++){
//Recogemos las preferencias
SharedPreferences.Editor prefs = context.getSharedPreferences(PREFS_NAME, 0).edit();
prefs.remove(PREF_PREFIX_NOM + appWidgetIds[i]);
prefs.remove(PREF_PREFIX_RAW + appWidgetIds[i]);
prefs.commit();
}
super.onDeleted(context, appWidgetIds);
}
/**Este método se llama cada vez que se refresca un widget. En nuestro caso, al crearse y al reboot del telefono.
Al crearse lo único que hace es guardar el id en el arrayList
Al reboot, vienen varios ID así que los recorremos y guardamos todos y también recuperamos de las preferencias el nombre y el sonido*/
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
for(int i=0; i<appWidgetIds.length; i++){
//Metemos en el array los IDs de los widgets
alWidgetsId.add(appWidgetIds[i]);
//Recogemos las preferencias
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, 0);
String nomSound = prefs.getString(PREF_PREFIX_NOM + appWidgetIds[i], null);
int rawSound = prefs.getInt(PREF_PREFIX_RAW + appWidgetIds[i], 0);
//Si están creadas, actualizamos la interfaz
if(nomSound != null){
updateWidgetGrafico(context, appWidgetManager, appWidgetIds[i], nomSound, rawSound);
}
}
}
MediaPlayer.OnCompletionListener completionListener = new MediaPlayer.OnCompletionListener(){
public void onCompletion(MediaPlayer mp) {
if(mp != null){
mp.stop();
mp.release();
mp = null;
}
}
};
}
Sorry for the comments in Spanish.
I have the possibility to put differents widgets on the desktop, that's why I use the widgetId as the "unique id" for the PendingIntent.
Any ideas please? The 70% of the functionality of my app is the widgets, and it isn't working for some users :(
Thanks in advance and sorry for my English.
I think you need to determine exactly what the user means by "stops working". Does it force close (crash) or just become unresponsive? Collect any information about their phone that you can, e.g. what handset they have, what version of Android they are running (look it up if they don't know), etc. Also, make sure you specifically ask them if they are using custom firmware like CyanogenMod.
Make your app write some logging information to the SD card, that way you can ask the user to email you the log when it happens again, and hopefully shed some light on what the last task was before the app started misbehaving.
Update
It appears you are actually playing the music from within the appwidget, which will force you to adhere to the lifecycle of the widget on the screen. In particular the fact the widget is no longer a priority process once the Home screen is no longer in focus, and the fail-fast behaviour of a BroadcastReceiver:
Note: Because the AppWidgetProvider is a BroadcastReceiver, your process is not guaranteed to keep running after the callback methods return (see Application Fundamentals > Broadcast Receiver Lifecycle for more information). If your App Widget setup process can take several seconds (perhaps while performing web requests) and you require that your process continues, consider starting a Service in the onUpdated() method.
My suggestion would be to move the music playback code out of the appwidget and into a Service, you only need to start the service when playback starts, and you should tear it down when playback ends. This will provide you with a background process for playing music without being affected by the lifecycle of your appwidget. An example of this pattern is the Last.FM appwidget (provided with the app).
精彩评论