In Flutter, the MethodChannel
communication between Flutter (Dart) and the native platform (Android/iOS) involves both Flutter’s thread and the native platform’s threads. The threads used for MethodChannel
operations depend on how the method calls are invoked and handled.
Threads Used by MethodChannel
:
- Flutter Side (Dart Thread):
- When you call
MethodChannel.invokeMethod()
from Flutter, it runs on the main (UI) thread of the Dart side. - Dart code in Flutter runs on the main thread, also called the UI thread. This means the
MethodChannel.invokeMethod()
call is made from the same thread that is responsible for rendering the UI. - The method call is asynchronous (via
async
/await
), so the Dart code continues executing without blocking the UI while waiting for the native platform’s response.
- When you call
- Native Side (Android/iOS Threads):
- When the platform method (native method) is invoked via
MethodChannel
, the method handler (the callback that handles the method call) runs on the platform’s main thread by default. - On Android, the method handler is executed on the UI thread (Main thread) of the Android application.
- On iOS, similarly, the method call is handled on the main thread (UI thread).
- When the platform method (native method) is invoked via
Thread Considerations:
- Dart (Flutter) UI Thread:
MethodChannel.invokeMethod()
is called on Flutter’s main thread, which handles the UI. This means you should avoid blocking this thread with heavy computations, as it can cause UI lag or freezes. - Native (Android/iOS) Main Thread: By default, the native method handlers in Android and iOS also run on the main thread, which is responsible for handling UI and input events on the native side. If the native method performs a long-running or intensive operation (e.g., reading a large file, accessing the network, querying a database), it can block the UI and cause the app to become unresponsive.
Recommendation for Handling Threads:
On the Flutter (Dart) Side:
- Call
MethodChannel.invokeMethod()
from the UI thread (main Dart thread). Since this method is asynchronous, it will not block the UI, but you should avoid performing any heavy computations in the same function or callback. - Use
async
/await
for handling the result from the native platform, ensuring that the Dart code remains responsive.
On the Native (Android/iOS) Side:
- If the native method performs lightweight tasks (e.g., returning the battery level, device info), handling it on the main thread is fine.
- For heavy or long-running tasks (e.g., file I/O, network requests, database access, sensor data processing), you should offload these operations to a background thread to avoid blocking the platform’s UI thread.
For Android (Kotlin/Java):
You can use background threading mechanisms such as:
AsyncTask
(deprecated, but still used in older Android code).- Kotlin Coroutines (recommended).
- Java Threads or Executors.
Here’s an example using Kotlin Coroutines for offloading a task to a background thread in Android:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
import kotlinx.coroutines.* class MainActivity: FlutterActivity() { private val CHANNEL = "samples.flutter.dev/battery" override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) MethodChannel(flutterEngine?.dartExecutor?.binaryMessenger, CHANNEL).setMethodCallHandler { call, result -> if (call.method == "getBatteryLevel") { // Perform the task on a background thread using Kotlin coroutines CoroutineScope(Dispatchers.IO).launch { val batteryLevel = getBatteryLevel() withContext(Dispatchers.Main) { // Send result back to Flutter on the main thread if (batteryLevel != -1) { result.success(batteryLevel) } else { result.error("UNAVAILABLE", "Battery level not available.", null) } } } } else { result.notImplemented() } } } // Simulate a long-running task or platform API access private fun getBatteryLevel(): Int { Thread.sleep(2000) // Simulate delay return 85 // Example battery level } } |
Dispatchers.IO
: Offloads the task to a background thread for I/O operations.Dispatchers.Main
: Returns the result on the main thread after the background task is completed.
For iOS (Swift):
You can use Grand Central Dispatch (GCD) to move heavy tasks to a background thread.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
import Flutter import UIKit public class SwiftMyAppPlugin: NSObject, FlutterPlugin { public static func register(with registrar: FlutterPluginRegistrar) { let channel = FlutterMethodChannel(name: "samples.flutter.dev/battery", binaryMessenger: registrar.messenger()) let instance = SwiftMyAppPlugin() registrar.addMethodCallDelegate(instance, channel: channel) } public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { if (call.method == "getBatteryLevel") { // Run the task on a background thread DispatchQueue.global(qos: .background).async { let batteryLevel = self.getBatteryLevel() // Return result to Flutter on the main thread DispatchQueue.main.async { if batteryLevel != -1 { result(batteryLevel) } else { result(FlutterError(code: "UNAVAILABLE", message: "Battery level not available", details: nil)) } } } } else { result(FlutterMethodNotImplemented) } } private func getBatteryLevel() -> Int { // Simulate a long task or battery level query Thread.sleep(forTimeInterval: 2) return 85 // Example battery level } } |
DispatchQueue.global(qos: .background)
: Executes the task in a background thread.DispatchQueue.main.async
: Sends the result back to Flutter on the main (UI) thread after the background task finishes.
Best Practices:
- Offload Heavy Tasks to Background Threads:
- On the native side (Android/iOS), avoid blocking the main thread with long-running tasks. Use background threads, coroutines, or GCD for such operations.
- Return Results to Flutter on the Main Thread:
- After completing the background work, return the result to Flutter on the main thread. This ensures the result is processed correctly by the Flutter framework and the UI remains responsive.
- Flutter’s Async/Await:
- On the Dart side, use
async
/await
for handling results from the native platform. This keeps the UI responsive while waiting for the platform response.
- On the Dart side, use
Conclusion:
- Thread usage in
MethodChannel
:- Flutter/Dart calls are made on the main UI thread.
- Native method handlers (Android/iOS) by default run on the platform’s main thread.
- Recommendation:
- For lightweight tasks (e.g., getting simple device info), handling them on the main thread is fine.
- For long-running tasks, use background threads or async mechanisms (e.g., Kotlin Coroutines, Java Executors, or Swift GCD) on the native side to ensure the UI remains responsive.