Flutter фоновые уведомления: Полное руководство по исправлению
Исправьте проблемы с Flutter уведомлениями, которые не работают при закрытом приложении. Узнайте решения с Android WorkManager и iOS фоновой для уведомлений.
Как исправить проблему с планированными уведомлениями, которые не работают, когда приложение Flutter закрыто на Android и iOS?
Запланированные уведомления в Flutter могут не работать, когда приложение закрыто, из‑за ограничений конкретных платформ и требований к конфигурации. Чтобы исправить эту проблему, необходимо реализовать правильную обработку фоновых задач, корректно настроить нативные разрешения и использовать подходящие плагины уведомлений, поддерживающие выполнение в фоне как на Android, так и на iOS.
Contents
- Понимание проблемы
- Решения для Android
- Решения для iOS
- Рекомендации по плагинам
- Советы по отладке
- Лучшие практики
Понимание проблемы
Когда ваше Flutter‑приложение закрыто, операционная система приостанавливает его процессы, включая любые сервисы планирования уведомлений. Это поведение преднамеренно как в Android, так и в iOS, чтобы экономить заряд батареи и системные ресурсы. Однако это создаёт сложности для разработчиков, которым необходимо, чтобы уведомления срабатывали даже тогда, когда приложение не активно.
Основные проблемы:
- Ограничения фонового выполнения Android: с Android 6.0 (Marshmallow) Google постепенно ограничивает выполнение приложений в фоне.
- Ограничения фонового обновления iOS: iOS имеет строгие правила о том, когда фоновые процессы могут работать.
- Ограничения плагинов: не все Flutter‑плагины уведомлений поддерживают выполнение в фоне.
- Отсутствие нативных настроек: правильная настройка в нативных проектах Android и iOS часто упускается из виду.
Многие разработчики сталкиваются с этой проблемой, полагая, что код Dart будет продолжать работать, когда приложение закрыто, но это не так.
Решения для Android
Реализация WorkManager
Для Android наиболее надёжный подход – использовать WorkManager для планирования уведомлений даже при закрытом приложении:
# pubspec.yaml
dependencies:
workmanager: ^0.5.2
flutter_local_notifications: ^16.0.0
import 'package:workmanager/workmanager.dart';
void callbackDispatcher() {
Workmanager().executeTask((task, inputData) async {
// Notification logic here
await FlutterLocalNotificationsPlugin().show(
0,
'Scheduled Notification',
'This is a scheduled notification',
const NotificationDetails(
android: AndroidNotificationDetails(
'main_channel',
'Main Channel',
channelDescription: 'Main channel notifications',
importance: Importance.max,
priority: Priority.high,
),
),
);
return Future.value(true);
});
}
void scheduleNotification() {
Workmanager().initialize(
callbackDispatcher,
isInDebugMode: true,
);
Workmanager().registerPeriodicTask(
'1',
'simplePeriodicTask',
frequency: const Duration(minutes: 15),
constraints: Constraints(
networkType: NetworkType.not_required,
),
);
}
Конфигурация AndroidManifest.xml
Добавьте это в ваш AndroidManifest.xml:
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application
...>
<receiver
android:name="com.fluttercandies.flutter_local_notifications.ScheduledNotificationReceiver"
android:enabled="true"
android:exported="false" />
<receiver
android:name="com.fluttercandies.flutter_local_notifications.ScheduledBootReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON" />
</intent-filter>
</receiver>
</application>
Решения для iOS
Настройка режимов фона
Для iOS необходимо включить режимы фона в вашем Info.plist:
<key>UIBackgroundModes</key>
<array>
<string>background-fetch</string>
<string>background-processing</string>
<string>remote-notification</string>
</array>
Конфигурация AppDelegate
Измените ваш AppDelegate.swift:
import UIKit
import Flutter
import UserNotifications
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().delegate = self
}
// Request notification permissions
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
if granted {
print("Notification permission granted")
}
}
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
Реализация фонового получения
func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
// Schedule notification logic here
// Call completionHandler with the appropriate result
completionHandler(.newData)
}
Рекомендации по плагинам
flutter_local_notifications
Самое полное решение:
dependencies:
flutter_local_notifications: ^16.0.0
Правильно инициализируйте его:
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
Future<void> _initNotifications() async {
const AndroidInitializationSettings initializationSettingsAndroid =
AndroidInitializationSettings('@mipmap/ic_launcher');
const DarwinInitializationSettings initializationSettingsIOS =
DarwinInitializationSettings(
requestAlertPermission: true,
requestBadgePermission: true,
requestSoundPermission: true,
onDidReceiveLocalNotification: _onDidReceiveLocalNotification,
);
const InitializationSettings initializationSettings =
InitializationSettings(
android: initializationSettingsAndroid,
iOS: initializationSettingsIOS,
);
await flutterLocalNotificationsPlugin.initialize(initializationSettings);
}
void _onDidReceiveLocalNotification(
int id,
String? title,
String? body,
String? payload,
) {
// Handle foreground notifications
}
workmanager + flutter_local_notifications
Для самой надёжной планировки комбинируйте оба плагина:
void scheduleNotification() {
Workmanager().registerOneOffTask(
'task-identifier',
'simpleTask',
initialDelay: Duration(minutes: 5),
existingWorkPolicy: ExistingWorkPolicy.replace,
);
}
Советы по отладке
Тестирование фонового выполнения
- Принудительно закройте приложение: используйте переключатель приложений, чтобы полностью закрыть ваше приложение.
- Установите время устройства: продвиньте время устройства, чтобы вызвать запланированные уведомления.
- Проверьте логи устройства: ищите записи о фоновых выполнениях.
Общие проблемы и решения
| Проблема | Решение |
|---|---|
| Уведомления не срабатывают | Проверьте настройки оптимизации батареи |
| Разрешения отклонены | Убедитесь, что запросы разрешений выполнены корректно |
| Инициализация плагинов | Убедитесь, что плагины инициализированы до использования |
| Фоновые процессы убиты | Проверьте с отключённой оптимизацией батареи |
Информация для отладки
Добавьте логирование, чтобы проверить фоновое выполнение:
Workmanager().registerPeriodicTask(
'1',
'simplePeriodicTask',
frequency: Duration(minutes: 15),
inputData: {'debug': 'true'},
constraints: Constraints(
networkType: NetworkType.not_required,
),
);
Лучшие практики
Обработка разрешений
Всегда запрашивайте разрешения корректно:
Future<void> _requestPermissions() async {
if (Platform.isAndroid) {
final AndroidFlutterLocalNotificationsPlugin =
flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>();
await AndroidFlutterLocalNotificationsPlugin?.requestNotificationsPermission();
}
if (Platform.isIOS) {
await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
IOSFlutterLocalNotificationsPlugin>()
?.requestPermissions(
alert: true,
badge: true,
sound: true,
);
}
}
Оптимизация батареи
Обрабатывайте оптимизацию батареи на Android:
if (await PowerSaveMode.isIgnoringBatteryOptimizations()) {
// Proceed with background work
} else {
// Request permission to ignore battery optimization
await PowerSaveMode.requestIgnoreBatteryOptimizations();
}
Стратегия тестирования
- Тестируйте на реальных устройствах, а не в эмуляторе.
- Тестируйте как в переднем, так и в заднем плане.
- Тестируйте с разными настройками оптимизации батареи.
- Тестируйте с разными версиями iOS и Android.
Альтернативные решения
Для сложных задач планирования:
- Firebase Cloud Messaging: для серверного планирования.
- Firebase Functions: для более сложной логики уведомлений.
- Background Isolate: для длительных фоновых задач.
Заключение
Запланированные уведомления в Flutter требуют реализации платформенно‑специфичных решений, чтобы работать при закрытом приложении. Ключевые решения включают:
- Использование WorkManager на Android для надёжного фонового выполнения.
- Настройку режимов фона на iOS для разрешения периодических фоновых задач.
- Правильную обработку разрешений для обеих платформ.
- Выбор подходящей комбинации плагинов, поддерживающих фоновое выполнение.
Всегда тщательно тестируйте реализацию на реальных устройствах с разными настройками оптимизации батареи и версиями ОС. Помните, что мобильные ОС имеют строгие политики фонового выполнения, поэтому понимание этих ограничений критически важно для успешного планирования уведомлений.
Для наиболее надёжных результатов комбинируйте flutter_local_notifications с workmanager и убедитесь в правильной нативной конфигурации в проектах Android и iOS.