如何在Oreo中更新小部件(AlarmManager在几个小时/几天后停止)

3
我有一个信息小部件,每60秒更新一次。在OREO之前,我使用服务来完成此操作,但现在无法这样做,因为当应用程序在后台时无法使用服务,因此我遵循了这里的一些帖子,并使用了闹钟和AlarmManager来更新小部件。
问题是它可以工作几个小时,但在一两天内,闹钟的onReceive方法不再被调用。某些情况下会发生这种情况,因为闹钟停止了,而用户没有删除小部件,因此小部件停止更新。
似乎AlarmManager不是最好的方法,或者我做错了什么。
这是我的小部件闹钟接收器:
public class WidgetAlarmReceiver extends BroadcastReceiver {
    private final static String TAG = "WidgetAlarmReceiver";

    @Override
    public void onReceive(final Context context, Intent intent) {
        Log.d(TAG,"onReceive");

        ComponentName componentName = new ComponentName(context, AppWidget.class);
        RemoteViews remoteViews = WidgetHelper.getInstance().generateRemoteViews(context);

        AppWidgetManager.getInstance(context).updateAppWidget(componentName, remoteViews);
    }
}

这是我设置闹钟的小部件的源代码:
public class AppWidget extends AppWidgetProvider {
    private AlarmManager alarmMgr;
    private PendingIntent alarmIntent;

static void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {
    RemoteViews remoteViews = WidgetHelper.getInstance().generateRemoteViews(context);
    appWidgetManager.updateAppWidget(appWidgetId, remoteViews);
}

@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
    for (int appWidgetId : appWidgetIds) {
        updateAppWidget(context, appWidgetManager, appWidgetId);
    }
}

private void startWidgetAlarm(Context context){
    alarmMgr = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
    Intent intent = new Intent(context, WidgetAlarmReceiver.class);
    intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
    alarmIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
    alarmMgr.setInexactRepeating(AlarmManager.RTC, System.currentTimeMillis(), 60000, alarmIntent);
}

private void stopWidgetAlarm(Context context){
    alarmMgr = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
    Intent intent = new Intent(context, WidgetAlarmReceiver.class);
    PendingIntent sender = PendingIntent.getBroadcast(context, 0, intent, 0);
    alarmMgr.cancel(sender);
}

@Override
public void onEnabled(Context context) {
    super.onEnabled(context);
    startWidgetAlarm(context);
}

@Override
public void onDisabled(Context context) {
    super.onDisabled(context);
    stopWidgetAlarm(context);
}
}

我的清单部分:

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

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

<receiver android:name=".WidgetAlarmReceiver"  android:exported="false"/>

AlarmManager 的一个缺点是,设备重新启动后必须重新注册,也许这就是你所经历的。你可以尝试使用 JobScheduler ==> https://medium.com/google-developers/scheduling-jobs-like-a-pro-with-jobscheduler-286ef8510129 - HedeH
@HedShafran 无法使用它,因为我需要在不兼容JobScheduler的低端设备上执行此操作。此外,设备没有重新启动,因此这不是闹钟停止的原因。 - NullPointerException
你正在测试哪个设备? - earthw0rmjim
@earthw0rmjim Nexus 5X已升级至8.1版本。 - NullPointerException
2
@NullPointerException 你找到解决方案了吗?我也遇到了同样的问题。Android O真是让人头疼。开发者似乎并没有真正关注向后兼容性。 - narb
我正在使用Android O(Nexus),但我几乎无法重现这个问题。然而,我的一些客户抱怨这种方法(AlarmManager)不再起作用。我的计划是迁移到WorkManager。但是,由于我无法重现这个问题,因此我几乎无法测试它。 - Cheok Yan Cheng
1个回答

1

根据我的经验,最好使用一次性闹钟并在触发后重新注册它,因为对于重复闹钟,Android可能会认为您滥用了AlarmManager。所以这是我使用的方法:

void registerOneTimeAlarm(PendingIntent alarmIntent, long delayMillis, boolean triggerNow) {
    int SDK_INT = Build.VERSION.SDK_INT;
    long timeInMillis = (System.currentTimeMillis() + (triggerNow ? 0 : delayMillis));

    if (SDK_INT < Build.VERSION_CODES.KITKAT) {
        alarmManager.set(AlarmManager.RTC_WAKEUP, timeInMillis, alarmIntent);
    } else if (Build.VERSION_CODES.KITKAT <= SDK_INT && SDK_INT < Build.VERSION_CODES.M) {
        alarmManager.setExact(AlarmManager.RTC_WAKEUP, timeInMillis, alarmIntent);
    } else if (SDK_INT >= Build.VERSION_CODES.M) {
        alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, timeInMillis, alarmIntent);
    }
}

也许它对你也有用。

当设备处于睡眠状态时更新小部件并没有太多意义。 - earthw0rmjim
所以使用 setExact(),您可以根据自己的需要修改代码。但是方法才是最重要的... - HedeH
有没有更好的方法来实现这个?这真让人沮丧。 - NullPointerException
不要使用AlarmManager。它是用于闹钟的,而不是每60秒执行任务的。我提供的方法应该是一个足够好的解决方案,在我的应用程序中也是如此,以实现跨版本重复任务而不需要太多代码。 - HedeH
1
除了AlarmManager之外,我还有哪些其他可用于API 14的方式? - NullPointerException

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