perf(android): keep Core calls and events off the main thread

Changelog: Smoother Android UI while the proxy is busy
This commit is contained in:
chen08209
2026-10-14 09:34:47 +08:00
parent 5984c161c5
commit 2201c199b8
9 changed files with 114 additions and 74 deletions
+49 -10
View File
@@ -1,12 +1,17 @@
import 'dart:convert';
import 'dart:io';
import 'package:fl_clash/common/constant.dart';
import 'package:fl_clash/core/method.dart';
import 'package:fl_clash/core/spill.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/plugins/service.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:path/path.dart';
import '../helpers/app_paths.dart';
class _RecordingListener with ServiceListener {
final events = <CoreEvent>[];
@@ -53,18 +58,25 @@ void main() {
const channelName = '$packageName/service';
const channel = MethodChannel(channelName);
const coreChannel = MethodChannel('$packageName/core');
const codec = StandardMethodCodec();
late List<MethodCall> calls;
void mockChannel(Future<Object?>? Function(MethodCall call) handler) {
void mockChannel(
Future<Object?>? Function(MethodCall call) handler, {
MethodChannel target = channel,
}) {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(channel, (call) async {
.setMockMethodCallHandler(target, (call) async {
calls.add(call);
return handler(call);
});
}
void mockCoreChannel(Future<Object?>? Function(MethodCall call) handler) =>
mockChannel(handler, target: coreChannel);
Future<void> emitFromPlatform(Object? arguments) async {
final payload = json.encode(
CoreMethodCall(method: CoreMethod.message, arguments: arguments).toJson(),
@@ -72,7 +84,7 @@ void main() {
await TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.handlePlatformMessage(
channelName,
codec.encodeMethodCall(MethodCall('event', payload)),
codec.encodeMethodCall(MethodCall('event', utf8.encode(payload))),
null,
);
}
@@ -82,8 +94,10 @@ void main() {
});
tearDown(() {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(channel, null);
for (final target in [channel, coreChannel]) {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(target, null);
}
});
test('is a singleton so the platform handler is installed once', () {
@@ -259,7 +273,7 @@ void main() {
group('invokeMethod', () {
test('encodes the call and decodes the response envelope', () async {
mockChannel(
mockCoreChannel(
(_) async => utf8.encode(
json.encode(
const CoreMethodResponse(id: '7', result: {'up': 1}).toJson(),
@@ -292,7 +306,7 @@ void main() {
test('decodes a large UTF-8 envelope off the calling isolate', () async {
final names = List.generate(5000, (index) => '节点-$index');
mockChannel(
mockCoreChannel(
(_) async => utf8.encode(
json.encode(CoreMethodResponse(id: '3', result: names).toJson()),
),
@@ -305,8 +319,33 @@ void main() {
expect(response!.result, names);
});
test('reads an envelope the Core spilled to a file', () async {
final home = Directory.systemTemp.createTempSync('service_spill');
addTearDown(() => home.deleteSync(recursive: true));
await useAppPaths(home);
final file = File(join(home.path, coreSpillDirectoryName, '1.json'))
..createSync(recursive: true)
..writeAsStringSync(
json.encode(
const CoreMethodResponse(id: '8', result: 'big').toJson(),
),
);
mockCoreChannel(
(_) async => utf8.encode(
json.encode(CoreMethodResponse(id: '8', file: file.path).toJson()),
),
);
final response = await Service().invokeMethod(
const CoreMethodCall(id: '8', method: CoreMethod.getConfig),
);
expect(response!.result, 'big');
expect(file.existsSync(), isFalse);
});
test('returns null when the platform sends no envelope', () async {
mockChannel((_) async => null);
mockCoreChannel((_) async => null);
expect(
await Service().invokeMethod(
@@ -317,7 +356,7 @@ void main() {
});
test('surfaces a Core error inside the envelope', () async {
mockChannel(
mockCoreChannel(
(_) async => utf8.encode(
json.encode({
'id': '1',
@@ -452,7 +491,7 @@ void main() {
.defaultBinaryMessenger
.handlePlatformMessage(
channelName,
codec.encodeMethodCall(const MethodCall('event', '')),
codec.encodeMethodCall(MethodCall('event', Uint8List(0))),
null,
);