You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android14下flutter_local_notifications实现不可滑动删除的永久通知

解决Android 14中Flutter永久通知可滑动删除的问题

核心问题分析

在Android 14中,仅设置ongoing: true不足以阻止用户滑动删除通知,系统对通知的可清除逻辑做了调整:

  • 普通的ongoing通知仍允许用户滑动删除,除非该通知属于前台服务绑定的通知,或指定了特定服务类别。
  • 你尝试的Workmanager周期性任务会重复创建新通知(taskNotificationId自增),旧通知会被系统视为可清除;且周期性任务本身不是持续的前台服务,无法维持永久通知。

修复方案

1. 使用前台服务绑定通知(真正实现不可滑动删除)

Android中只有前台服务的通知才会被系统标记为不可滑动删除,结合flutter_local_notifications实现步骤:

调整通知配置

在AndroidNotificationDetails中添加关键参数,强化不可清除属性:

const AndroidNotificationDetails androidNotificationDetails =
    AndroidNotificationDetails(
  'task-channel',
  'Task Notification',
  channelDescription: 'Permanent Task Notification reminder',
  importance: Importance.max,
  priority: Priority.high,
  ongoing: true,
  autoCancel: false,
  // 指定服务类别,通知系统这是后台服务关联通知
  category: AndroidNotificationCategory.service,
  // 禁用灯光、震动,避免触发额外交互逻辑
  enableLights: false,
  enableVibration: false,
  // 标记为前台服务通知类型
  foregroundServiceType: AndroidForegroundServiceType.regular,
);

启动前台服务绑定通知

通过flutter_local_notifications的原生方法启动前台服务,将通知与服务绑定:

// 在initialize完成后调用,启动前台服务并显示永久通知
Future<void> startPermanentNotification({
  required String title,
  required String description,
}) async {
  await flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<
          AndroidFlutterLocalNotificationsPlugin>()
      ?.startForeground(
          1, // 固定通知ID,避免重复创建
          title,
          description,
          androidNotificationDetails);
}

2. 修复Workmanager版本的重复通知问题

如果需要后台任务配合,不要用周期性任务重复创建通知,而是维持同一个通知的存活:

  • 移除taskNotificationId自增逻辑,始终使用同一个ID显示通知:
await flutterLocalNotificationsPlugin.show(
  1, // 固定ID,覆盖旧通知而非创建新通知
  'Background Service',
  'Service is running',
  platformChannelSpecifics,
);

3. 解决cleanUp()的Failure错误

错误原因是调用disableBackgroundExecution后,前台服务与通知的绑定未正确解除,调整清理逻辑:

Future<void> cleanUp() async {
  // 先停止前台服务并移除关联通知
  await flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<
          AndroidFlutterLocalNotificationsPlugin>()
      ?.stopForeground(true); // true表示同时移除通知
  await Workmanager().cancelAll();
  await flutterLocalNotificationsPlugin.cancelAll();
  await FlutterBackground.disableBackgroundExecution();
}

完整调整后的示例代码

import 'package:flutter_local_notifications/flutter_local_notifications.dart';

class NotificationProvider {
  late FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin;
  static const int permanentNotificationId = 1; // 固定通知ID

  NotificationProvider() {
    flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
  }

  void _onDidReceiveNotificationResponse(
      NotificationResponse notificationResponse) async {
    final String? payload = notificationResponse.payload;
    if (payload != null) {
      print('notification payload: $payload');
    }
  }

  void _onDidReceiveLocalNotification(
      int id, String? title, String? body, String? payload) async {
    print("id $id, title $title, body $body, payload $payload");
  }

  Future<void> initialize() async {
    const AndroidInitializationSettings initializationSettingsAndroid =
        AndroidInitializationSettings('app_icon');

    final DarwinInitializationSettings initializationSettingsDarwin =
        DarwinInitializationSettings(
            onDidReceiveLocalNotification: _onDidReceiveLocalNotification);

    final LinuxInitializationSettings initializationSettingsLinux =
        LinuxInitializationSettings(defaultActionName: 'Open notification');

    final InitializationSettings initializationSettings =
        InitializationSettings(
      android: initializationSettingsAndroid,
      iOS: initializationSettingsDarwin,
      macOS: initializationSettingsDarwin,
      linux: initializationSettingsLinux,
    );

    await flutterLocalNotificationsPlugin.initialize(
      initializationSettings,
      onDidReceiveNotificationResponse: _onDidReceiveNotificationResponse,
    );

    // 创建匹配的通知渠道,确保渠道属性与通知一致
    const AndroidNotificationChannel channel = AndroidNotificationChannel(
      'task-channel',
      'Task Notification',
      description: 'Permanent Task Notification reminder',
      importance: Importance.max,
      enableVibration: false,
      enableLights: false,
    );

    await flutterLocalNotificationsPlugin
        .resolvePlatformSpecificImplementation<
            AndroidFlutterLocalNotificationsPlugin>()
        ?.createNotificationChannel(channel);

    await flutterLocalNotificationsPlugin
        .resolvePlatformSpecificImplementation<
            AndroidFlutterLocalNotificationsPlugin>()!
        .requestNotificationsPermission();
  }

  Future<void> startPermanentNotification({
    required String title,
    required String description,
  }) async {
    const AndroidNotificationDetails androidNotificationDetails =
        AndroidNotificationDetails(
      'task-channel',
      'Task Notification',
      channelDescription: 'Notification reminder for Tasks',
      importance: Importance.max,
      priority: Priority.high,
      ongoing: true,
      autoCancel: false,
      category: AndroidNotificationCategory.service,
      enableLights: false,
      enableVibration: false,
      foregroundServiceType: AndroidForegroundServiceType.regular,
    );
    const NotificationDetails notificationDetails =
        NotificationDetails(android: androidNotificationDetails);

    // 启动前台服务并绑定通知
    await flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<
            AndroidFlutterLocalNotificationsPlugin>()
        ?.startForeground(
            permanentNotificationId, title, description, notificationDetails);
  }

  Future<void> cleanUp() async {
    // 先停止前台服务并移除通知
    await flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<
            AndroidFlutterLocalNotificationsPlugin>()
        ?.stopForeground(true);
    await flutterLocalNotificationsPlugin.cancelAll();
  }
}

关键注意事项

  • 在AndroidManifest.xml中添加必要权限:
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
    
  • 不要依赖Workmanager周期性任务维持通知,前台服务是唯一能保证通知永久不可删除的方式。
  • 确保通知渠道的配置与通知详情一致,渠道属性会直接影响通知的可清除性。

内容的提问来源于stack exchange,提问作者André

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 10:55:58