Skip to content
This repository was archived by the owner on Jul 30, 2026. It is now read-only.

Commit 394b011

Browse files
committed
fix: harden Android background runtime against platform channel failures
1 parent 89108d6 commit 394b011

3 files changed

Lines changed: 45 additions & 27 deletions

File tree

lib/core/platform/android_foreground_runtime.dart

Lines changed: 38 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -87,28 +87,37 @@ class AndroidForegroundRuntime {
8787
return;
8888
}
8989

90-
final l10n = lookupKickLocalizations();
91-
await ensurePermissions();
92-
if (await FlutterForegroundTask.checkNotificationPermission() !=
93-
NotificationPermission.granted) {
94-
return;
95-
}
96-
await _setNotificationState(mode: _notificationModeProxy);
97-
if (await isRunning()) {
98-
await FlutterForegroundTask.updateService(
90+
try {
91+
final l10n = lookupKickLocalizations();
92+
await ensurePermissions();
93+
if (await FlutterForegroundTask.checkNotificationPermission() !=
94+
NotificationPermission.granted) {
95+
return;
96+
}
97+
await _setNotificationState(mode: _notificationModeProxy);
98+
if (await isRunning()) {
99+
await FlutterForegroundTask.updateService(
100+
notificationTitle: l10n.runtimeNotificationTitle,
101+
notificationText: l10n.runtimeNotificationReturn,
102+
);
103+
return;
104+
}
105+
106+
await FlutterForegroundTask.startService(
107+
serviceId: 701,
99108
notificationTitle: l10n.runtimeNotificationTitle,
100-
notificationText: l10n.runtimeNotificationReturn,
109+
notificationText: l10n.runtimeNotificationManage,
110+
notificationInitialRoute: '/home',
111+
callback: startForegroundRuntimeCallback,
101112
);
102-
return;
113+
} on PlatformException {
114+
// Platform channel may not be available if the engine is still
115+
// recovering after a background/foreground transition. The proxy can
116+
// continue without the foreground service — it will be retried on the
117+
// next start cycle.
118+
} on StateError {
119+
// BackgroundIsolateBinaryMessenger may not be ready yet.
103120
}
104-
105-
await FlutterForegroundTask.startService(
106-
serviceId: 701,
107-
notificationTitle: l10n.runtimeNotificationTitle,
108-
notificationText: l10n.runtimeNotificationManage,
109-
notificationInitialRoute: '/home',
110-
callback: startForegroundRuntimeCallback,
111-
);
112121
}
113122

114123
static Future<bool> ensureTemporaryRunning({String? notificationTitle}) async {
@@ -162,11 +171,17 @@ class AndroidForegroundRuntime {
162171
return;
163172
}
164173

165-
if (await isRunning()) {
166-
await FlutterForegroundTask.stopService();
174+
try {
175+
if (await isRunning()) {
176+
await FlutterForegroundTask.stopService();
177+
}
178+
await FlutterForegroundTask.removeData(key: _notificationModeStorageKey);
179+
await FlutterForegroundTask.removeData(key: _notificationTitleStorageKey);
180+
} on PlatformException {
181+
// Platform channel may be unavailable during background transitions.
182+
} on StateError {
183+
// BackgroundIsolateBinaryMessenger may not be ready.
167184
}
168-
await FlutterForegroundTask.removeData(key: _notificationModeStorageKey);
169-
await FlutterForegroundTask.removeData(key: _notificationTitleStorageKey);
170185
}
171186

172187
static Future<void> _setNotificationState({

lib/proxy/engine/proxy_controller.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,11 @@ typedef AndroidRuntimeRunningCheck = Future<bool> Function();
2929
typedef AndroidRuntimeEffect = Future<void> Function();
3030
typedef AndroidLocalNetworkPermissionRequest = Future<bool> Function();
3131

32-
/// Short grace period after stopping the Android foreground runtime before the
32+
/// Grace period after stopping the Android foreground runtime before the
3333
/// isolate binds a new socket. Gives the OS time to release the previous port.
34-
const _androidRuntimeStopGracePeriod = Duration(milliseconds: 250);
34+
/// Android may take longer to release sockets when transitioning between
35+
/// foreground and background states, so this needs to be generous.
36+
const _androidRuntimeStopGracePeriod = Duration(milliseconds: 800);
3537

3638
class ProxyRuntimeState {
3739
const ProxyRuntimeState({

lib/proxy/engine/proxy_isolate.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,10 @@ import 'proxy_cors.dart';
2626

2727
const _maxRequestBodyBytes = 20 * 1024 * 1024;
2828
const _proxyBindRetryDelays = <Duration>[
29-
Duration(milliseconds: 100),
3029
Duration(milliseconds: 200),
31-
Duration(milliseconds: 400),
30+
Duration(milliseconds: 500),
31+
Duration(milliseconds: 1000),
32+
Duration(milliseconds: 2000),
3233
];
3334
const _modelCatalogRefreshInterval = Duration(minutes: 30);
3435
const defaultProxyRuntimeHost = '127.0.0.1';

0 commit comments

Comments
 (0)