我需要 Widget 和 PendingIntents 方面的帮助

Posted

技术标签:

【中文标题】我需要 Widget 和 PendingIntents 方面的帮助【英文标题】:I need help with Widget and PendingIntents 【发布时间】:2011-02-12 06:45:39 【问题描述】:

我在这里问过一个关于任务杀手和小部件停止工作的问题 (SO Question),但现在,我有用户报告说他们没有使用任何任务杀手并且小部件在一段时间后没有工作。我有一个 Nexus One,我没有这个问题。

不知道是内存问题还是什么。基于 API:

PendingIntent 本身只是一个 引用由 描述原始数据的系统 用来取回它。这意味着, 即使它拥有的应用程序的 进程被杀死,PendingIntent 本身将保持可用于其他 已赋予它的进程。

所以,我不知道为什么小部件停止工作,如果 android 不自行杀死 PendingIntent,有什么问题?

这是我的清单代码:

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

还有小部件代码:

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;
            
        

    ;


对不起西班牙语的 cmets。

我可以将不同的小部件放在桌面上,这就是为什么我使用 widgetId 作为 PendingIntent 的“唯一 ID”。

有什么想法吗?我的应用程序 70% 的功能是小部件,它不适用于某些用户:(

提前感谢我的英语。

【问题讨论】:

【参考方案1】:

我认为您需要确切地确定用户所说的“停止工作”是什么意思。它是强制关闭(崩溃)还是变得无响应?尽可能收集有关他们手机的任何信息,例如他们有什么手机,他们正在运行什么版本的 Android(如果他们不知道,请查看)等。另外,请确保您特别询问他们是否使用了 CyanogenMod 等自定义固件。

让您的应用程序将一些日志记录信息写入 SD 卡,这样您就可以要求用户在再次发生日志时通过电子邮件向您发送日志,并希望能够了解应用程序开始出现异常之前的最后一项任务。


更新

看起来您实际上是在应用小部件中播放音乐,这将迫使您遵守屏幕上小部件的生命周期。特别是一旦主屏幕不再是焦点,小部件就不再是优先进程,fail-fast behaviour of a BroadcastReceiver:

注意:因为 AppWidgetProvider 是 一个广播接收器,你的过程是 不保证之后继续运行 回调方法返回(参见 应用基础 > 广播 接收器生命周期更多 信息)。如果您的 App Widget 设置 过程可能需要几秒钟 (也许在执行网络 请求)并且您要求您的 过程继续,考虑开始一个 onUpdated() 方法中的服务。

我的建议是将音乐播放代码移出appwidget并放入Service,您只需要在播放开始时启动服务,并且在播放结束时将其删除。这将为您提供一个播放音乐的后台进程,而不受应用小部件生命周期的影响。 Last.FM appwidget(随应用提供)就是这种模式的一个例子。

【讨论】:

感谢您的评论。小部件只是停止再现声音,不显示任何错误或 fc。设备种类繁多,Android 版本也很丰富(从 1.5 到 2.1)。我猜是 Android 操作系统本身的东西...... 也许,尽管其他应用程序已经能够在 Android 上可靠地产生声音 - 例如。股票音乐播放器和 Last.fm。我已经用一些进一步的建议修改了我的解决方案......

以上是关于我需要 Widget 和 PendingIntents 方面的帮助的主要内容,如果未能解决你的问题,请参考以下文章

需要一个“Widget”类型的值,但得到一个“Null”类型的值

我需要在android.widget.datepicker中自定义月份名称

yii2-widget、Select2 和 taggest 支持

将 Kivy Widget 传递给另一个类

为啥 RangeSlider 在 QtQuick 中可用,而不是作为标准 Widget

QT+常见控件+tab Widget 和Stacked Widget