Flutter Android:将AlarmManager与通知结合使用

3
我正在尝试使用Android中的alarmmanager和通知,但我遇到了困难。基本上,这是我想要实现的行为:
  1. 从sharedpreferences和/或firebase中获取alarmmanager必须触发的时间点。使用它来安排alarmmanager。
  2. 当alarmmanager触发时,从sharedpreferences和/或firebase中获取一些数据,并使用它创建一个通知。同时执行步骤1以安排下一个alarm。
  3. 按下通知后,必须打开特定页面。
我创建了一个基本示例,可以在此处找到:https://github.com/robindijkhof/flutter_noti 当存储库被删除时,我将在下面包含代码片段。
我遇到的问题:
  • 无法在单击通知时打开特定页面。
  • 使用“返回”按钮关闭应用程序后,alarmmanager仍在触发,这是预期的。然而,当单击通知时,应用程序会打开并且alarmmanager不再触发。可能是因为它在另一个Isolate上运行?
我不知道如何解决这个问题。而且,我也不确定我是否正确地处理了这个问题。我需要一些帮助。 如何修复 除了接受的答案之外,我正在使用反射来更新AtomicBoolean。
    new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(
            (call, result) -> {

                if (call.method.equals("resetAlarmManager")) {
                    try {
                        // Get field instance
                        Field field = io.flutter.plugins.androidalarmmanager.AlarmService.class.getDeclaredField("sStarted"); // NOTE: this field may change!!!
                        field.setAccessible(true); // Suppress Java language access checking

                        // Remove "final" modifier
                        Field modifiersField = Field.class.getDeclaredField("accessFlags");
                        modifiersField.setAccessible(true);
                        modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);

                        // Set value
                        field.set(null, new AtomicBoolean(false));
                    } catch (Exception ignored) {
                        Log.d("urenapp", "urenapp:reflection");

                        if (BuildConfig.DEBUG) {
                            throw new RuntimeException("REFLECTION ERROR, FIX DIT.");
                        }
                    }
                }
            });

我在通知点击回调和应用启动时调用了这个本地函数。这要求我重新安排所有闹钟。 代码片段:
import 'package:android_alarm_manager/android_alarm_manager.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';

class AlarmHelper {
  static final int _REQUEST_CODE = 12377;
  static final int _REQUEST_CODE_OVERTIME = 12376;

  static void scheduleAlarm() async {
    print('schedule');
    //Read the desired time from sharedpreferences and/or firebase.

    AndroidAlarmManager.cancel(_REQUEST_CODE);
    AndroidAlarmManager.oneShot(Duration(seconds: 10), _REQUEST_CODE, clockIn, exact: true, wakeup: true);
  }
}

void clockIn() async {
  //Do Stuff
  print('trigger');

  //Read some stuff from sharedpreference and/or firebase.

  setNotification();

  //Schedule the next alarm.
  AlarmHelper.scheduleAlarm();
}

void setNotification() async {
  print('notification set');

  //Read some more stuff from sharedpreference and/or firebase. Use that information for the notification text.




  FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = new FlutterLocalNotificationsPlugin();

  var androidPlatformChannelSpecifics = new AndroidNotificationDetails('test', 'test', 'test',
      importance: Importance.Max, priority: Priority.Max, ongoing: true, color: Colors.blue[500]);
  var iOSPlatformChannelSpecifics = new IOSNotificationDetails();
  var platformChannelSpecifics = new NotificationDetails(androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
  await flutterLocalNotificationsPlugin
      .show(0, 'test', 'time ' + DateTime.now().toIso8601String(), platformChannelSpecifics, payload: 'item id 2');

}

你知道我不知道这是否对你有所帮助,但这是我找到的东西 https://github.com/tensor-programming/flutter_notification_live_stream/blob/master/lib/background.dart - Gaurav Mall
你不能在“通知服务”内部触发警报吗?比如说,你设置了一个特定时间的闹钟,然后计算从现在到“闹钟”时间的时间,并将其作为“Timer”的“schedule()”方法的第一个参数传递,并设置一些周期,表示多久触发一次警报,直到你取消它。然后你可以得到“Notification”类,它可以同时触发通知和闹钟。如果你感兴趣,我可以提供一些这种类型的类的示例代码。 - SkypeDogg
2个回答

3

经过几个小时的调试,android_alarm_manager包不再触发回调的原因似乎是本机端的静态布尔值:

// TODO(mattcarroll): make sIsIsolateRunning per-instance, not static.
private static AtomicBoolean sIsIsolateRunning = new AtomicBoolean(false);

这个布尔变量用于跟踪您的Flutter回调函数是否已在闹钟服务中注册。由于Android的工作方式,在通过点击返回键关闭应用程序时,活动被销毁,但应用程序内存没有清除,因此静态变量(如上述布尔变量)如果您再次打开应用程序,则保留其值。该布尔变量的值从未设置为false,因此,即使插件再次向应用程序注册自身,并重新初始化它,它也不会运行启动Flutter隔离执行您的闹钟回调代码的部分:
sBackgroundFlutterView = new FlutterNativeView(context, true);
if (mAppBundlePath != null && !sIsIsolateRunning.get()) { // HERE sIsIsolateRunning will already be true when you open the app the 2nd time
  if (sPluginRegistrantCallback == null) {
    throw new PluginRegistrantException();
  }
  Log.i(TAG, "Starting AlarmService...");
  FlutterRunArguments args = new FlutterRunArguments();
  args.bundlePath = mAppBundlePath;
  args.entrypoint = flutterCallback.callbackName;
  args.libraryPath = flutterCallback.callbackLibraryPath;
  sBackgroundFlutterView.runFromBundle(args);
  sPluginRegistrantCallback.registerWith(sBackgroundFlutterView.getPluginRegistry());
}

考虑到布尔值是私有的,我认为您无法对其进行任何操作,需要等待修复 - 在布尔值声明上面的TODO表明包的开发人员可能已经意识到静态变量可能引起的潜在问题。

至于在单击通知时导航到特定页面:

_MyAppState中创建一个导航器的GlobalKey

final GlobalKey<NavigatorState> navigatorKey = GlobalKey();

将其添加到您的MaterialApp中:
return MaterialApp(
  navigatorKey: navigatorKey,

_MyAppStateinitState()中初始化flutter_local_notifications插件。这样,您传递给flutterLocalNotificationsPlugin.initializeonSelectNotification函数可以引用您的navigatorKey
flutterLocalNotificationsPlugin.initialize(initializationSettings, onSelectNotification: (String payload) {
  navigatorKey.currentState.pushNamed("your_route");
});

这很有道理。你觉得我能否使用反射重置那个布尔值?顺便说一下,赏金已经过期了,不幸的是。 - Robin Dijkhof
1
是的,私有静态变量应该可以通过反射进行编辑。请在SO上搜索如何实现。很遗憾这个赏金问题... - Ovidiu

0
我无法在点击通知时打开特定页面。
在主页面的initState中设置onSelectNotification。在处理程序中,您可以使用 Navigator 推到特定页面。您需要将MyApp更改为有状态小部件。
Future<void> onSelectNotification(String payload) async {
  await Navigator.push(
    context,
    MaterialPageRoute(builder: (context) => SecondScreen(payload)),
  );
}

当使用返回按钮关闭应用程序时,AlarmManager会继续触发,这是预期的。但是,当单击通知时,应用程序打开并且AlarmManager不再触发。可能是因为它在另一个Isolate上运行?
我不太确定出了什么问题,您可以尝试使用flutter_local_notifications包中的API,因为它提供了相同的功能来实现您想要的效果。

我没有上下文,因为我正在从alarmmanager回调中设置通知。正如您可能已经从我的pubspec.yml文件中看到的那样,我已经在使用flutter_local_notifications - Robin Dijkhof
如果您在主StatefulWidget中设置本地通知,将会有onSelectNotification回调函数。请参考此链接,示例展示了如何通过在StatefulWidget中进行初始化而不是在主函数中获取上下文。 - Yaobin Then
是的,你正在使用 flutter_local_notifications,我很清楚这一点,并且已经克隆了你的存储库。我的建议是,你可以使用它的通知调度API而不是闹钟管理器。我不知道那是否能解决你的问题,从我所看到的来看,问题似乎不在Flutter方面。AlarmManager的本地方法被正确调用了。 - Yaobin Then

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