Flutter Android 将 alarmmanager 与通知相结合

Flutter Android combine alarmmanager with notifications

我正在尝试将 Android alarmmanager 与通知一起使用,但我遇到了困难。基本上,这是我想要实现的行为:

  1. 从 sharedpreferences and/or firebase 获取警报管理器必须触发的时间点。使用它来安排警报管理器。
  2. 当 alarmmanager 触发时,从 sharedpreferences and/or firebase 获取一些数据并使用它来创建通知。同时执行步骤 1 以安排下一个闹钟。
  3. 按下通知时,必须打开特定页面。

我创建了一些基本示例,可在此处获得:https://github.com/robindijkhof/flutter_noti 当回购被删除时,我将在下面包含一个片段。

我遇到的问题:

我不知道如何解决这个问题。另外,我不知道我是否在正确的轨道上。我会很感激一些帮助。

如何修复 除了接受的答案外,我还使用反射来更新 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');

}

I can't open a specific page when the notification is clicked.

在主页的 initState 中设置 onSelectNotification。在处理程序中,您可以使用 Navigator 推送到特定页面。您需要将 MyApp 更改为有状态小部件。

Future<void> onSelectNotification(String payload) async {
  await Navigator.push(
    context,
    MaterialPageRoute(builder: (context) => SecondScreen(payload)),
  );
}

When the app is closed using the back button, the alarmmanager keep firing which is as expected. However, when the notification is clicked, the app opens and the alarmmanager does not fire anymore. Probably because it runs on another Isolate?

不太确定这有什么问题,您可以尝试使用 flutter_local_notifications 包中的 API,因为它提供了相同的功能来实现您想要的。

经过几个小时调试 android_alarm_manager 包后,通过点击返回然后再次打开关闭应用程序后不再触发回调的原因似乎是本机上的这个静态布尔值边:

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

此布尔值跟踪您的 Flutter 回调是否已注册到警报服务。使用 Android 的工作方式,当通过点击返回关闭应用程序时, activity 被销毁,但应用程序内存不会被清除,因此静态变量如上面提到的布尔值会保留它们的值,如果你再次打开应用程序。这个布尔值永远不会设置回 false,因此,即使插件再次向应用程序注册自己,并且您再次初始化它,它也不会 运行 开始的代码部分一个 Flutter isolate 会 运行 你的闹钟回调:

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");
});