Detailed explanation of the use of the Platform Channel of the Flutter series

PS: In many situations, 80% of the known effects stem from 20% of the possible causes.

The previous articles introduced the basics of Flutter development such as Navigator components, Flex layout, image loading, Widget life cycle, and hybrid development. The articles are as follows:

The following introduces the use of Platform Channel in Flutter hybrid development. The main contents are as follows:

  1. Platform channel introduction
  2. Platform data type comparison
  3. BasicMessageChannel
  4. MethodChannel
  5. EventChannel

Platform channel introduction

Platform Channel is an asynchronous message channel. The message will be encoded into a binary message before being sent, and the received binary message will be decoded into a Dart value. The message type it transmits can only be the value supported by the corresponding decoder, and all decodes All devices support empty messages, and the communication architecture between Native and Flutter is shown in the figure below:

Three different types of PlatformChannel are defined in Flutter, the main three are as follows:

  • BasicMessageChannel: used for data transmission;
  • MethodChannel: used to transfer method calls;
  • EventChannel: used to deliver events;

Its construction method needs to specify a channel identifier, decoder, and BinaryMessenger. BinaryMessenger is a communication tool between Flutter and the platform, used to transfer binary data, set the corresponding message processor, etc.

There are two codecs, MethodCodec and MessageCodec, the former corresponds to the method and the latter corresponds to the message. BasicMessageChannel uses MessageCodec, and MethodChannel and EventChannel use MethodCodec.

Platform data type comparison

Platform Channel provides different message decoding mechanisms. For example, StandardMessageCodec provides basic data type decoding, JSONMessageCodec supports Json decoding, etc., which are automatically converted when communicating between platforms. The data types of each platform are compared as follows:

BasicMessageChannel

BasicMessageChannel is mainly used for data transmission, including binary data. With the help of BasicMessageChannel, the functions of MethodChannel and EventChannel can be realized. Here, BasicMessageChannel is used to realize the case of using Flutter resource files in Android projects. The key process is as follows:

  1. The Flutter side obtains the binary data corresponding to the image resource. BinaryCodec is used here, and the data format is ByteData;
  2. Use BasicMessageChannel to send the data corresponding to the picture;
  3. Use ByteBuffer on the Android side to receive, convert it into ByteArray, and then parse it into Bitmap and display it.

The key code on the Flutter side is as follows:

// 创建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);
}

The key code on the Android side is as follows:

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)

In addition, BasicMessageChannel combined with BinaryCodec supports the transfer of large memory data blocks.

MethodChannel

MethodChannel is mainly used to transfer methods. Natural methods and Dart methods can be passed naturally. That is, you can call Android native methods in Flutter through MethodChannel, and call Dart methods in Android. The mutual calls are all called through the invokeMethod method of MethodChannel. The same channel identifier must be used, as follows:

  1. Flutter calls Android methods

Let's use MethodChannel to realize the jump from Flutter to Android's native interface MainActivity. The Android side is as follows:

/**
 * @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()
            }
        }
    }
}

As above, you can also use the MethodChannel.Result object to call back the execution result to Flutter. The Flutter side is as follows:

/// 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 to Android"),
      ),
    );
  }

  /// 跳转到原生Activity
  void _startMainActivity() {
    
    
    platform.invokeMethod('startMainActivity', 'flutter message').then((value) {
    
    
      // 接收返回的数据
      print("value:$value");
    }).catchError((e) {
    
    
      print(e.message);
    });
  }
}
  1. Android calls Dart method

Next, call the Dart method getName in Flutter through MethodChannel, the Android code is as follows:

/**
 * @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)
        }
    }
}

The Flutter side is as follows:

/// 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 Method
  Future<dynamic> platformCallHandler(MethodCall call) async{
    
    
    switch(call.method){
    
    
      case "getName":
        return "name from flutter";
        break;
    }
  }
}

EventChannel

EventChannel is mainly used for one-way calls from Flutter to the native. Its usage is similar to broadcasting in Android. The native interface is responsible for sending Event. Flutter can register and listen to it. Not to mention, just look at the code. The Android code is as follows:

/// 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!!)
}

The Flutter side is as follows:

/// 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),
        ));
  }
}

The above is the use of the Flutter platform channel. You can get the source code in the reply keyword [Channel] of the official account .

Guess you like

Origin blog.csdn.net/jzman/article/details/114242678