Flutter通知监听插件flutter_notification_listener的使用

发布于 1周前 作者 h691938207 来自 Flutter

Flutter通知监听插件 flutter_notification_listener 的使用

简介

flutter_notification_listener 是一个 Flutter 插件,用于监听 Android 设备上的所有传入通知。该插件通过启动一个服务来监听通知,并提供了简单的方法来访问通知的字段。此外,它还支持在后台执行 Dart 代码,并在设备重启后自动启动服务。

特性

  • 服务:启动一个服务来监听通知。
  • 简单:轻松访问通知的字段。
  • 后台运行:在后台执行 Dart 代码,并在设备重启后自动启动服务。
  • 交互式:通知在 Flutter 中是可交互的。

安装

1. 修改 pubspec.yaml

在你的 pubspec.yaml 文件中添加 flutter_notification_listener 依赖:

dependencies:
  flutter_notification_listener: ^<latest_version>

你可以通过以下链接获取最新版本: Version

然后安装依赖:

  • 从终端运行:flutter pub get
  • 从 Android Studio/IntelliJ:点击 pubspec.yaml 顶部的 Packages get
  • 从 VS Code:点击 pubspec.yaml 顶部的 Get Packages

2. 注册服务和权限

AndroidManifest.xml 文件中注册服务和权限:

<service android:name="im.zoe.labs.flutter_notification_listener.NotificationsHandlerService"
    android:label="Flutter Notifications Handler"
    android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
    <intent-filter>
        <action android:name="android.service.notification.NotificationListenerService" />
    </intent-filter>
</service>

<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

快速开始

1. 初始化插件并添加监听处理器

import 'package:flutter/material.dart';
import 'package:flutter_notification_listener/flutter_notification_listener.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: NotificationsLog(),
    );
  }
}

class NotificationsLog extends StatefulWidget {
  @override
  _NotificationsLogState createState() => _NotificationsLogState();
}

class _NotificationsLogState extends State<NotificationsLog> {
  List<NotificationEvent> _log = [];
  bool started = false;
  bool _loading = false;

  ReceivePort port = ReceivePort();

  @override
  void initState() {
    initPlatformState();
    super.initState();
  }

  @pragma('vm:entry-point') // 防止 Flutter 在发布构建时剥离此函数
  static void _callback(NotificationEvent evt) {
    print("send evt to ui: $evt");
    final SendPort? send = IsolateNameServer.lookupPortByName("_listener_");
    if (send == null) print("can't find the sender");
    send?.send(evt);
  }

  Future<void> initPlatformState() async {
    NotificationsListener.initialize(callbackHandle: _callback);

    IsolateNameServer.removePortNameMapping("_listener_");
    IsolateNameServer.registerPortWithName(port.sendPort, "_listener_");
    port.listen((message) => onData(message));

    var isRunning = (await NotificationsListener.isRunning) ?? false;
    print("Service is ${!isRunning ? "not " : ""}already running");

    setState(() {
      started = isRunning;
    });
  }

  void onData(NotificationEvent event) {
    setState(() {
      _log.add(event);
    });

    print(event.toString());
  }

  void startListening() async {
    print("start listening");
    setState(() {
      _loading = true;
    });
    var hasPermission = (await NotificationsListener.hasPermission) ?? false;
    if (!hasPermission) {
      print("no permission, so open settings");
      NotificationsListener.openPermissionSettings();
      return;
    }

    var isRunning = (await NotificationsListener.isRunning) ?? false;

    if (!isRunning) {
      await NotificationsListener.startService(
          foreground: false,
          title: "Listener Running",
          description: "Welcome to having me");
    }

    setState(() {
      started = true;
      _loading = false;
    });
  }

  void stopListening() async {
    print("stop listening");

    setState(() {
      _loading = true;
    });

    await NotificationsListener.stopService();

    setState(() {
      started = false;
      _loading = false;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Listener Example'),
        actions: [
          IconButton(
              onPressed: () {
                print("TODO:");
              },
              icon: Icon(Icons.settings))
        ],
      ),
      body: Center(
          child: ListView.builder(
              itemCount: _log.length,
              reverse: true,
              itemBuilder: (BuildContext context, int idx) {
                final entry = _log[idx];
                return ListTile(
                    onTap: () {
                      entry.tap();
                    },
                    title: Container(
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Text(entry.title ?? "<<no title>>"),
                          Text(entry.text ?? "<<no text>>"),
                          Row(
                            children: (entry.actions ?? []).map((act) {
                              return TextButton(
                                  onPressed: () {
                                    if (act.semantic == 1) {
                                      Map<String, dynamic> map = {};
                                      (act.inputs ?? []).forEach((e) {
                                        map[e.resultKey ?? 'null'] =
                                            "Auto reply from me";
                                      });
                                      act.postInputs(map);
                                    } else {
                                      act.tap();
                                    }
                                  },
                                  child: Text(act.title ?? ''));
                            }).toList()
                              ..add(TextButton(
                                  child: Text("Full"),
                                  onPressed: () async {
                                    try {
                                      var data = await entry.getFull();
                                      print("full notifaction: $data");
                                    } catch (e) {
                                      print(e);
                                    }
                                  })),
                          ),
                          Text(entry.createAt.toString().substring(0, 19)),
                        ],
                      ),
                    ));
              })),
      floatingActionButton: FloatingActionButton(
        onPressed: started ? stopListening : startListening,
        tooltip: 'Start/Stop sensing',
        child: _loading
            ? Icon(Icons.close)
            : (started ? Icon(Icons.stop) : Icon(Icons.play_arrow)),
      ),
    );
  }
}

使用说明

1. 启动服务后自动重启

AndroidManifest.xml 中注册广播接收器:

<receiver android:name="im.zoe.labs.flutter_notification_listener.RebootBroadcastReceiver"
    android:enabled="true">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
</receiver>

添加必要的权限:

<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

2. 在后台线程执行任务

定义你自己的回调函数来处理传入的通知:

@pragma('vm:entry-point')
static void _callback(NotificationEvent evt) {
  // 持久化数据
  db.save(evt);

  // 如果需要,将事件发送到 UI 线程
  print("send evt to ui: $evt");
  final SendPort send = IsolateNameServer.lookupPortByName("_listener_");
  if (send == null) print("can't find the sender");
  send?.send(evt);
}

在初始化时注册处理程序:

Future<void> initPlatformState() async {
  NotificationsListener.initialize(callbackHandle: _callback);
}

在 UI 线程中监听事件(如果需要):

void onData(NotificationEvent event) {
  print(event.toString());
}

Future<void> initPlatformState() async {
  // ...
  NotificationsListener.receivePort.listen((evt) => onData(evt));
}

3. 更改监听服务的通知

在启动监听服务之前,可以设置一些参数:

await NotificationsListener.startService({
  bool foreground = true, // 使用 false 将不会提升到前台并且没有通知
  String title = "Change the title",
  String description = "Change the text",
});

4. 点击通知

在通知到达时自动点击通知:

void onData(NotificationEvent event) {
  print(event.toString());
  if (event.canTap) event.tap();
}

5. 点击通知的操作

与通知中的操作进行交互:

void onData(NotificationEvent event) {
  print(event.toString());

  event.actions.forEach((act) {
    if (act.semantic == 2) { // 语义代码为 2 表示这是一个忽略操作
      act.tap();
    }
  });
}

6. 回复对话

自动回复通知中的对话:

void onData(NotificationEvent event) {
  print(event.toString());

  event.actions.forEach((act) {
    if (act.semantic == 1) { // 语义代码为 1 表示快速回复
      Map<String, dynamic> map = {};
      act.inputs.forEach((e) {
        map[e.resultKey] = "Auto reply from flutter";
      });

      act.postInputs(map);
    }
  });
}

API 参考

对象 NotificationEvent

  • uniqueId: String, 通知的唯一 ID。
  • key: String, 状态栏通知的键。
  • packageName: String, 发布通知的应用程序的包名。
  • uid: int, 通知的用户 ID。
  • channelId: String, 通知的通道 ID。
  • id: int, 通知的 ID。
  • createAt: DateTime, 通知在 Flutter 侧的创建时间。
  • timestamp: int, 通知的发布时间。
  • title: String, 通知的标题。
  • text: String, 通知的文本。
  • hasLargeIcon: bool, 通知是否有大图标。
  • largeIcon: Uint8List, 通知的大图标。
  • canTap: bool, 通知是否有内容待处理意图。
  • raw: Map<String, dynamic>, 通知的原始数据。

方法:

  • Future<bool> tap(): 点击通知,通常会清除通知。
  • Future<dynamic> getFull(): 获取 Android 侧的完整通知对象。

对象 Action

  • id: int, 动作在动作数组中的索引。
  • title: String, 动作的标题。
  • semantic: int, 动作的语义类型。
  • inputs: ActionInput, 动作的输入列表。

语义类型:

SEMANTIC_ACTION_ARCHIVE = 5;
SEMANTIC_ACTION_CALL = 10;
SEMANTIC_ACTION_DELETE = 4;
SEMANTIC_ACTION_MARK_AS_READ = 2;
SEMANTIC_ACTION_MARK_AS_UNREAD = 3;
SEMANTIC_ACTION_MUTE = 6;
SEMANTIC_ACTION_NONE = 0;
SEMANTIC_ACTION_REPLY = 1;
SEMANTIC_ACTION_THUMBS_DOWN = 9;
SEMANTIC_ACTION_THUMBS_UP = 8;
SEMANTIC_ACTION_UNMUTE = 7;

方法:

  • Future<bool> tap(): 点击通知的动作。
  • Future<bool> postInputs(Map<String, dynamic> map): 向通知发送输入数据。

对象 ActionInput

  • label: String, 输入的标签。
  • resultKey: String, 输入的结果键。

NotificationsListener

字段:

  • isRunning: bool, 检查监听服务是否正在运行。
  • hasPermission: bool, 检查是否授予了启动监听服务的权限。
  • receivePort: ReceivePort, 默认的事件接收端口。

静态方法:

  • Future<void> initialize(): 初始化插件。
  • Future<void> registerEventHandle(EventCallbackFunc callback): 注册事件处理程序。
  • Future<void> openPermissionSettings(): 打开系统监听通知权限设置页面。
  • Future<bool?> startService({...}): 启动监听服务。
  • Future<bool?> stopService(): 停止监听服务。
  • Future<void> promoteToForeground({...}): 将服务提升到前台。
  • Future<void> demoteToBackground(): 将服务降级到后台。

已知问题

  • 如果服务不是前台服务,服务在重启后将无法启动。

支持

如果你觉得这个插件有用,请考虑捐款以帮助改进它!

贡献

欢迎贡献!


更多关于Flutter通知监听插件flutter_notification_listener的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter通知监听插件flutter_notification_listener的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,下面是一个关于如何使用 flutter_notification_listener 插件的示例代码。这个插件允许你监听 Android 和 iOS 上的本地通知事件。首先,你需要确保你的 Flutter 项目中已经添加了 flutter_notification_listener 依赖。

  1. pubspec.yaml 文件中添加依赖
dependencies:
  flutter:
    sdk: flutter
  flutter_notification_listener: ^x.y.z  # 请替换为最新版本号
  1. 运行 flutter pub get 来获取依赖

  2. 配置 Android 和 iOS 通知权限(这里只展示 Android 配置,iOS 配置类似):

    • android/app/src/main/AndroidManifest.xml 中添加通知权限:

      <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
      <uses-permission android:name="android.permission.VIBRATE"/>
      
    • 确保你的应用具有处理通知的权限,通常在 MainActivity.ktMainActivity.java 中配置。

  3. 在 Dart 代码中使用 flutter_notification_listener

import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter_notification_listener/flutter_notification_listener.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: NotificationListenerScreen(),
    );
  }
}

class NotificationListenerScreen extends StatefulWidget {
  @override
  _NotificationListenerScreenState createState() => _NotificationListenerScreenState();
}

class _NotificationListenerScreenState extends State<NotificationListenerScreen> {
  final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
  FlutterNotificationListener? flutterNotificationListener;

  @override
  void initState() {
    super.initState();

    // 初始化 FlutterLocalNotificationsPlugin
    const AndroidInitializationSettings initializationSettingsAndroid =
        AndroidInitializationSettings('app_icon');
    const IOSInitializationSettings initializationSettingsIOS = IOSInitializationSettings();
    const InitializationSettings initializationSettings = InitializationSettings(
        android: initializationSettingsAndroid, iOS: initializationSettingsIOS);
    flutterLocalNotificationsPlugin.initialize(initializationSettings,
        onSelectNotification: (String? payload) async {
      if (payload != null) {
        showDialog(
            context: context,
            builder: (BuildContext context) {
              return AlertDialog(
                title: Text('Payload:'),
                content: Text(payload),
                actions: <Widget>[
                  TextButton(
                    child: Text('Ok'),
                    onPressed: () {
                      Navigator.of(context).pop();
                    },
                  ),
                ],
              );
            });
      }
    });

    // 初始化 FlutterNotificationListener
    flutterNotificationListener = FlutterNotificationListener(
      onSelectNotification: (String? payload) async {
        print("Notification clicked with payload: $payload");
      },
      onCancelNotification: (String? id) async {
        print("Notification cancelled with id: $id");
      },
      onBackgroundMessage: (Map<String, dynamic> message) async {
        print("Received background message: $message");
      },
    );

    // 配置通知监听
    flutterNotificationListener?.initialize();
  }

  @override
  void dispose() {
    flutterNotificationListener?.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter Notification Listener Example'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () async {
            const AndroidNotificationDetails androidPlatformChannelSpecifics =
                AndroidNotificationDetails(
              'your_channel_id',
              'Your Channel Name',
              'Your Channel Description',
              importance: Importance.max,
              priority: Priority.high,
            );
            const NotificationDetails platformChannelSpecifics = NotificationDetails(
                android: androidPlatformChannelSpecifics, iOS: null);
            await flutterLocalNotificationsPlugin.show(
                0, 'plain title', 'plain body', platformChannelSpecifics,
                payload: 'item x');
          },
          child: Text('Show Notification'),
        ),
      ),
    );
  }
}

注意

  • flutter_notification_listenerflutter_local_notifications 是两个插件,但在这个示例中,我们使用了 flutter_local_notifications 来发送通知,因为 flutter_notification_listener 专注于监听通知事件,而不是发送通知。
  • 确保你正确配置了 Android 和 iOS 的通知权限和通道。
  • 在真实应用中,处理后台消息时,你可能需要在 iOS 项目中配置 AppDelegateBackgroundMessageHandler

这段代码展示了如何初始化 flutter_local_notificationsflutter_notification_listener 插件,并发送一个本地通知,同时监听通知的点击和取消事件。

回到顶部