(Original) The way Flutter communicates with Native: MethodChannel

Preface

As more and more Flutter hybrid development projects are developed, we also have a practical scenario,
which is how Flutter communicates with the native (Native) side.
Currently, it seems that there are roughly three ways, namely: MethodChannel
EventChannel
MessageChannel
.
The implementation methods are actually very similar, and the basic codes are similar
. But in order to let everyone know the difference between the three more clearly, this blog is created. This
article mainly talks about the MethodChannel method
, which is also the most commonly used method in actual development.
You can read the next blog for the other two methods:
EventChannel and BasicMessageChannel
, so now we officially start

accomplish

FlutterActivity

First of all, we need to know that the so-called pages of Flutter actually run on Activity in the final analysis.
This is a bit like our WebView. What ultimately hosts the page is still an Activity.
In Flutter, what hosts the page is actually an activity called FlutterActivity class
So our interaction is actually the interaction between Flutter Page and this FlutterActivity.
First, we create a new ordinary Activity, which is the home page.

class MainActivity : AppCompatActivity() {
    
    


  override fun onCreate(savedInstanceState: Bundle?) {
    
    
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    findViewById<TextView>(R.id.textview).setOnClickListener {
    
    
      startActivity(MyFlutterActivity.withCachedEngine("your_engine_id").build(this))
    }
  }

}

This homepage is our most basic AppCompatActivity,
it is not the focus.
Click the button to enter the page we host Flutter: MyFlutterActivity
, which inherits a class called FlutterFragmentActivity.
This page can officially interact with the Flutter page in its own page.
The relevant interaction code is also written here.

Flutter side calls Android side

Let’s first look at how we implement it on the Flutter side.
First, we can create a new file ForNativePlugin.dart.
This file is used to define the method to be called on the Android side.

class ForNativePlugin {
    
    
  MethodChannel methodChannel = const MethodChannel("ForNativePlugin");

  /// 调用安卓,得到当前时间
  Future<String> getTime() async {
    
    
    var nowTime = await methodChannel.invokeMethod("getTime");
    return nowTime;
  }


  /// 显示安卓土司
  showAndroidToast(String msg) async {
    
    
    try {
    
    
      await methodChannel.invokeMethod('toast', {
    
    'msg': msg});
    } on PlatformException catch (e) {
    
    
      print(e.toString());
    }
  }
}

As you can see, a MethodChannel class is created.
This is the key class for interaction.
The name attribute of MethodChannel must be agreed upon for both-end development.
What is agreed here is a ForNativePlugin string.
Then two methods are defined
. The implementation of the method is actually to use methodChanneld.invokeMethod. To trigger the method invokeMethod on the Android side
, write the method name on the Android side as one parameter, and write the name of the parameter on the second parameter. There can be multiple input parameters.

After writing, we can create this class in Flutter

ForNativePlugin forNativePlugin = ForNativePlugin();

Then use the tool class we wrote to trigger the methods on the Android side when needed.
For example:

forNativePlugin.showAndroidToast("当前时间为:${
      
      forNativePlugin.getTime()}");

In this way, we can call the Android side to get the time, and then let the Android side help us return the time and
pop it up through the Toast method
. So how to write the Android side?
It is also very simple. The Android side also needs to define a class
to implement the onMethodCall method of the MethodCallHandler interface.

class MyMethodCallHandler(var context: Context) : MethodCallHandler {
    
    
  override fun onMethodCall(methodCall: MethodCall, result: MethodChannel.Result) {
    
    
    when (methodCall.method) {
    
    
      "getTime" -> {
    
    
        result.success(SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(System.currentTimeMillis()))
      }
      "toast" -> {
    
    
        if (methodCall.hasArgument("msg") && !TextUtils.isEmpty(
            methodCall.argument<Any>("msg").toString()
          )
        ) {
    
    
          Toast.makeText(context, methodCall.argument<Any>("msg").toString(), Toast.LENGTH_LONG)
            .show()
        } else {
    
    
//          result.error()
          Toast.makeText(context, "msg 不能为空", Toast.LENGTH_SHORT).show()
        }
      }
      else -> {
    
    
        // 表明没有对应实现
        result.notImplemented()
      }
    }
  }
}

The onMethodCall method is used to handle the logic we want to be triggered.
Then we come to the class that inherits FlutterFragmentActivity.
First, we also define a MethodChannel.

private lateinit var flutterMethodChannel: MethodChannel

Then let our Activity implement the configureFlutterEngine method

  override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
    
    
    super.configureFlutterEngine(flutterEngine)
    //通过MethodChannel与原生通信,ForNativePlugin为约定字段
    flutterMethodChannel = MethodChannel(flutterEngine.dartExecutor, "ForNativePlugin")
    flutterMethodChannel.setMethodCallHandler(MyMethodCallHandler(this))
  }

As you can see, this is actually a registration link.
Register our MyMethodCallHandler class into MethodChannel
. At this point, the Flutter side calls the Android side code.

One additional point:
When the Android side is triggered,
you can tell Flutter whether the request result is successful or not through the result class.
result.success(), success,
result.error(), failure
, result.notImplemented(). This method has no corresponding implementation.

Android side calls Flutter side

Calling the Flutter side on the Android side is actually similar.
First, the Android side:
we can get a FlutterEngine by calling the getFlutterEngine method
and then use the getDartExecutor of this FlutterEngine to operate.

  fun toFlutter(num1: Int, num2: Int) {
    
    
    flutterEngine?.dartExecutor?.let {
    
    
      nativeMethodChannel = MethodChannel(it, "ForFlutterPlugin")
      nativeMethodChannel.invokeMethod(
        "callFlutterSum",
        listOf<Int>(num1, num2),
        object : MethodChannel.Result {
    
    
          override fun success(result: Any?) {
    
    
            Log.d("MyFlutterActivity", "计算结果为 = $result")
          }

          override fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) {
    
    
            Log.d("MyFlutterActivity", "出错了:$errorCode, $errorMessage, $errorDetails")
          }

          override fun notImplemented() {
    
    
            Log.d("MyFlutterActivity", "notImplemented")
          }
        })
    }
  }

As you can see here, a MethodChannel is also defined. The agreed field is: ForFlutterPlugin
and then calls the callFlutterSum method on the Flutter side to obtain the calculated return value.
One thing to note here is that the calling Fluter code must be in the main thread.

Then come to the Flutter side
and define the MethodChannel on the Flutter side:

MethodChannel methodChannel = const MethodChannel("ForFlutterPlugin");

And perform registration and callback monitoring, you can register in the initState method:

  
  void initState() {
    
    
    // TODO: implement initState
    super.initState();
    methodChannel.setMethodCallHandler(
      (call) {
    
    
        switch (call.method) {
    
    
          case 'callFlutterSum':
            print('call callMe : arguments = ${
      
      call.arguments}');
            List<Object?> numbers = call.arguments;
            var num1=numbers[0] as int;
            var num2=numbers[1] as int;
            //触发Android端的success方法
            return Future.value(num1+num2);
            //触发Android端的error方法,errorCode为error,errorMessage为:调用出错,不能传details
            // return Future.error("调用出错");
            //触发Android端的error方法,可以额外传details对象
            throw PlatformException(code: 'errorCode', message: 'Error occurred',details: "出错了");
          default:
            print('Unknowm method ${
      
      call.method}');
            //触发Android端的notImplemented方法
            throw MissingPluginException();
        }
      },
    );
  }

At this point, the work on the Flutter side has been completed
. It can be seen that the mutual calls between the two ends are basically divided into three steps:
1: The called side first writes the called processing logic
2: The called side creates the MethodChannel and calls setMethodCallHandler to register.
3: The calling side creates a MethodChannel, agrees on the fields, and then uses invokeMethod to actively call the method.

Notice

When you write double-ended communication, if you find that the call is not triggered, you can pay attention to the following reasons:
1: Whether it inherits FlutterFragmentActivity or FlutterActivity
2: Whether the agreed fields are consistent
3: Whether the method name and parameters are consistent

Source code

Source code address of this blog:
MethodChannel

Relevant information

This is a comprehensive & detailed study guide on the communication methods between Android Native and Flutter.

Guess you like

Origin blog.csdn.net/Android_xiong_st/article/details/131904836