Flutter后台处理问题:应用关闭时如何从SQLite触发本地通知
Great question! When a Flutter app is closed (either by the user or system), Dart code stops executing immediately, so we need to lean on native platform capabilities to read from SQLite and trigger local notifications. Let's break down solutions for both your scenarios:
1. 应用主动关闭时触发通知(用户划掉应用)
In this case, we can listen for the app's lifecycle state to trigger a native task right before the app fully shuts down. The native task will handle reading from SQLite and sending the notification.
Step-by-Step Implementation
- Setup Dependencies: Use
flutter_local_notificationsfor cross-platform notification support, and create aMethodChannelto communicate with native code. - Lifecycle Listening: Detect when the app enters the
detachedstate (final step before shutdown) and invoke a native method. - Native Task Execution: Use Android's
WorkManageror iOS'sBGTaskSchedulerto run a one-time task that reads SQLite data and sends a notification.
Flutter Code (Lifecycle & Channel Communication)
import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; class MyApp extends StatefulWidget { @override _MyAppState createState() => _MyAppState(); } class _MyAppState extends State<MyApp> with WidgetsBindingObserver { final MethodChannel _notificationChannel = MethodChannel('com.your.app/notification_trigger'); @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); } @override void didChangeAppLifecycleState(AppLifecycleState state) { super.didChangeAppLifecycleState(state); // Trigger native task when app is fully detached if (state == AppLifecycleState.detached) { _notificationChannel.invokeMethod('triggerFromSQLite'); } } @override void dispose() { WidgetsBinding.instance.removeObserver(this); super.dispose(); } @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold(appBar: AppBar(title: Text("SQLite Notification Demo"))), ); } }
Android Code (Kotlin with WorkManager)
First, add WorkManager dependency to your build.gradle:
dependencies { implementation "androidx.work:work-runtime-ktx:2.8.1" }
Then implement the worker and channel handler:
import android.content.Context import android.database.sqlite.SQLiteDatabase import android.os.Build import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import androidx.work.Worker import androidx.work.WorkerParameters class SQLiteNotificationWorker(context: Context, params: WorkerParameters) : Worker(context, params) { override fun doWork(): Result { // Access Flutter's SQLite database val dbPath = applicationContext.getDatabasePath("your_database_name.db").absolutePath val db = SQLiteDatabase.openDatabase(dbPath, null, SQLiteDatabase.OPEN_READONLY) // Query example: Get unread message count val cursor = db.rawQuery("SELECT COUNT(*) FROM messages WHERE is_read = 0", null) val unreadCount = if (cursor.moveToFirst()) cursor.getInt(0) else 0 cursor.close() db.close() // Send notification if there are unread items if (unreadCount > 0) { // Create notification channel (required for Android 8+) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val channel = android.app.NotificationChannel( "unread_channel", "Unread Notifications", android.app.NotificationManager.IMPORTANCE_DEFAULT ) val notificationManager = applicationContext.getSystemService(android.app.NotificationManager::class.java) notificationManager.createNotificationChannel(channel) } val notification = NotificationCompat.Builder(applicationContext, "unread_channel") .setSmallIcon(R.drawable.ic_notification) .setContentTitle("Unread Messages") .setContentText("You have $unreadCount unread messages") .setPriority(NotificationCompat.PRIORITY_DEFAULT) .build() NotificationManagerCompat.from(applicationContext).notify(1, notification) } return Result.success() } } // In your FlutterActivity or FlutterFragment MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "com.your.app/notification_trigger") .setMethodCallHandler { call, result -> if (call.method == "triggerFromSQLite") { val workRequest = androidx.work.OneTimeWorkRequestBuilder<SQLiteNotificationWorker>().build() androidx.work.WorkManager.getInstance(context).enqueue(workRequest) result.success(null) } else { result.notImplemented() } }
iOS Code (Swift with BGTaskScheduler)
First, add FMDB (SQLite wrapper) to your Podfile, and enable background modes in Info.plist (add fetch to UIBackgroundModes).
Then implement the task handler and channel communication:
import UIKit import Flutter import BackgroundTasks import FMDB @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { let taskIdentifier = "com.your.app.sqlite_notification_task" override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { // Register background task BGTaskScheduler.shared.register(forTaskWithIdentifier: taskIdentifier, using: nil) { task in self.handleNotificationTask(task: task as! BGAppRefreshTask) } let controller : FlutterViewController = window?.rootViewController as! FlutterViewController let channel = FlutterMethodChannel(name: "com.your.app/notification_trigger", binaryMessenger: controller.binaryMessenger) channel.setMethodCallHandler { [weak self] call, result in if call.method == "triggerFromSQLite" { let request = BGAppRefreshTaskRequest(identifier: self!.taskIdentifier) request.earliestBeginDate = Date(timeIntervalSinceNow: 1) // Run ASAP do { try BGTaskScheduler.shared.submit(request) result.success(nil) } catch { result.error("SUBMIT_FAILED", "Could not schedule task", error.localizedDescription) } } else { result.notImplemented() } } GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } func handleNotificationTask(task: BGAppRefreshTask) { // Get Flutter's SQLite database path let fileManager = FileManager.default guard let supportDir = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first else { task.setTaskCompleted(success: false) return } let dbPath = supportDir.appendingPathComponent("your_database_name.db").path let db = FMDatabase(path: dbPath) guard db.open() else { task.setTaskCompleted(success: false) return } // Query unread messages guard let result = db.executeQuery("SELECT COUNT(*) FROM messages WHERE is_read = 0", withArgumentsIn: nil) else { db.close() task.setTaskCompleted(success: false) return } let unreadCount = result.next() ? Int(result.int(forColumnIndex: 0)) : 0 result.close() db.close() // Send notification if unreadCount > 0 { let content = UNMutableNotificationContent() content.title = "Unread Messages" content.body = "You have \(unreadCount) unread messages" content.sound = UNNotificationSound.default let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil) UNUserNotificationCenter.current().add(request) { error in if let error = error { print("Notification error: \(error.localizedDescription)") } } } task.setTaskCompleted(success: true) } }
2. 后台处理场景下应用关闭时触发通知
If your app was running a background task when it got closed, you need to ensure the task persists natively. Flutter's background plugins (like workmanager) wrap native APIs, but for reliability, it's better to handle SQLite reading and notification sending directly in native background tasks.
Key Notes
- Android:
WorkManagertasks are persistent by default—they'll run even if the app is closed or the device reboots (if you addPersistedWorkPolicy.PERSISTED). - iOS: Use
BGProcessingTaskfor longer-running tasks (like heavy SQLite queries) instead ofBGAppRefreshTask. Remember iOS has strict background execution limits, so keep tasks short and focused. - Avoid Flutter Background Code: Once the app is closed, Dart isolates may be terminated, so native code is the only reliable way to complete the task.
Critical Considerations
- SQLite Path: Double-check the database path on both platforms—Flutter's
sqflitestores databases ingetDatabasesPath(), which maps to different native locations as shown in the code examples. - Permissions:
- Android 13+: Request
POST_NOTIFICATIONSpermission at runtime. - iOS: Request notification permission using
UNUserNotificationCenterand enable background modes in Info.plist.
- Android 13+: Request
- Testing:
- Android: Use
adb shell am kill com.your.appto force-close the app and verify the task runs. - iOS: Use Xcode's
Debug > Simulate Background Fetchor wait for the system to trigger the task (may take a few minutes).
- Android: Use
内容的提问来源于stack exchange,提问作者Mahesh Bhattarai

