使用AlarmManager更新应用小部件

11

我正在尝试以比1.6文档规定的30分钟更频繁的方式更新一个小部件。在阅读了SO中几乎每一篇文章,开发者文档以及其他各种来源后,我认为自己已经理解并可以实现它。但是,我尝试了却失败了。此后,我又在更多的论坛和解决方案中搜寻,但似乎仍然无法成功更新。

我有一个Update类,它设置了AlarmManager:

public class Update extends Service{

    @Override
    public void onStart(Intent intent, int startId) {
          String currentTemp = Battery.outputTemp;
          String currentLevel = Battery.outputLevel;
          String currentCard = Battery.outputCard;
          String currentInternal = Battery.memory;
          String currentRam = String.valueOf(Battery.outputRam).substring(0, 3) + "MB";

          // Change the text in the widget
          RemoteViews updateViews = new RemoteViews( 
          this.getPackageName(), R.layout.main);
          //update temp
          updateViews.setTextViewText(R.id.batteryTemp, currentTemp); 
          //update %
          updateViews.setTextViewText(R.id.batteryLevel, currentLevel);     
          //update level
          updateViews.setTextViewText(R.id.sdCard, currentCard);
          //update internal memory
          updateViews.setTextViewText(R.id.internal, currentInternal);
          //update ram
          updateViews.setTextViewText(R.id.ram, currentRam);

          ComponentName thisWidget = new ComponentName(this, Widget.class);
          AppWidgetManager manager = AppWidgetManager.getInstance(this);
          manager.updateAppWidget(thisWidget, updateViews);

    }
    @Override
    public IBinder onBind(Intent intent) {
        // no need to bind
        return null;
    }

}

这导致我的小部件类中的onReceive方法频繁触发(我有一个toast来查看它何时触发),但它不携带任何意图(这个toast应该在接收到它们时显示,但是它是空的)。

我无法弄清楚原因(我是相对较新的开发者-缓慢地进行了2个月的安卓开发),并且感谢您提供的任何见解。

这是我的小部件类,供参考:

    public class Widget extends AppWidgetProvider {


    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager,
            int[] appWidgetIds) {

        AlarmManager alarmManager;
        Intent intent = new Intent(context, Update.class);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0,
                intent, PendingIntent.FLAG_UPDATE_CURRENT);
        alarmManager = (AlarmManager) context
                .getSystemService(Context.ALARM_SERVICE);
        Calendar cal = Calendar.getInstance();
        cal.setTimeInMillis(System.currentTimeMillis());
        cal.add(Calendar.SECOND, 10);
        alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, cal
                .getTimeInMillis(), 5 * 1000, pendingIntent);

        String currentTemp = Battery.outputTemp;
        String currentLevel = Battery.outputLevel;
        String currentCard = Battery.outputCard;
        String currentInternal = Battery.memory;
        String currentRam = String.valueOf(Battery.outputRam).substring(0, 3)
                + "MB";

        // Change the text in the widget
        RemoteViews updateViews = new RemoteViews(context.getPackageName(),
                R.layout.main);
        // update temp
        updateViews.setTextViewText(R.id.batteryTemp, currentTemp);
        appWidgetManager.updateAppWidget(appWidgetIds, updateViews);
        // update %
        updateViews.setTextViewText(R.id.batteryLevel, currentLevel);
        appWidgetManager.updateAppWidget(appWidgetIds, updateViews);
        // update level
        updateViews.setTextViewText(R.id.sdCard, currentCard);
        appWidgetManager.updateAppWidget(appWidgetIds, updateViews);
        // update internal memory
        updateViews.setTextViewText(R.id.internal, currentInternal);
        appWidgetManager.updateAppWidget(appWidgetIds, updateViews);
        // update ram
        updateViews.setTextViewText(R.id.ram, currentRam);
        appWidgetManager.updateAppWidget(appWidgetIds, updateViews);
        super.onUpdate(context, appWidgetManager, appWidgetIds);

    }

    public void onReceive(Context context, Intent intent) {

        super.onReceive(context, intent);
        Toast
                .makeText(context, intent.getAction() + context,
                        Toast.LENGTH_LONG).show();
        Bundle extras = intent.getExtras();
        if (extras != null) {
            AppWidgetManager appWidgetManager = AppWidgetManager
                    .getInstance(context);
            ComponentName thisAppWidget = new ComponentName(context
                    .getPackageName(), Widget.class.getName());
            int[] appWidgetIds = appWidgetManager
                    .getAppWidgetIds(thisAppWidget);

            onUpdate(context, appWidgetManager, appWidgetIds);
        }

    }
}

3
这是我几年前的问题,涉及我第一个安卓程序(也是我的第一个编程项目)。看着以前的代码,让我感到非常难堪。 - doydoy
4个回答

28

这是我的解决方案,如何自动更新小部件的频率高于30分钟。我使用了AlarmManager。在使用AlarmManager刷新应用程序小部件之前,请确保您知道您正在做什么,因为这种技术可能会耗尽设备的电池。

在Android文档中阅读有关小部件更新的更多信息,特别是有关updatePeriodMillis参数的内容。

这是我Manifest.xml的一部分。我定义了自定义操作AUTO_UPDATE。

<receiver android:name=".appwidget.AppWidget" >
    <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
    </intent-filter>
    <intent-filter>
        <action android:name="AUTO_UPDATE" />
    </intent-filter>
    <meta-data android:name="android.appwidget.provider" android:resource="@xml/appwidget_info" />
</receiver>

这是我的AppWidget.java的一部分。在onReceive方法中,我处理自定义操作AUTO_UPDATE。在onEnabled和onDisabled方法中,我启动/停止闹钟。

public class AppWidget extends AppWidgetProvider
{
    public static final String ACTION_AUTO_UPDATE = "AUTO_UPDATE";

    @Override
    public void onReceive(Context context, Intent intent)
    {
        super.onReceive(context, intent);

        if(intent.getAction().equals(ACTION_AUTO_UPDATE))
        {
            // DO SOMETHING
        }

        ...
    }

    @Override
    public void onEnabled(Context context)
    {
        // start alarm
        AppWidgetAlarm appWidgetAlarm = new AppWidgetAlarm(context.getApplicationContext());
        appWidgetAlarm.startAlarm();
    }

    @Override
    public void onDisabled(Context context)
    {
        // stop alarm only if all widgets have been disabled
        AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
        ComponentName thisAppWidgetComponentName = new ComponentName(context.getPackageName(),getClass().getName());
        int[] appWidgetIds = appWidgetManager.getAppWidgetIds(thisAppWidgetComponentName);
        if (appWidgetIds.length == 0) {
            // stop alarm
            AppWidgetAlarm appWidgetAlarm = new AppWidgetAlarm(context.getApplicationContext());
            appWidgetAlarm.stopAlarm();
    }

    }

    ...
}

这是我的AppWidgetAlarm.java文件,用于启动/停止闹钟。闹钟管理器会向AppWidget发送广播。

public class AppWidgetAlarm
{
    private final int ALARM_ID = 0;
    private final int INTERVAL_MILLIS = 10000;

    private Context mContext;


    public AppWidgetAlarm(Context context)
    {
        mContext = context;
    }


    public void startAlarm()
    {
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.MILLISECOND, INTERVAL_MILLIS);

        Intent alarmIntent=new Intent(mContext, AppWidget.class);
        alarmIntent.setAction(AppWidget.ACTION_AUTO_UPDATE);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, ALARM_ID, alarmIntent, PendingIntent.FLAG_CANCEL_CURRENT);

        AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
        // RTC does not wake the device up
        alarmManager.setRepeating(AlarmManager.RTC, calendar.getTimeInMillis(), INTERVAL_MILLIS, pendingIntent);
    }


    public void stopAlarm()
    {
        Intent alarmIntent = new Intent(AppWidget.ACTION_AUTO_UPDATE);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, ALARM_ID, alarmIntent, PendingIntent.FLAG_CANCEL_CURRENT);

        AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
        alarmManager.cancel(pendingIntent);
    }
}

你好 - 我尝试了你的闹钟例子,但是 onReceive() 从来没有接收到 ACTION_AUTO_UPDATE 广播。有什么想法吗? - wufoo
仍然无法工作。如果您不介意的话,请查看我在pastebin上发布的代码(http://pastebin.com/mr4Gih0i)。----编辑:刚刚注意到代码有问题。我会修复它,但我忘记粘贴appWidgetAlarm类了 :/ - wufoo
1
好的-不用了。 我原来在应用程序部分中有AUTO_UPDATE的意图过滤器。 将其移动到接收器部分后,我的电池现在正确地耗尽;) - wufoo
1
简单基础!谢谢 :) - narb
还有一件事,我在 Oreo 上遇到了这个问题:“W/BroadcastQueue: 后台执行不允许:接收 Intent....”https://dev59.com/ZKzka4cB1Zd3GeqP5DjK#50955019。 - desperateCoder
显示剩余2条评论

14
我有一个Update类,用于设置AlarmManager:
不是的,在代码片段中没有任何出现AlarmManager。
在第二个代码片段中,您确实引用了AlarmManager。其中存在以下问题:
- 每次应用程序小部件更新时都会设置新的重复闹钟。 - 将警报频率设置为5秒是完全疯狂的。 - 在一些司法管辖区,将5秒钟的频率设置为_WAKEUP警报可能会导致您被逮捕。 - 您有一个无意义的onReceive()方法,即使忽略临时Toast也是如此。 - 您假设Intent中会有一个操作字符串,但在创建放入闹钟PendingIntent中的Intent时未指定操作字符串。 - 您的代码引用了Battery类上的静态数据成员(我认为),但如果闹钟的频率合理,这些成员很可能全部为空/ null。

感谢回复。首先,我只是为了测试而使用了5秒频率,当我最终让它正常工作后,将会减少到我想要的每1分钟一次的频率。您提到使用“_WAKEUP”闹钟不合适。能否详细说明一下原因?为什么不是这样,可能有更好的选择吗? - doydoy
2
@Ben Bullock:_WAKEUP 闹钟会唤醒手机。即使在一分钟的轮询周期下,也会严重地消耗用户的电池。 - CommonsWare
你的最后一点比我想象中更让我感到不安。这个行为凸显出了如此多的糟糕实践、无知和普遍的糟糕开发,令人深思。 - doydoy
2
@mnVoh:“即使闹钟没有唤醒,它也是这样吗?” - 考虑到Google不再支持从Android 5.1开始的次分钟轮询周期,是的。“我应该让他/她选择低间隔,比如5秒甚至2秒吗?” - 以前,我会说“当然可以,也许还要加上一些额外的警告对话框之类的东西”。现在,我不会了,因为在5.1上看起来你的应用程序好像出了问题。 - CommonsWare
@CommonsWare 所以基本上,如果我想坚持使用AlarmManager,我必须将我的单位从秒改为分钟。真的很好知道,谢谢。 - Milad.Nozari
显示剩余2条评论

4

谢谢您提供的示例 - 我也遇到了使用较新版本的Android时出现问题。

这篇帖子对我有用: widget case that doesn't work(请看Larry Schiefer的答案)。

所以,从上面的代码中替换为:

Intent alarmIntent = new Intent(AppWidget.ACTION_AUTO_UPDATE);
PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, ALARM_ID, alarmIntent, PendingIntent.FLAG_CANCEL_CURRENT);

通过这个引用:

Intent alarmIntent=new Intent(mContext, MyWidget.class);
alarmIntent.setAction(AppWidget.ACTION_AUTO_UPDATE);
PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, ALARM_ID, alarmIntent, PendingIntent.FLAG_CANCEL_CURRENT);

完成了任务。

2

这是petrnohejl方案的稍作修改版本,这个版本在我的项目中起作用。(使用kotlin编写):

这是Manifest.xml文件的一部分。我添加了以下操作:AUTO_UPDATE、APPWIDGET_UPDATE、APPWIDGET_ENABLED、APWIDGET_DISABLED。

                <receiver android:name=".AppWidget">
                    <intent-filter>
                        <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
                        <action android:name="android.appwidget.action.APPWIDGET_ENABLED" />
                        <action android:name="android.appwidget.action.APPWIDGET_DISABLED"/>
                    </intent-filter>
                    <intent-filter>
                        <action android:name="ACTION_AUTO_UPDATE" />
                    </intent-filter>

                    <meta-data
                            android:name="android.appwidget.provider"
                            android:resource="@xml/appwidget_info"/>
                </receiver>

这是AppWidget.kt的一部分。在这里,我实现了onUpdate()、onEnabled()、onDisabled()和onReceive()函数。

class AppWidget: AppWidgetProvider() {

    override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {

        // There may be multiple widgets active, so update all of them
        for (appWidgetId in appWidgetIds) {
            updateAppWidget(context, appWidgetManager, appWidgetId)
        }
    }

    override fun onEnabled(context: Context) { // Enter relevant functionality for when the first widget is created

        // start alarm
        val appWidgetAlarm = AppWidgetAlarm(context.applicationContext)
        appWidgetAlarm.startAlarm()
    }

    override fun onDisabled(context: Context) { // Enter relevant functionality for when the last widget is disabled

        // stop alarm only if all widgets have been disabled
        val appWidgetManager = AppWidgetManager.getInstance(context)

        if (appWidgetIds.isEmpty()) {
            // stop alarm
            val appWidgetAlarm = AppWidgetAlarm(context.getApplicationContext())
            appWidgetAlarm.stopAlarm()
        }
    }

    companion object {

        val ACTION_AUTO_UPDATE = "AUTO_UPDATE"

        fun updateAppWidget(context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int) {

        val widgetText = Random.nextInt(0, 100).toString()

            // Construct the RemoteViews object
            val views = RemoteViews(context.packageName, R.layout.appwidget)
            views.setTextViewText(R.id.widget_text, widgetText)

            // Instruct the widget manager to update the widget
            appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.widget_text)
            appWidgetManager.updateAppWidget(appWidgetId, views)
        }
    }

    override fun onReceive(context: Context?, intent: Intent?) {
        super.onReceive(context, intent)

        // Do something

        /*if (intent!!.action == ACTION_AUTO_UPDATE) {
            // DO SOMETHING
        }*/
    }
}

这是AppWidgetAlarm.kt文件。这里是我的主要修改。虽然答案没有帮助我,但它确实起作用了。我在这里设置了一个重复的闹钟。(https://developer.android.com/training/scheduling/alarms

class AppWidgetAlarm(private val context: Context?) {
        private val ALARM_ID = 0
        private val INTERVAL_MILLIS : Long = 10000

    fun startAlarm() {
        val calendar: Calendar = Calendar.getInstance()
        calendar.add(Calendar.MILLISECOND, INTERVAL_MILLIS.toInt())        

        val alarmIntent = Intent(context, AppWidget::class.java).let { intent ->
            //intent.action = AppWidget.ACTION_AUTO_UPDATE
            PendingIntent.getBroadcast(context, 0, intent, 0)
        }
        with(context!!.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
           setRepeating(AlarmManager.RTC,calendar.timeInMillis, INTERVAL_MILLIS ,alarmIntent)
        }
    }

    fun stopAlarm() {
        val alarmIntent = Intent(AppWidget.ACTION_AUTO_UPDATE)
        val pendingIntent = PendingIntent.getBroadcast(context, ALARM_ID, alarmIntent, PendingIntent.FLAG_CANCEL_CURRENT)
        val alarmManager = context!!.getSystemService(Context.ALARM_SERVICE) as AlarmManager
        alarmManager.cancel(pendingIntent)
    }
}

为什么你的 intent.action = ... 被注释掉了? - Moth

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接