Flutter中在Isolate调用sqflite函数报错问题求助
问题背景
我有一个能正常工作的sqflite数据库,单元测试也没问题。但在UI的onTap方法里调用sqflite方法时,因为不能在主线程执行(Flutter会报错),所以尝试用Isolate来处理,结果又出现了新的异常。如果Isolate里不调用sqflite函数,只返回bool值是正常的,一调用就报错。
下面是相关代码和异常信息:
UI组件代码片段
loginButton(BuildContext context) { return Expanded( child: Padding( padding: const EdgeInsets.only(left: 20.0, right: 5.0, top: 20.0, bottom: 0.0), child: GestureDetector( onTap: () { if (emailController.text.length > 1 && passwordController.text.length > 7) { /// 查找用户... doCheckIfFoundUser(emailController.text.trim(), passwordController.text); } else { printUserNotFound(); } }, child: buttonContainer(Colors.indigo, "Login", 20.0), ), ), ); }
UI调用的方法
doCheckIfFoundUser(String email, String password) async { var result; List<String> emailPasswordList = new List(); emailPasswordList.add(email); emailPasswordList.add(password); var receivePort = new ReceivePort(); Isolate.spawn(callbackFunction, receivePort.sendPort); SendPort sendPort = await receivePort.first; var ans = await sendReceive(sendPort, emailPasswordList); setState(() { result = ans; print("The value is $result - please do your thing"); }); } Future sendReceive(SendPort send, message) { ReceivePort receivePort = ReceivePort(); send.send([message, receivePort.sendPort]); return receivePort.first; }
Isolate回调函数
static void callbackFunction(SendPort callerSendPort) async { ReceivePort newIsolateReceivePort = ReceivePort(); callerSendPort.send(newIsolateReceivePort.sendPort); var msg = await newIsolateReceivePort.first; List<String> emailPasswordList = msg[0]; print("email: ${emailPasswordList[0]}, password: ${emailPasswordList[1]}"); bool foundUser = await searchForUser(emailPasswordList[0], emailPasswordList[1]); SendPort replyPort = msg[1]; replyPort.send(foundUser); }
异常信息
Isolate调用时的异常
I/flutter ( 2073): email: email, password: Passw0rd E/flutter ( 2073): [ERROR:flutter/runtime/dart_isolate.cc(805)] Unhandled exception: E/flutter ( 2073): error: native function 'Window_sendPlatformMessage' (4 arguments) cannot be found E/flutter ( 2073): #0 Window.sendPlatformMessage (dart:ui/window.dart:1089:9) E/flutter ( 2073): #1 _DefaultBinaryMessenger._sendPlatformMessage (package:flutter/src/services/binary_messenger.dart:85:15) E/flutter ( 2073): #2 _DefaultBinaryMessenger.send (package:flutter/src/services/binary_messenger.dart:129:12) E/flutter ( 2073): #3 MethodChannel.invokeMethod (package:flutter/src/services/platform_channel.dart:309:51) E/flutter ( 2073): E/flutter ( 2073): #4 invokeMethod (package:sqflite/src/sqflite_impl.dart:18:34) E/flutter ( 2073): E/flutter ( 2073): #5 SqfliteDatabaseFactoryImpl.invokeMethod (package:sqflite/src/factory_impl.dart:33:7) E/flutter ( 2073): #6 _SqfliteDatabaseFactoryImpl&Object&SqfliteDatabaseFactoryMixin.safeInvokeMethod.<anonymous closure> (package:sqflite/src/factory_mixin.dart:22:35) E/flutter ( 2073): #7 wrapDatabaseException (package:sqflite/src/exception_impl.dart:7:34) E/flutter ( 2073): . E/flutter ( 2073): #8 SqfliteDatabaseFactoryImpl.wrapDatabaseException (package:sqflite/src/factory_impl.dart:29:7). E/flutter ( 2073): #9 _SqfliteDatabaseFactoryImpl&Object&SqfliteDatabaseFactoryMixin.safeInvokeMethod (package:sqflite/src/factory_mixin.dart:22:7). E/flutter ( 2073): #10 _SqfliteDatabaseFactoryImpl&Object&SqfliteDatabaseFactoryMixin.getDatabasesPath (package:sqflite/src/factory_mixin.dart:136:17). E/flutter ( 2073): E/flutter ( 2073): #11 getDatabasesPath.<anonymous closure> (package:sqflite/sqflite.dart:166:54) E/flutter ( 2073): #12 UsersSqflite.init (package:himrepo/controller/users_database.dart:20:47). E/flutter ( 2073): . E/flutter ( 2073): #13 _LoginPageState.searchForUser.<anonymous closure> (package:himrepo/ui/login.dart:268:24). E/flutter ( 2073): . E/flutter ( 2073): #14 _LoginPageState.callbackFunction(package:himrepo/ui/login.dart:166:28). E/flutter ( 2073): . E/flutter ( 2073): #15 _startIsolate.<anonymous closure>.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:304:17). E/flutter ( 2073): #16 _RawReceivePortImpl._handleMessage(dart:isolate-patch/isolate_patch.dart:172:12).
直接调用sqflite方法时的异常
E/AndroidRuntime( 6628): FATAL EXCEPTION: Sqflite E/AndroidRuntime( 6628): java.lang.RuntimeException: Methods marked with @UiThread must be executed on the main thread. Current thread: Sqflite E/AndroidRuntime( 6628): at io.flutter.embedding.engine.FlutterJNI.ensureRunningOnMainThread(FlutterJNI.java:794) E/AndroidRuntime( 6628): at io.flutter.embedding.engine.FlutterJNI.invokePlatformMessageResponseCallback(FlutterJNI.java:727) E/AndroidRuntime( 6628): at io.flutter.embedding.engine.dart.DartMessenger$Reply.reply(DartMessenger.java:140) E/AndroidRuntime( 6628): at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler$1.success(MethodChannel.java:225) E/AndroidRuntime( 6628): at com.tekartik.sqflite.SqflitePlugin$6.run(SqflitePlugin.java:778) E/AndroidRuntime( 6628): at android.os.Handler.handleCallback(Handler.java:873) E/AndroidRuntime( 6628): at android.os.Handler.dispatchMessage(Handler.java:99) E/AndroidRuntime( 6628): at android.os.Looper.loop(Looper.java:193) E/AndroidRuntime( 6628): at android.os.HandlerThread.run(HandlerThread.java:65)
解决方案
核心问题在于:sqflite本身已经在后台线程处理数据库操作了,你不需要自己手动创建Isolate。而且Isolate无法直接访问Flutter的MethodChannel(这就是你看到Window_sendPlatformMessage异常的原因),因为MethodChannel依赖于主线程的UI环境。
正确的做法是直接用Future配合async/await,把数据库操作放在异步函数里,Flutter会自动处理后台执行,同时你只需要确保在更新UI的时候回到主线程(setState本身就是主线程执行的)。
优化后的代码
- 去掉Isolate相关代码,直接在
doCheckIfFoundUser里调用数据库方法:
doCheckIfFoundUser(String email, String password) async { // 直接异步调用数据库方法,sqflite会自己在后台处理 bool result = await searchForUser(email.trim(), password); setState(() { print("The value is $result - please do your thing"); // 这里处理结果,比如跳转到首页或者提示错误 }); }
- 确保你的
searchForUser方法是标准的异步数据库操作:
Future<bool> searchForUser(String email, String password) async { final db = await UsersSqflite.instance.database; // 假设这是你的数据库实例获取方式 // 执行查询 List<Map<String, dynamic>> results = await db.query( 'users', where: 'email = ? AND password = ?', whereArgs: [email, password], ); return results.isNotEmpty; }
为什么之前直接调用会报错?
你之前直接调用时的异常Methods marked with @UiThread must be executed on the main thread,大概率是因为你在数据库操作的回调里直接调用了需要主线程执行的方法(比如更新UI或者调用某些依赖主线程的Flutter API),而不是数据库操作本身的问题。只需要确保所有UI更新逻辑都放在setState里,或者用WidgetsBinding.instance.addPostFrameCallback来处理,就可以避免这个问题。
额外建议
- 如果你确实需要处理大量耗时的数据库操作(比如批量数据处理),可以考虑使用
compute函数,它是Flutter封装好的Isolate工具,不过对于sqflite来说,一般不需要,因为sqflite已经做了异步处理。 - 数据库操作的初始化(比如打开数据库)最好在APP启动时完成,或者用单例模式确保只初始化一次,避免重复打开数据库连接。
内容的提问来源于stack exchange,提问作者ezaspi

