如何为Flutter下载插件的每个调用创建独立事件通道?
在Flutter插件中为每次下载请求创建独立事件通道
要实现每次调用downloadFile时创建独立的事件通道,核心思路是为每个下载请求生成唯一的通道标识,在Flutter端动态创建EventChannel,同时在原生端维护这些通道的生命周期,确保下载完成或出错后及时清理资源。
Flutter 端插件实现
import 'dart:async'; import 'dart:typed_data'; import 'package:flutter/services.dart'; class FileDownloadPlugin { static const MethodChannel _methodChannel = MethodChannel('file_download_plugin'); Future<Stream<Uint8List>> downloadFile(String url) async { // 生成唯一事件通道名称:URL编码+时间戳避免冲突 final channelId = '${Uri.encodeComponent(url)}-${DateTime.now().microsecondsSinceEpoch}'; final channelName = 'file_download_plugin/events/$channelId'; final eventChannel = EventChannel(channelName); // 通知原生端启动下载并绑定对应通道 await _methodChannel.invokeMethod('startDownload', { 'url': url, 'channelName': channelName, }); // 转换事件通道流为Uint8List,并添加生命周期处理 return eventChannel.receiveBroadcastStream() .map((data) => data as Uint8List) .handleError((error) { // 下载出错时清理通道 _disposeChannel(channelName); throw error; }) .whenComplete(() => _disposeChannel(channelName)); } Future<void> _disposeChannel(String channelName) async { try { await _methodChannel.invokeMethod('disposeChannel', {'channelName': channelName}); } catch (_) {} } }
Android 端实现(Kotlin)
在原生端维护活跃通道的映射,处理下载请求时创建对应EventChannel,并在任务结束后清理:
import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.EventChannel class FileDownloadPlugin : FlutterPlugin, MethodChannel.MethodCallHandler { private lateinit var methodChannel: MethodChannel private val activeChannels = mutableMapOf<String, Pair<EventChannel, DownloadTask>>() override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) { methodChannel = MethodChannel(binding.binaryMessenger, "file_download_plugin") methodChannel.setMethodCallHandler(this) } override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { when (call.method) { "startDownload" -> { val url = call.argument<String>("url") ?: run { result.error("INVALID_URL", "URL不能为空", null) return } val channelName = call.argument<String>("channelName") ?: run { result.error("INVALID_CHANNEL", "通道名称不能为空", null) return } val eventChannel = EventChannel(binding.binaryMessenger, channelName) val downloadTask = DownloadTask(url) { event -> when (event) { is DownloadEvent.Data -> activeChannels[channelName]?.first?.sendEvent(event.data) DownloadEvent.Complete -> { activeChannels.remove(channelName)?.let { (ec, task) -> task.cancel() ec.sendEndOfStream() } } is DownloadEvent.Error -> { activeChannels.remove(channelName)?.let { (ec, task) -> task.cancel() ec.sendError("DOWNLOAD_FAILED", event.message, null) } } } } eventChannel.setStreamHandler(object : EventChannel.StreamHandler { override fun onListen(arguments: Any?, events: EventChannel.EventSink?) { downloadTask.setEventSink(events) } override fun onCancel(arguments: Any?) { activeChannels.remove(channelName)?.second?.cancel() } }) activeChannels[channelName] = Pair(eventChannel, downloadTask) downloadTask.start() result.success(null) } "disposeChannel" -> { val channelName = call.argument<String>("channelName") ?: return activeChannels.remove(channelName)?.second?.cancel() result.success(null) } else -> result.notImplemented() } } override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { methodChannel.setMethodCallHandler(null) activeChannels.values.forEach { it.second.cancel() } activeChannels.clear() } // 下载事件封装 sealed class DownloadEvent { data class Data(val data: ByteArray) : DownloadEvent() object Complete : DownloadEvent() data class Error(val message: String) : DownloadEvent() } // 模拟下载任务类 class DownloadTask(private val url: String, private val callback: (DownloadEvent) -> Unit) { private var eventSink: EventChannel.EventSink? = null private var isCancelled = false fun setEventSink(sink: EventChannel.EventSink?) { eventSink = sink } fun start() { // 这里实现实际的下载逻辑,比如使用OkHttp分块下载 // 示例:模拟分块发送数据 Thread { repeat(10) { if (isCancelled) return@Thread val mockData = "Chunk $it".toByteArray() callback(DownloadEvent.Data(mockData)) Thread.sleep(500) } callback(DownloadEvent.Complete) }.start() } fun cancel() { isCancelled = true } } }
iOS 端实现(Swift)
同样通过字典管理活跃通道,处理动态创建的FlutterEventChannel:
import Flutter import UIKit public class FileDownloadPlugin: NSObject, FlutterPlugin { private var methodChannel: FlutterMethodChannel! private var activeChannels: [String: (channel: FlutterEventChannel, task: DownloadTask)] = [:] public static func register(with registrar: FlutterPluginRegistrar) { let channel = FlutterMethodChannel(name: "file_download_plugin", binaryMessenger: registrar.messenger()) let instance = FileDownloadPlugin() instance.methodChannel = channel registrar.addMethodCallDelegate(instance, channel: channel) } public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { switch call.method { case "startDownload": guard let args = call.arguments as? [String: Any], let url = args["url"] as? String, let channelName = args["channelName"] as? String else { result(FlutterError(code: "INVALID_ARGS", message: "缺少必要参数", details: nil)) return } let eventChannel = FlutterEventChannel(name: channelName, binaryMessenger: methodChannel.binaryMessenger) let downloadTask = DownloadTask(url: url) { [weak self] event in guard let self = self, let entry = self.activeChannels[channelName] else { return } switch event { case .data(let data): entry.channel.send(data) case .complete: self.activeChannels.removeValue(forKey: channelName) entry.channel.endOfStream() case .error(let code, let message): self.activeChannels.removeValue(forKey: channelName) entry.channel.sendError(code, message: message, details: nil) } } eventChannel.setStreamHandler(StreamHandler(task: downloadTask)) activeChannels[channelName] = (channel: eventChannel, task: downloadTask) downloadTask.start() result(nil) case "disposeChannel": guard let channelName = call.arguments as? String else { result(nil) return } activeChannels.removeValue(forKey: channelName)?.task.cancel() result(nil) default: result(FlutterMethodNotImplemented) } } } // 下载事件枚举 enum DownloadEvent { case data(Data) case complete case error(String, String?) } // 模拟下载任务 class DownloadTask { private let url: String private let callback: (DownloadEvent) -> Void private var isCancelled = false init(url: String, callback: @escaping (DownloadEvent) -> Void) { self.url = url self.callback = callback } func start() { // 实现实际下载逻辑,比如使用URLSession进行分块下载 // 示例:模拟分块发送数据 DispatchQueue.global().async { [weak self] in guard let self = self, !self.isCancelled else { return } for i in 0..<10 { if self.isCancelled { break } let mockData = "Chunk \(i)".data(using: .utf8)! self.callback(.data(mockData)) Thread.sleep(forTimeInterval: 0.5) } self.callback(.complete) } } func cancel() { isCancelled = true } } // 事件通道流处理器 class StreamHandler: NSObject, FlutterStreamHandler { private let task: DownloadTask init(task: DownloadTask) { self.task = task super.init() } func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { // 可在此处将events关联到DownloadTask,用于直接发送事件 return nil } func onCancel(withArguments arguments: Any?) -> FlutterError? { task.cancel() return nil } }
关键注意事项
- 唯一通道标识:通过URL编码+时间戳生成唯一通道名称,避免不同下载请求的通道冲突。
- 资源清理:在下载完成、出错或用户取消监听时,必须从原生端的活跃通道映射中移除对应条目,并终止下载任务,防止内存泄漏。
- 插件销毁处理:在插件从引擎解绑时,清理所有剩余的下载任务和通道引用。
内容的提问来源于stack exchange,提问作者Hammerhead
相关产品推荐
相关产品推荐

