From 0f146968c133fc8df46fc8814b8d51f4795a3b5f Mon Sep 17 00:00:00 2001 From: xiwangly2 Date: Fri, 5 Jun 2026 13:44:55 +0800 Subject: [PATCH] Handle foreground service start failures --- .../FlutterBackgroundPlugin.kt | 112 ++++++++++++++++-- .../IsolateHolderService.kt | 72 ++++++++--- lib/src/flutter_background.dart | 9 +- 3 files changed, 163 insertions(+), 30 deletions(-) diff --git a/android/src/main/kotlin/de/julianassmann/flutter_background/FlutterBackgroundPlugin.kt b/android/src/main/kotlin/de/julianassmann/flutter_background/FlutterBackgroundPlugin.kt index 12e6b65..26cf8af 100644 --- a/android/src/main/kotlin/de/julianassmann/flutter_background/FlutterBackgroundPlugin.kt +++ b/android/src/main/kotlin/de/julianassmann/flutter_background/FlutterBackgroundPlugin.kt @@ -3,6 +3,8 @@ package de.julianassmann.flutter_background import android.app.Activity import android.content.Context import android.content.Intent +import android.os.Handler +import android.os.Looper import androidx.annotation.NonNull import androidx.core.app.NotificationCompat @@ -56,6 +58,63 @@ class FlutterBackgroundPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { @JvmStatic var shouldRequestBatteryOptimizationsOff: Boolean = true + private const val SERVICE_START_TIMEOUT_MS = 5000L + private val mainHandler = Handler(Looper.getMainLooper()) + private var pendingEnableResult: Result? = null + private var pendingEnableRequestId = 0 + + @JvmStatic + var isBackgroundExecutionRunning: Boolean = false + private set + + @JvmStatic + fun preparePendingEnableResult(result: Result): Boolean { + if (pendingEnableResult != null) { + return false + } + + pendingEnableRequestId += 1 + val requestId = pendingEnableRequestId + pendingEnableResult = result + mainHandler.postDelayed({ + if (pendingEnableResult != null && pendingEnableRequestId == requestId) { + val pending = pendingEnableResult + pendingEnableResult = null + isBackgroundExecutionRunning = false + pending?.error( + "ForegroundServiceStartTimeout", + "Timed out waiting for the foreground service to start.", + null + ) + } + }, SERVICE_START_TIMEOUT_MS) + return true + } + + @JvmStatic + fun notifyServiceStarted() { + isBackgroundExecutionRunning = true + val pending = pendingEnableResult + pendingEnableResult = null + pending?.success(true) + } + + @JvmStatic + fun notifyServiceStartFailed(error: Throwable) { + isBackgroundExecutionRunning = false + val pending = pendingEnableResult + pendingEnableResult = null + pending?.error( + "ForegroundServiceStartError", + "Unable to start the foreground service: ${error.message}", + error.toString() + ) + } + + @JvmStatic + fun notifyServiceStopped() { + isBackgroundExecutionRunning = false + } fun loadNotificationConfiguration(context: Context?) { val sharedPref = context?.getSharedPreferences(context.packageName + "_preferences", Context.MODE_PRIVATE) @@ -150,6 +209,12 @@ class FlutterBackgroundPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { } } "enableBackgroundExecution" -> { + val appContext = context + if (appContext == null) { + result.error("NoContextError", "The plugin is not attached to an application context.", "") + return + } + // Ensure all the necessary permissions are granted if (!permissionHandler!!.isWakeLockPermissionGranted()) { result.error("PermissionError", "Please add the WAKE_LOCK permission to the AndroidManifest.xml in order to use background_sockets.", "") @@ -157,25 +222,48 @@ class FlutterBackgroundPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { } else if (shouldRequestBatteryOptimizationsOff && !permissionHandler!!.isIgnoringBatteryOptimizations()) { result.error("PermissionError", "The battery optimizations are not turned off.", "") } else { + if (isBackgroundExecutionRunning) { + result.success(true) + return + } + + if (!preparePendingEnableResult(result)) { + result.error("ForegroundServiceStartInProgress", "The foreground service is already starting.", "") + return + } + val intent = Intent(context, IsolateHolderService::class.java) intent.action = IsolateHolderService.ACTION_START - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { - context!!.startForegroundService(intent) - } else { - context!!.startService(intent) + try { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { + appContext.startForegroundService(intent) + } else { + appContext.startService(intent) + } + } catch (e: Exception) { + notifyServiceStartFailed(e) } - result.success(true) } } "disableBackgroundExecution" -> { - val intent = Intent(context!!, IsolateHolderService::class.java) - intent.action = IsolateHolderService.ACTION_SHUTDOWN - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { - context!!.startForegroundService(intent) - } else { - context!!.startService(intent) + val appContext = context + if (appContext == null) { + result.error("NoContextError", "The plugin is not attached to an application context.", "") + return + } + + val intent = Intent(appContext, IsolateHolderService::class.java) + try { + appContext.stopService(intent) + notifyServiceStopped() + result.success(true) + } catch (e: Exception) { + result.error( + "ForegroundServiceStopError", + "Unable to stop the foreground service: ${e.message}", + e.toString() + ) } - result.success(true) } else -> { result.notImplemented() diff --git a/android/src/main/kotlin/de/julianassmann/flutter_background/IsolateHolderService.kt b/android/src/main/kotlin/de/julianassmann/flutter_background/IsolateHolderService.kt index 21e0cc5..24f3b52 100644 --- a/android/src/main/kotlin/de/julianassmann/flutter_background/IsolateHolderService.kt +++ b/android/src/main/kotlin/de/julianassmann/flutter_background/IsolateHolderService.kt @@ -1,6 +1,7 @@ package de.julianassmann.flutter_background import android.annotation.SuppressLint +import android.app.Notification import android.app.NotificationChannel import android.app.NotificationManager import android.app.PendingIntent @@ -12,6 +13,7 @@ import android.net.wifi.WifiManager import android.os.Build import android.os.IBinder import android.os.PowerManager +import android.util.Log import androidx.core.app.NotificationCompat class IsolateHolderService : Service() { @@ -50,18 +52,30 @@ class IsolateHolderService : Service() { if (intent?.action == ACTION_SHUTDOWN) { cleanupService() stopSelf() + return START_NOT_STICKY } else if (intent?.action == ACTION_START) { - startService() + return if (startService()) START_STICKY else START_NOT_STICKY } - return START_STICKY + return START_NOT_STICKY } private fun cleanupService() { + cleanupLocks() + try { + stopForeground(true) + } catch (e: Exception) { + Log.w(TAG, "Unable to stop foreground service cleanly", e) + } + FlutterBackgroundPlugin.notifyServiceStopped() + } + + private fun cleanupLocks() { wakeLock?.apply { if (isHeld) { release() } } + wakeLock = null if (FlutterBackgroundPlugin.enableWifiLock) { wifiLock?.apply { @@ -70,12 +84,11 @@ class IsolateHolderService : Service() { } } } - - stopForeground(true) + wifiLock = null } @SuppressLint("WakelockTimeout") - private fun startService() { + private fun startService(): Boolean { val pm = applicationContext.packageManager val notificationIntent = pm.getLaunchIntentForPackage(applicationContext.packageName) @@ -112,6 +125,17 @@ class IsolateHolderService : Service() { .setPriority(FlutterBackgroundPlugin.notificationImportance) .build() + if (!startForegroundSafely(notification)) { + return false + } + + acquireLocks() + FlutterBackgroundPlugin.notifyServiceStarted() + return true + } + + @SuppressLint("WakelockTimeout") + private fun acquireLocks() { (getSystemService(Context.POWER_SERVICE) as PowerManager).run { wakeLock = newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG).apply { setReferenceCounted(false) @@ -119,21 +143,41 @@ class IsolateHolderService : Service() { } } - (applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager).run { - wifiLock = createWifiLock(WifiManager.WIFI_MODE_FULL, WIFILOCK_TAG).apply { - setReferenceCounted(false) - acquire() + if (FlutterBackgroundPlugin.enableWifiLock) { + (applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager).run { + wifiLock = createWifiLock(WifiManager.WIFI_MODE_FULL, WIFILOCK_TAG).apply { + setReferenceCounted(false) + acquire() + } } } + } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { - // Use all foreground service types set in the manifest file - startForeground(1, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST); - } else { - startForeground(1, notification) + private fun startForegroundSafely(notification: Notification): Boolean { + return try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + // Use all foreground service types set in the manifest file + startForeground(1, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST) + } else { + startForeground(1, notification) + } + true + } catch (e: SecurityException) { + handleStartFailure(e) + false + } catch (e: RuntimeException) { + handleStartFailure(e) + false } } + private fun handleStartFailure(error: Throwable) { + Log.e(TAG, "Unable to start foreground service", error) + cleanupLocks() + FlutterBackgroundPlugin.notifyServiceStartFailed(error) + stopSelf() + } + override fun onTaskRemoved(rootIntent: Intent) { super.onTaskRemoved(rootIntent) cleanupService() diff --git a/lib/src/flutter_background.dart b/lib/src/flutter_background.dart index 15a2823..b7dd51b 100644 --- a/lib/src/flutter_background.dart +++ b/lib/src/flutter_background.dart @@ -51,10 +51,11 @@ class FlutterBackground { /// May throw a [PlatformException]. static Future enableBackgroundExecution() async { if (_isInitialized) { - final success = - await _channel.invokeMethod('enableBackgroundExecution'); - _isBackgroundExecutionEnabled = true; - return success == true; + final success = await _channel.invokeMethod( + 'enableBackgroundExecution', + ); + _isBackgroundExecutionEnabled = success == true; + return _isBackgroundExecutionEnabled; } else { throw Exception( 'FlutterBackground plugin must be initialized before calling enableBackgroundExecution()');