Repository navigation
Description
AI Disclosure, discovered and written by Opus 5.5.
I was diagnosing rising memory in an application with Opus while profiling heap.
Summary
On Android, five branches of MethodCallHandlerImpl.onMethodCall never call result:
The Dart side discards the Future from invokeMethod, so nothing visibly fails. But the Future never completes, and its suspended async frame stays reachable until the process exits. sendDataToTask matters most, because apps call it repeatedly.
The iOS sendData case also appears not to call result. I only read that code and did not run it.
Reproduction
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_foreground_task/flutter_foreground_task.dart';
const _channel = MethodChannel('flutter_foreground_task/methods');
Future<void> _probe(String method, [Object? arguments]) async {
try {
final reply = await _channel
.invokeMethod<Object?>(method, arguments)
.timeout(const Duration(seconds: 5));
debugPrint('PROBE $method: replied $reply');
} on TimeoutException {
debugPrint('PROBE $method: NO REPLY after 5 s');
} on Object catch (error) {
debugPrint('PROBE $method: error $error');
}
}
void main() {
WidgetsFlutterBinding.ensureInitialized();
FlutterForegroundTask.initCommunicationPort();
runApp(const MaterialApp(home: Scaffold(body: Center(child: Text('probe')))));
WidgetsBinding.instance.addPostFrameCallback((_) async {
await Future<void>.delayed(const Duration(seconds: 2));
await _probe('isRunningService');
await _probe('isAppOnForeground');
await _probe('sendData', 'hello');
await _probe('wakeUpScreen');
await _probe('setOnLockScreenVisibility', false);
await _probe('launchApp');
await _probe('minimizeApp');
debugPrint('PROBE done');
});
}The app also declares android.permission.WAKE_LOCK. Output:
PROBE isRunningService: replied false
PROBE isAppOnForeground: replied true
PROBE sendData: NO REPLY after 5 s
PROBE wakeUpScreen: NO REPLY after 5 s
PROBE setOnLockScreenVisibility: NO REPLY after 5 s
PROBE launchApp: NO REPLY after 5 s
PROBE minimizeApp: NO REPLY after 5 s
PROBE done
The first two lines are controls, and they reply normally.
Environment: flutter_foreground_task 11.0.3, Flutter 3.47.2 stable, Android 15 (API 35) arm64 emulator.
Impact
I found this while checking a long-running production app on 8.17.0 for leaks, using a profile build on the same emulator. After forced GCs, 46 suspended frames held the flutter_foreground_task/methods channel object, and the count grew by about 4 each time the app went to the background and resumed. sendData is the only non-replying method that app uses. It came to roughly 0.5 KB per stuck call.
The retaining path of those frames:
_SuspendState
<- closure errorCallback in _createAsyncCallbacks
<- _FutureListener
<- _Future
<- _AsyncCompleter.future
<- closure in send
<- closure in _zonedPlatformMessageResponseCallback
[root: persistent handle]
Each call costs little, so this isn't urgent, but it's unbounded. It adds up most in the long-running apps this plugin is built for, and in any app that sends messages to the task at a high rate.
Suggested fix
Reply in each branch, for example:
"sendData" -> {
provider.getForegroundServiceManager().sendData(args)
result.success(null)
}launchApp and setOnLockScreenVisibility also need a reply when their argument type check fails, since neither has an else branch today.
Activity