从flutter_downloader的进度回调不响应来理解IsolateNameServer

568 阅读3分钟

问题的总结

出现

这个月在做Flutter的下载需求时。将flutter_downloader的版本更新了到了1.9.1版本。 发现注册下载回调的API

FlutterDownloader.registerCallback((String id,int status, int progress){
final SendPort? send =
    IsolateNameServer.lookupPortByName('downloader_send_port');
send?.send([id, status.value, progress]);
});

变成了下面的样子

FlutterDownloader.registerCallback((String id,DownloadStatus status, int progress){
final SendPort? send =
    IsolateNameServer.lookupPortByName('downloader_send_port');
send?.send([id, status, progress]);
});

回调函数的第二个参数从int变成了DownloadStatus

使用IsolateNameServer来监听回调也顺理成章的修改了第二个参数的类型。

IsolateNameServer.registerPortWithName(
    _port!.sendPort, 'downloader_send_port');
_port?.listen((dynamic data) async {
  String id = data[0];
  DownloadTaskStatus status = data[1];
  int progress = data[2];
  List<DownloadTask>? list = await FlutterDownloader.loadTasks();
  DownloadTask? task =
      list?.firstWhereOrNull((element) => element.taskId == id);
  if (task == null) {
    return;
  }
  eventBus.fire(ApkDownloadEvent(task));
  if (status == DownloadTaskStatus.complete) {
    debugPrint("下载完成");
  } else if (status == DownloadTaskStatus.failed) {
    BotToast.showText(text: "下载失败,请重试");
  }
});

当执行下载的时候发现_port?.listen的监听方法没有回调了。检查API的写法,FlutterDownload.registerCallback的注释也是上面的写法注释。

排查

一层一层的查找问题,发现FlutterDownloader.registerCallback的方法是有正常回调的。最后发现问题出现在send?.send([id, status, progress]); status是一个DownloadStatus对象,而send?.send的参数只能包含以下类型的对象:

  • null
  • true和false
  • int、double、String的实例
  • 通过List, Map 和 Set创建的实例
  • TransferableTypedData
  • Capability

解决

找到问题之后,那么只需要想修改对应代码即可

FlutterDownloader.registerCallback((String id,DownloadStatus status, int progress){
final SendPort? send =
    IsolateNameServer.lookupPortByName('downloader_send_port');
send?.send([id, status.value, progress]);
});
IsolateNameServer.registerPortWithName(
    _port!.sendPort, 'downloader_send_port');
_port?.listen((dynamic data) async {
  String id = data[0];
  DownloadTaskStatus status = DownloadTaskStatus(data[1]);
  int progress = data[2];
  List<DownloadTask>? list = await FlutterDownloader.loadTasks();
  DownloadTask? task =
      list?.firstWhereOrNull((element) => element.taskId == id);
  if (task == null) {
    return;
  }
  eventBus.fire(ApkDownloadEvent(task));
  if (status == DownloadTaskStatus.complete) {
    debugPrint("下载完成");
  } else if (status == DownloadTaskStatus.failed) {
    BotToast.showText(text: "下载失败,请重试");
  }
});

再次运行程序,成功监听到回调了。

拓展学习

在查找问题的时候,我当时在FlutterDownloader.registerCallback试着用eventBus去通知数据,而不是用IsolateNameServer,当我在执行FlutterDownloder.loadTasks去查找正在下载的内容时,控制台直接输出

plugin flutter_downloader has already been initialized

我已经是执行过初始化方法,所以说明回调内容确实是隔离的,只能去用IsolateNameServer去接收对应的数据。

深入API

因为不会,所以要学。去查看FlutterDownloader的源码时 在FlutterDownloader.registerCallback源码中有:

final callbackHandle = PluginUtilities.getCallbackHandle(callback);

创建了插件的后台回调下载进度的Isolate的回调句柄。下载的回调事件通过这个方法句柄来返回。

PluginUtilities.getCallbackHandle的注释

获取命名的顶级或静态回调函数的句柄,该函数可以在隔离之间轻松传递。回调参数不能为null。返回可提供给PluginUtilities的CallbackHandle.getCallbackFromHandle,以检索原始回调的剥离。如果回调不是顶级函数或静态函数,则返回null。

FlutterDownloader.initialize的源码

final callback = PluginUtilities.getCallbackHandle(callbackDispatcher)!;
await _channel.invokeMethod<void>('initialize', <dynamic>[
  callback.toRawHandle(),
  if (debug) 1 else 0,
  if (ignoreSsl) 1 else 0,
]);

_initialized = true;

也创建了一个Isolate的Callback句柄。

在实际的进度回调方法中

@pragma('vm:entry-point')
void callbackDispatcher() {
  const backgroundChannel = MethodChannel('vn.hunghd/downloader_background');

  WidgetsFlutterBinding.ensureInitialized();

  backgroundChannel
    ..setMethodCallHandler((call) async {
      final args = call.arguments as List<dynamic>;
      final handle = CallbackHandle.fromRawHandle(args[0] as int);
      final callback = PluginUtilities.getCallbackFromHandle(handle);

      if (callback == null) {
        // ignore: avoid_print
        print('fatal error: could not find callback');
        exit(1);
      }

      final id = args[1] as String;
      final status = args[2] as int;
      final progress = args[3] as int;

      callback(id, DownloadTaskStatus(status), progress);
    })
    ..invokeMethod<void>('didInitializeDispatcher');
}

也是PluginUtilities.getCallbackFromHandle通过回调方法的句柄找到方法后进行回调的。