Flutter系列之Platform Channel使用詳解

躬行之發表於2021-02-28

PS:很多情境中,80% 的已知效果源自 20% 的可能原因。

前面幾篇文章介紹了 Navigator 元件、Flex 佈局、圖片載入、Widget 生命週期、混合開發等 Flutter 開發基礎知識, 文章如下:

下面介紹一下 Flutter 混合開發中 Platform Channel 的使用,主要內容如下:

  1. 平臺通道介紹
  2. 平臺資料型別對照
  3. BasicMessageChannel
  4. MethodChannel
  5. EventChannel

平臺通道介紹

Platform Channel 是一個非同步訊息通道,訊息在傳送之前會編碼成二進位制訊息,接收到的二進位制訊息會解碼成 Dart 值,其傳遞的訊息型別只能是對應的解編碼器支援的值,所有的解編碼器都支援空訊息,其 Native 與 Flutter 通訊架構如下圖所示:

Flutter 中定義了三種不同型別的 PlatformChannel,主要有三種如下:

  • BasicMessageChannel:用於資料傳遞;
  • MethodChannel:用於傳遞方法呼叫;
  • EventChannel:用於傳遞事件;

其構造方法都需指定一個通道標識、解編碼器以及 BinaryMessenger,BinaryMessenger 是一個 Flutter 與平臺的通訊工具,用來傳遞二進位制資料、設定對應的訊息處理器等。

解編碼器有兩種分別是 MethodCodec 和 MessageCodec,前者對應方法後者對應訊息,BasicMessageChannel 使用的是 MessageCodec,MethodChannel 和 EventChannel 使用的是 MethodCodec。

平臺資料型別對照

Platform Channel 提供不同的訊息解碼機制,如 StandardMessageCodec 提供基本資料型別的解編碼、JSONMessageCodec 支援 Json 的解編碼等,在平臺之間通訊時都會自動轉換,各平臺資料型別對照如下:

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

EventChannel

EventChannel 主要用於 Flutter 到原生之間的單向呼叫,其使用方式類似 Android 中的廣播,原生介面負責 Event 的傳送,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 平臺通道的使用,可以在公眾號躬行之回覆關鍵字【Channel】獲取原始碼。

相關文章