banner
jzman

jzman

Coding、思考、自觉。
github

FlutterシリーズのPlatform Channel使用詳細

PS:多くの状況において、80% の既知の効果は 20% の可能な原因に起因しています。

前の数記事では、Navigator コンポーネント、Flex レイアウト、画像の読み込み、Widget のライフサイクル、混合開発など、Flutter 開発の基本知識を紹介しました。記事は以下の通りです:

次に、Flutter 混合開発における Platform Channel の使用について紹介します。主な内容は以下の通りです:

  1. プラットフォームチャネルの紹介
  2. プラットフォームデータ型の対照
  3. BasicMessageChannel
  4. MethodChannel
  5. EventChannel

プラットフォームチャネルの紹介#

Platform Channel は非同期メッセージチャネルで、メッセージは送信前にバイナリメッセージにエンコードされ、受信したバイナリメッセージは Dart 値にデコードされます。伝達されるメッセージの型は、対応するデコーダーがサポートする値のみであり、すべてのデコーダーは空のメッセージをサポートしています。Native と Flutter の通信アーキテクチャは以下の図の通りです:

image

Flutter では、3 種類の異なる PlatformChannel が定義されており、主に以下の 3 つがあります:

  • BasicMessageChannel:データ伝達に使用;
  • MethodChannel:メソッド呼び出しの伝達に使用;
  • EventChannel:イベントの伝達に使用;

そのコンストラクタは、チャネル識別子、デコーダー、および BinaryMessenger を指定する必要があります。BinaryMessenger は Flutter とプラットフォームの通信ツールで、バイナリデータの伝達、対応するメッセージハンドラーの設定などに使用されます。

デコーダーには MethodCodec と MessageCodec の 2 種類があり、前者はメソッドに、後者はメッセージに対応します。BasicMessageChannel は MessageCodec を使用し、MethodChannel と EventChannel は MethodCodec を使用します。

プラットフォームデータ型の対照#

Platform Channel は異なるメッセージデコードメカニズムを提供します。例えば、StandardMessageCodec は基本データ型のデコードを提供し、JSONMessageCodec は Json のデコードをサポートします。プラットフォーム間で通信する際には自動的に変換されます。各プラットフォームのデータ型対照は以下の通りです:

image

BasicMessageChannel#

BasicMessageChannel は主にデータ伝達に使用され、バイナリデータを含みます。BasicMessageChannel を使用することで、MethodChannel と EventChannel の機能を実現できます。ここでは、BasicMessageChannel を使用して Android プロジェクトで Flutter リソースファイルを使用するケースを示します。重要なプロセスは以下の通りです:

  1. Flutter 側で画像リソースに対応するバイナリデータを取得します。ここでは BinaryCodec を使用し、データ形式は ByteData です;
  2. BasicMessageChannel を使用して画像に対応するデータを送信します;
  3. Android 側で ByteBuffer を使用して受信し、ByteArray に変換して Bitmap として表示します。

Flutter 側の重要なコードは以下の通りです:

// BasicMessageChannelを作成
_basicMessageChannel = BasicMessageChannel<ByteData>("com.manu.image", BinaryCodec());

// assets内の画像に対応するByteDataデータを取得
rootBundle.load('images/miao.jpg').then((value) => {
  _sendStringMessage(value)
});

// 画像データを送信
_sendStringMessage(ByteData byteData) async {
  await _basicMessageChannel.send(byteData);
}

Android 側の重要なコードは以下の通りです:

override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
    super.configureFlutterEngine(flutterEngine)
    Log.i(tag, "configureFlutterEngine")
    // メッセージハンドラーを設定
    BasicMessageChannel<ByteBuffer>(
        flutterEngine.dartExecutor, "com.manu.image", BinaryCodec.INSTANCE
    ).setMessageHandler { message, reply ->
        Log.i(tag, "configureFlutterEngine > message:$message")
        // データ変換:ByteBuffer->ByteArray
        val byteBuffer = message as ByteBuffer
        imageByteArray = ByteArray(byteBuffer.capacity())
        byteBuffer.get(imageByteArray)
    }

    // FlutterからAndroidへの遷移メソッドハンドラーを設定
    MethodChannel(flutterEngine.dartExecutor, channel).setMethodCallHandler { call, result ->
        Log.i(tag, "configureFlutterEngine > method:${call.method}")
        if ("startBasicMessageChannelActivity" == call.method) {
            // 画像データを持参
            BasicMessageChannelActivity.startBasicMessageChannelActivity(this,imageByteArray)
        }
    }
}

// Flutterのassetsからの画像を表示
val imageByteArray = intent.getByteArrayExtra("key_image")
val bitmap = BitmapFactory.decodeByteArray(imageByteArray,0,imageByteArray.size)
imageView.setImageBitmap(bitmap)

さらに、BasicMessageChannel は BinaryCodec と組み合わせることで、大きなメモリデータブロックの伝達をサポートしています。

MethodChannel#

MethodChannel は主にメソッドの伝達に使用され、Native メソッドと Dart メソッドを伝達できます。つまり、MethodChannel を介して Flutter 内で Android のネイティブメソッドを呼び出し、Android 内で Dart メソッドを呼び出すことができます。相互呼び出しはすべて MethodChannel の invokeMethod メソッドを介して行われ、通信時には同じチャネル識別子を使用する必要があります。具体的には以下の通りです:

  1. Flutter が Android メソッドを呼び出す

以下の例では、MethodChannel を使用して Flutter から Android のネイティブ画面 MainActivity に遷移します。Android 側は以下の通りです:

/**
 * @desc FlutterActivity
 * @author jzman
 */
val tag = AgentActivity::class.java.simpleName;

class AgentActivity : FlutterActivity() {
    val tag = AgentActivity::class.java.simpleName;
    private val channel = "com.manu.startMainActivity"
    private var platform: MethodChannel? = null;

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        Log.d(tag,"configureFlutterEngine")
        platform = MethodChannel(flutterEngine.dartExecutor, channel)
        // メソッドハンドラーを設定
        platform!!.setMethodCallHandler(StartMethodCallHandler(this@AgentActivity))
    }

    companion object{
        /**
         * NewEngineIntentBuilderを再作成する必要があります
         */
        fun withNewEngine(): MNewEngineIntentBuilder? {
            return MNewEngineIntentBuilder(AgentActivity::class.java)
        }
    }

    /**
     * カスタムNewEngineIntentBuilder
     */
    class MNewEngineIntentBuilder(activityClass: Class<out FlutterActivity?>?) :
        NewEngineIntentBuilder(activityClass!!)

    /**
     * MethodCallHandlerを実装
     */
    class StartMethodCallHandler(activity:Activity) : MethodChannel.MethodCallHandler{
        private val context:Activity = activity
        override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
            if ("startMainActivity" == call.method) {
                Log.i(tag,"arguments:"+call.arguments)
                startMainActivity(context)
                // Flutterに実行結果をコールバック
                result.success("success")
            } else {
                result.notImplemented()
            }
        }
    }
}

上記のように、MethodChannel.Result オブジェクトを使用して Flutter に実行結果をコールバックできます。Flutter 側は以下の通りです:

/// State
class _PageState extends State<PageWidget> {
  MethodChannel platform;

  @override
  void initState() {
    super.initState();
    platform = new MethodChannel('com.manu.startMainActivity');
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      width: double.infinity,
      margin: EdgeInsets.fromLTRB(8, 8, 8, 0),
      child: RaisedButton(
        onPressed: () {
          _startMainActivity();
        },
        child: Text("FlutterからAndroidへ"),
      ),
    );
  }

  /// ネイティブActivityに遷移
  void _startMainActivity() {
    platform.invokeMethod('startMainActivity', 'flutter message').then((value) {
      // 戻り値を受け取る
      print("value:$value");
    }).catchError((e) {
      print(e.message);
    });
  }
}
  1. Android が Dart メソッドを呼び出す

以下の例では、MethodChannel を使用して Flutter 内の Dart メソッド getName を呼び出します。Android 側のコードは以下の通りです:

/**
 * @desc MainActivity
 * @author jzman
 */
class MainActivity : FlutterActivity() {
    private val tag = MainActivity::class.java.simpleName;
    private val channel = "com.manu.startMainActivity"
    private var methodChannel: MethodChannel? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        btnGetDart.setOnClickListener {
            getDartMethod()
        }
    }

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        Log.i(tag,"configureFlutterEngine")
        methodChannel = MethodChannel(flutterEngine.dartExecutor,channel)
    }

    private fun getDartMethod(){
        methodChannel?.invokeMethod("getName",null, object :MethodChannel.Result{
            override fun success(result: Any?) {
                Log.i(tag,"success: "+result.toString())
                Toast.makeText(this@MainActivity,result.toString(),Toast.LENGTH_LONG).show()
            }

            override fun error(errorCode: String,errorMessage: String?,errorDetails: Any?) {
                Log.i(tag,"error")
            }

            override fun notImplemented() {
                Log.i(tag,"notImplemented")
            }
        })
    }

    companion object{
        fun startMainActivity(context: Context) {
            val intent = Intent(context, MainActivity::class.java)
            context.startActivity(intent)
        }
    }
}

Flutter 側は以下の通りです:

/// State
class _PageState extends State<PageWidget> {
  MethodChannel platform;

  @override
  void initState() {
    super.initState();
    platform = new MethodChannel('com.manu.startMainActivity');

    // AndroidがFlutterメソッドを呼び出すのをリッスン
    platform.setMethodCallHandler(platformCallHandler);
  }

  @override
  Widget build(BuildContext context) {
    return Container();
  }
  /// Flutterメソッド
  Future<dynamic> platformCallHandler(MethodCall call) async{
    switch(call.method){
      case "getName":
        return "flutterからの名前";
        break;
    }
  }
}

EventChannel#

EventChannel は主に Flutter からネイティブへの一方向の呼び出しに使用され、その使用方法は Android のブロードキャストに似ています。ネイティブインターフェースがイベントの送信を担当し、Flutter 側がリスナーを登録するだけです。詳細はコードを見てください。Android 側のコードは以下の通りです:

/// Android
class MFlutterFragment : FlutterFragment() {
    // ここではFragmentを使用していますが、Activityでも同様です
    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        Log.d(tag,"configureFlutterEngine")
        EventChannel(flutterEngine.dartExecutor,"com.manu.event").setStreamHandler(object:
            EventChannel.StreamHandler{
            override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
                Log.i(tag,"configureFlutterEngine > onListen")
                // EventSinkがイベント通知を送信
                events?.success("event message")
            }

            override fun onCancel(arguments: Any?) {
                Log.i(tag,"configureFlutterEngine > onCancel")
            }
        })
    }

    companion object{
        fun withNewEngine(): NewEngineFragmentBuilder? {
            return MNewEngineIntentBuilder(
                MFlutterFragment::class.java
            )
        }
    }

    class MNewEngineIntentBuilder(activityClass: Class<out FlutterFragment?>?) :
        NewEngineFragmentBuilder(activityClass!!)
}

Flutter 側は以下の通りです:

/// State
class EventState extends State<EventChannelPage> {
  EventChannel _eventChannel;
  String _stringMessage;
  StreamSubscription _streamSubscription;

  @override
  void initState() {
    super.initState();
    _eventChannel = EventChannel("com.manu.event");
    // Eventイベントをリッスン
    _streamSubscription =
        _eventChannel.receiveBroadcastStream().listen((event) {
      setState(() {
        _stringMessage = event;
      });
    }, onError: (error) {
      print("event error$error");
    });
  }

  @override
  void dispose() {
    super.dispose();
    if (_streamSubscription != null) {
      _streamSubscription.cancel();
      _streamSubscription = null;
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("EventChannel"),
          centerTitle: true,
        ),
        body: Center(
          child: Text(_stringMessage == null ? "default" : _stringMessage),
        ));
  }
}

以上が Flutter プラットフォームチャネルの使用方法です。

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。