You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter使用flutter_isolate做PDF文本搜索时触发空值检查运算符错误

解决PDF文本搜索UI卡顿与flutter_isolate空指针错误

问题描述

主线程执行PDF文本搜索时耗时久、UI卡顿,尝试用flutter_isolate将搜索逻辑移到子线程后,出现Null check operator used on a null value错误,错误栈指向FlutterIsolate.spawn方法。

用户代码:

import 'package:syncfusion_flutter_pdf/pdf.dart';
import 'package:flutter_isolate/flutter_isolate.dart';

void createThread() async {
  searching.value = true;
  ReceivePort port = ReceivePort();
  final isolate = await FlutterIsolate.spawn(
    _searchWithThread,
    [port.sendPort, widget.filePath, _searchTextEditingController.text],
  );
  final result = await port.first;
  isolate.kill(priority: Isolate.immediate);
  findResult.value = result;
  if (findResult.value.isNotEmpty) {
    _goPage();
  } else {
    print('${findResult.value.length} matches found.');
  }
  searching.value = false;
}

void _searchWithThread(List<dynamic> values) {
  SendPort sendPort = values[0];
  String filePath = values[1];
  String searchText = values[2];
  final file = File(filePath);
  PdfDocument document = PdfDocument(inputBytes: file.readAsBytesSync());
  PdfTextExtractor extractor = PdfTextExtractor(document);
  final result = extractor.findText([searchText]);
  sendPort.send(result);
}

错误栈:

E/flutter (19898): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: Null check operator used on a null value
E/flutter (19898): #0      FlutterIsolate.spawn
flutter_isolate.dart:28
E/flutter (19898): #1      _PdfViewerSearchState.createThread
pdf_viewer_search.dart:39
E/flutter (19898): #2      _InkResponseState.handleTap
ink_well.dart:1154
E/flutter (19898): #3      GestureRecognizer.invokeCallback

错误原因

核心问题是_searchWithThread是State类的私有实例方法,而flutter_isolate.spawn要求传入的函数必须是顶级函数或静态方法。隔离区无法共享State实例,导致内部初始化失败,触发空指针错误。

修复后的flutter_isolate方案

把搜索函数改成顶级函数(或State类的静态方法),同时增加异常处理和资源释放:

import 'dart:io';
import 'dart:isolate';
import 'package:syncfusion_flutter_pdf/pdf.dart';
import 'package:flutter_isolate/flutter_isolate.dart';

// 改为顶级函数,确保隔离区能正常调用
void _searchWithThread(List<dynamic> values) {
  SendPort sendPort = values[0];
  String filePath = values[1];
  String searchText = values[2];

  try {
    final file = File(filePath);
    if (!file.existsSync()) {
      sendPort.send([]);
      return;
    }

    PdfDocument document = PdfDocument(inputBytes: file.readAsBytesSync());
    PdfTextExtractor extractor = PdfTextExtractor(document);
    final result = extractor.findText([searchText]);
    sendPort.send(result);
    document.dispose(); // 释放PDF文档资源
  } catch (e) {
    print('搜索异常: $e');
    sendPort.send([]);
  }
}

void createThread() async {
  searching.value = true;
  ReceivePort port = ReceivePort();

  try {
    final isolate = await FlutterIsolate.spawn(
      _searchWithThread,
      [port.sendPort, widget.filePath, _searchTextEditingController.text],
    );

    final result = await port.first;
    isolate.kill(priority: Isolate.immediate);
    findResult.value = result;

    if (findResult.value.isNotEmpty) {
      _goPage();
    } else {
      print('找到${findResult.value.length}个匹配项');
    }
  } catch (e) {
    print('创建隔离区失败: $e');
    findResult.value = [];
  } finally {
    searching.value = false;
    port.close(); // 关闭ReceivePort释放资源
  }
}

更优的无UI卡顿方案

方案1:使用Flutter自带的compute函数

不需要第三方包,底层基于Isolate实现,适合简单耗时任务:

import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:syncfusion_flutter_pdf/pdf.dart';

void createThread() async {
  searching.value = true;

  try {
    final result = await compute(_searchWithCompute, {
      'filePath': widget.filePath,
      'searchText': _searchTextEditingController.text,
    });

    findResult.value = result;
    if (findResult.value.isNotEmpty) {
      _goPage();
    } else {
      print('找到${findResult.value.length}个匹配项');
    }
  } catch (e) {
    print('搜索异常: $e');
    findResult.value = [];
  } finally {
    searching.value = false;
  }
}

// compute要求函数为顶级/静态类型
List<dynamic> _searchWithCompute(Map<String, String> params) {
  String filePath = params['filePath']!;
  String searchText = params['searchText']!;

  final file = File(filePath);
  if (!file.existsSync()) return [];

  PdfDocument document = PdfDocument(inputBytes: file.readAsBytesSync());
  PdfTextExtractor extractor = PdfTextExtractor(document);
  final result = extractor.findText([searchText]);
  document.dispose();
  return result;
}

方案2:直接使用原生Isolate类

比第三方包更灵活,可控性更强:

import 'dart:io';
import 'dart:isolate';
import 'package:syncfusion_flutter_pdf/pdf.dart';

void createThread() async {
  searching.value = true;
  ReceivePort port = ReceivePort();

  try {
    await Isolate.spawn(
      _searchWithIsolate,
      [port.sendPort, widget.filePath, _searchTextEditingController.text],
    );

    final result = await port.first;
    findResult.value = result;

    if (findResult.value.isNotEmpty) {
      _goPage();
    } else {
      print('找到${findResult.value.length}个匹配项');
    }
  } catch (e) {
    print('创建隔离区失败: $e');
    findResult.value = [];
  } finally {
    searching.value = false;
    port.close();
  }
}

void _searchWithIsolate(List<dynamic> values) {
  SendPort sendPort = values[0];
  String filePath = values[1];
  String searchText = values[2];

  try {
    final file = File(filePath);
    if (!file.existsSync()) {
      sendPort.send([]);
      return;
    }

    PdfDocument document = PdfDocument(inputBytes: file.readAsBytesSync());
    PdfTextExtractor extractor = PdfTextExtractor(document);
    final result = extractor.findText([searchText]);
    sendPort.send(result);
    document.dispose();
  } catch (e) {
    print('搜索异常: $e');
    sendPort.send([]);
  }
}

额外注意事项

  • 跨隔离区传递的对象必须是可序列化的(SendPort、String、列表、Map等基础类型均可,自定义对象需支持序列化)。
  • 操作PDF文件后必须调用document.dispose()释放资源,避免内存泄漏。
  • 所有异步操作都要加异常捕获,防止程序崩溃。

内容的提问来源于stack exchange,提问作者Salim Baskoy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 16:15:10