问题的总结
出现
这个月在做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通过回调方法的句柄找到方法后进行回调的。