https://docs.flutter.dev/platform-integration/platform-channels
1. 메소드 채널이란?
플러터에서 native code(android, ios)를 동작하고 싶을 때 사용하는 인터페이스이다.
2. 어떻게 만드는데?
먼저 flutter 코드에 아래와 같이 methodChannel을 생성한다. 생성할 때 채널이름을 넘겨준다.
그리고 채널을 통해 네이티브 메소드를 호출한다. 이때 method호출은 async/await를 지원한다.
static const channel = MethodChannel('채널이름');
var result = await channel.invokeMethod('네이티브메소드 이름');
3. 네이티브 코드는?
Android
MainAcitivity(혹은 FlutterActivity를 상속받는놈)에 configureFlutterEngine()안에 메소드 채널을 setMethodCallHandler()을 이용하여 정의하면 된다.
import androidx.annotation.NonNull
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
class MainActivity: FlutterActivity() {
private val CHANNEL_NAME = "채널이름"
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
var channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL_NAME).setMethodCallHandler {
call, result ->
// 이때 메소드는 메인스레드에서 호출된다.
}
}
}
위 코드에서 람다함수의 인풋으로 call, result를 전달받는것을 볼 수 있다.
여기서 call은 method와 argument라는 두가지 프로퍼티를 가지고 있다.
Call
- method : Flutter에서 호출한 함수의 이름정보이다
- argument : Flutter에서 네이티브 함수를 호출 할 때
Result
result의 경우 success와 error그리고 notImplemented 메소드가 있다
- success : 메소드의 수행이 성공할 때 호출해야하는 메소드. Object값으로 result를 올려준다
- error : 메소드의 수행이 실패할 때 호출되는 메소드. string으로 errorCode와 errorMessage, Object로 errorDetails를 올려준다
- notImplemented : 구현되지 않았을 때 호출해준다
위 정보를 바탕으로 메소드를 작성해보면 아래처럼 된다
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
// This method is invoked on the main thread.
call, result ->
if (call.method == "네이티브 메소드 이름") {
val ret = 네이티브메소드()
if (ret != -1) {
result.success(ret)
} else {
result.error("UNAVAILABLE", "method not available.", null)
}
} else {
result.notImplemented()
}
}
iOS
ios도 안드로이드와 유사하다. 먼저 application 메소드를 override한다. 그다음 MethodChannel을 만든 후 메소드 핸들러를 달아준다.
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
let channel = FlutterMethodChannel(name: "채널이름",
binaryMessenger: controller.binaryMessenger)
channel.setMethodCallHandler({
(call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
// This method is invoked on the UI thread.
// Handle battery messages.
})
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
채널의 구현부도 안드로이드와 동일하다. call을 이용해 플러터에서 호출한 메소드 이름을 확인한 뒤, 네이티브 메소드를 호출한다.
channel.setMethodCallHandler({
[weak self] (call: FlutterMethodCall, result: FlutterResult) -> Void in
// This method is invoked on the UI thread.
guard call.method == "네이티브메소드" else {
result(FlutterMethodNotImplemented)
return
}
self?.네이티브메소드(result: result)
})
4. 백그라운드에서 실행하기
위 코드들은 한가지 문제가 있다. 전무 메인스레드에서 실행된다. 실력있는 개발자라면 백그라운드에서 실행시켜서 성능을 향상시켜야한다.
Flutter
플러터는 멀티 스레드를 지원하지 않으니 isolate로 메인스레드와 분리해서 실행시키자
import 'package:flutter/services.dart';
import 'package:shared_preferences/shared_preferences.dart';
void _isolateMain(RootIsolateToken rootIsolateToken) async {
BackgroundIsolateBinaryMessenger.ensureInitialized(rootIsolateToken);
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
print(sharedPreferences.getBool('isDebug'));
}
void main() {
RootIsolateToken rootIsolateToken = RootIsolateToken.instance!;
Isolate.spawn(_isolateMain, rootIsolateToken);
}
Android / iOS
모바일에서는 flutter에서 task queue 라는 api를 제공한다. task queue를 넣어서 채널을 만들면 되는것이다.
Android
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
val taskQueue =
flutterPluginBinding.binaryMessenger.makeBackgroundTaskQueue()
channel = MethodChannel(flutterPluginBinding.binaryMessenger,
"com.example.foo",
StandardMethodCodec.INSTANCE,
taskQueue)
channel.setMethodCallHandler(this)
}
iOS
public static func register(with registrar: FlutterPluginRegistrar) {
let taskQueue = registrar.messenger.makeBackgroundTaskQueue()
let channel = FlutterMethodChannel(name: "com.example.foo",
binaryMessenger: registrar.messenger(),
codec: FlutterStandardMethodCodec.sharedInstance,
taskQueue: taskQueue)
let instance = MyPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}
5. 네이티브에서 데이터를 stream으로 올려주려면?
이럴 땐 method channel이 아닌, event channel을 사용하면 된다.
하기 개념을 기억하자
- 이벤트채널 : 메소드채널처럼, 이벤트가 발생하면 플러터에게 이벤트를 전달하는 채널이다
- 이벤트핸들러 : 실질적으로 이벤트를 일으키는 클래스이다. onListen과 onCancel 메소드를 구현해야한다.
핸들러의 구현법은 다음과 같다
- onListen에서 eventSink를 받아온다. 이 함수는 핸들러를 이벤트 채널에 붙일 때 호출된다
- onCancle에서 eventSink를 null로 반납한다
- (optional) 싱글톤으로 만들면 개발하기에 편리함
- Android의 경우 success, iOS의 경우 sink 함수로 플러터에 이벤트를 전달 할 수 있다.
Android
import android.os.Handler
import android.os.Looper
import io.flutter.plugin.common.EventChannel
class MyHandler: EventChannel.StreamHandler {
private var handler = Handler(Looper.getMainLooper())
private var eventSink: EventChannel.EventSink? = null
companion object {
private val instance = MyHandler()
fun getInstance() = instance
}
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
eventSink = events
}
override fun onCancel(arguments: Any?) {
eventSink = null
}
fun triggerEvent(result: String) {
handler.post {
eventSink?.success(result)
}
}
}
iOS
import Foundation
class MyHandler: NSObject, FlutterStreamHandler {
static let shared = MyHandler()
private var eventSink: FlutterEventSink?
private override init() {}
func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
eventSink = events
return nil
}
func onCancel(withArguments arguments: Any?) -> FlutterError? {
eventSink = nil
return nil
}
func triggerEvent(result: String) {
guard let sink = eventSink else { return }
sink(result)
}
}
다음으로 네이티브 코드에 이벤트채널을 생성한 뒤 앞서 만든 핸들러를 등록해준다.
Android (MainActivity.kt)
val eventChannel = EventChannel(flutterEngine.dartExecutor.binaryMessenger, "MyEventChannel");
eventChannel.setStreamHandler(MyHandler())
iOS (AppDelegate)
let eventChannel = FlutterEventChannel(name: "MyEventChannel", binaryMessenger: controller.binaryMessenger)
eventChannel.setStreamHandler(MyHandler())
마지막으로 Flutter에서 이벤트를 아래처럼 받으면 된다.
var eventChannel = EventChannel("MyEventChannel");
var eventStream = eventChannel.receiveBroadcastStream()
..listen((event) {
이벤트 처리 코드 작성
});
'Flutter > Flutter 필수개념' 카테고리의 다른 글
[Flutter] Sliver app bar 배우기 (0) | 2021.12.21 |
---|---|
[Flutter] Inherited Widget 배우기 (3) | 2021.12.16 |
[Flutter] Matrix4 이해하기 (0) | 2021.12.15 |
[Flutter] BuildContext 와 of 함수 (0) | 2021.12.14 |
[Flutter] state 관리와 provider (0) | 2021.12.14 |
댓글