午夜更新安卓小部件

8

如何在每天午夜更新 Android 小部件?以下解决方案是通过查看 stack overflow 上关于此主题的其他问题形成的。

1)在扩展 AppWidgetProvider 的活动中创建小部件时调用 schedule(context)。

    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        final int N = appWidgetIds.length;
        schedule(context);
    super.onUpdate(context, appWidgetManager, appWidgetIds);

}

2) Schedule方法应该设置一个计时器和闹钟,以便在每天午夜时更新小部件。然而,这并没有起作用。

protected void schedule(Context context) {
    final Intent i = new Intent(context, CalendarWidget.class);
    service = PendingIntent.getService(context, 0, i, PendingIntent.FLAG_CANCEL_CURRENT);

    Calendar calendar = Calendar.getInstance();
    calendar.setTimeInMillis(System.currentTimeMillis());
    calendar.set(Calendar.SECOND, 0);
    calendar.set(Calendar.MINUTE, 0);
    calendar.set(Calendar.HOUR, 0);
    calendar.set(Calendar.AM_PM, Calendar.AM);
    calendar.add(Calendar.DAY_OF_MONTH, 1); 

    AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);

//This doesn't work
       // alarmManager.set(AlarmManager.RTC, calendar.getTimeInMillis(), service);
//This doesn't work either.
        alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), AlarmManager.INTERVAL_DAY, service);
    }

这个解决方案并不起作用,而且很难测试,因为只能在每天午夜进行测试;因此,请提供帮助。我会将奖金授予第一个使它起作用的人。


为什么你设置了两次AlarmManager?删除其中一个,只保留setRepeating方法即可。 - Opiatefuchs
我对在日历上添加一天还有点不太清楚。为日历制作一个日志并使用calendar.getTime()检查时间,看看结果如何。也许会得到错误的日期。 - Opiatefuchs
@coolcool1994 不确定你是否还需要这个问题的答案。如果是,请先回答我的第一个问题:您在清单中是否具有唤醒锁定权限? - Dave
我不确定为什么你需要唤醒锁定权限。我不需要屏幕保持开启。我只是希望小部件在每天午夜更新 - 在接收或更新时被调用。 - coolcool1994
你能添加你的CalendarWidget代码吗?另外,请检查一下你的项目结构,确保两个类在同一个包中。 - Jofre Mateu
显示剩余4条评论
2个回答

10

denvercoder9的答案可行(某种程度上),但是可以简化。一个AppWidgetProvider已经是一个BroadcastReceiver,因此我们实际上可以将意图指回AppWidgetProvider,并覆盖onReceive来处理它。

在您扩展AppWidgetProvider的类中:

private static final String ACTION_SCHEDULED_UPDATE = "your.package.name.SCHEDULED_UPDATE";

@Override
public void onReceive(Context context, Intent intent) {
    if (intent.getAction().equals(ACTION_SCHEDULED_UPDATE)) {
        AppWidgetManager manager = AppWidgetManager.getInstance(context);
        int[] ids = manager.getAppWidgetIds(new ComponentName(context, AppWidget.class));
        onUpdate(context, manager, ids);
    }

    super.onReceive(context, intent);
}

@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
    // Do your updates...

    _scheduleNextUpdate(context);
}

private static void _scheduleNextUpdate(Context context) {
    AlarmManager alarmManager =
            (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
    // Substitute AppWidget for whatever you named your AppWidgetProvider subclass
    Intent intent = new Intent(context, AppWidget.class);
    intent.setAction(ACTION_SCHEDULED_UPDATE);
    PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0);

    // Get a calendar instance for midnight tomorrow.
    Calendar midnight = Calendar.getInstance();
    midnight.set(Calendar.HOUR_OF_DAY, 0);
    midnight.set(Calendar.MINUTE, 0);
    // Schedule one second after midnight, to be sure we are in the right day next time this 
    // method is called.  Otherwise, we risk calling onUpdate multiple times within a few 
    // milliseconds
    midnight.set(Calendar.SECOND, 1);
    midnight.set(Calendar.MILLISECOND, 0);
    midnight.add(Calendar.DAY_OF_YEAR, 1);

    // For API 19 and later, set may fire the intent a little later to save battery,
    // setExact ensures the intent goes off exactly at midnight.
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
        alarmManager.set(AlarmManager.RTC_WAKEUP, midnight.getTimeInMillis(), pendingIntent);
    } else {
        alarmManager.setExact(AlarmManager.RTC_WAKEUP, midnight.getTimeInMillis(), pendingIntent);
    }
}

我们需要一个自定义的意图操作,因为即使在AppWidgetManager.EXTRA_APPWIDGET_IDS下传递了完整的ids数组作为额外参数,AppWidgetManager.ACTION_APPWIDGET_UPDATE也不会更新所有小部件,它只会更新第一个创建的小部件。 这里有一个我正在开发的项目,实现了类似于此的计划小部件更新。

5
我曾经创建过一个日期和时间小部件。下面是实现方式:
在 AppWidgetProvider 类中:
private boolean first_run = true;
public AlarmManager am;
public PendingIntent pi;

@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
    first_run = context.getSharedPreferences("mywidet", MODE_PRIVATE).getBoolean("first_run", true);
    if(first_run) {
        am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
        Intent intent = new Intent(context, AlarmManagerBroadcastReceiver.class);
        pi = PendingIntent.getBroadcast(context, 0, intent, 0);
        long current = System.currentTimeMillis();

        LocalTime localTime = LocalTime.now();
        long triggerOffset = (24*60*60 - localTime.getHour()*60*60 - localTime.getMinute()*60 - localTime.getSecond())*1000;
        am.setRepeating(AlarmManager.RTC_WAKEUP, current + triggerOffset, 86400000, pi);
        SharedPreferences.Editor editor = context.getSharedPreferences("mywidet", MODE_PRIVATE).edit();
        editor.putBoolean("first_run", false);
        editor.apply();
    }
    for (int appWidgetId : appWidgetIds) {
        appWidgetManager.updateAppWidget(appWidgetId, remoteView);
    }
}

在AlarmManagerBroadcasrReceiver类中:
@Override
public void onReceive(Context context, Intent intent) {
    PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
    PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "YOUR TAG");
    //Acquire the lock
    wl.acquire();

    Log.d("widget", "in alarm manager. static method will come next");

    ComponentName thisWidget = new ComponentName(context, NewAppWidget.class);
    AppWidgetManager manager = AppWidgetManager.getInstance(context);
    manager.updateAppWidget(thisWidget, remoteViews);
    //Release the lock
    wl.release();
}

编辑1

从API 19开始,所有setReapeating()闹钟均为不精确。因此,可能需要执行以下操作:

if(Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
        alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, nexMin.getTimeInMillis()-current.getTimeInMillis(), 60000, pendingIntent);
} else {
        alarmManager.setExact(AlarmManager.RTC_WAKEUP, nexMin.getTimeInMillis() - current.getTimeInMillis(), pendingIntent);
}

你可以在这里找到完整的实现。

尽管我已经给你奖励,但是这个应用程序在午夜仍然没有得到更新。唉…… - coolcool1994
我认为你忘记添加super.onReceive了。 - coolcool1994
@coolcool1994 有一件事你需要知道...如果你在api 19+上测试你的应用程序,那么setRepeating()将无法正常工作。阅读文档以了解更多信息...改用setExact()并在onReceive()中设置下一个闹钟... http://developer.android.com/reference/android/app/AlarmManager.html#setRepeating(int, long, long, android.app.PendingIntent) - denvercoder9
我不确定该怎么做,而且已经很长时间没有修复过了,但你可以先尝试最简单的事情。比如说,首先让小部件每秒更新一次-设置一个计时器。然后其他所有东西都应该正常工作。逐步进行。希望这能有所帮助。 - coolcool1994
1
请将您的解决方案发布为答案,以帮助社区。 :) 我不需要解决方案,因为我之前已经实现了,但其他人会从中受益。 :) - denvercoder9
显示剩余6条评论

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