views:

258

answers:

1

Hi,

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 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.

+1  A: 

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).

seanhodges
Thanks for the comment. The widget simply stop to reproduce sound, don't show any errors or fc.The variety of devices is huge and the Android version too (from 1.5 to 2.1).I guess is something of the Android OS itself...
YaW
Perhaps, although other apps have been able to produce sound on Android reliably - e.g. the stock music player and Last.fm. I've amended my solution with some further suggestions...
seanhodges