Flutter 基礎(十三)Flutter 與 Android 的相互通訊

NiZerin發表於2019-08-12

Flutter基礎(十三)Flutter與Android的相互通訊

前言

本來這篇文章應該講一下Flutter的外掛開發,但是在外掛開發的基礎是PlatformChannel,也就是Flutter與Android/iOS Native的通訊,理解了這一個知識點,Flutter的外掛開發也就不在話下。

1.PlatformChannel概述

Flutter不能完成所有Native的功能,因此需要Flutter與Native的通訊,Flutter提供了一套Platform Channel的機制,來滿足Flutter與Native通訊的需求。
下面是PlatformChannel架構。
ZLyOw4.png
圖中可以看到,Flutter是Client端,Native是Host,Client和host通訊是通過PlatformChannel,Client通過PlatformChannel向Host傳送訊息,Host監聽PlatformChannel並接收訊息,然後將響應結果傳送給Client。訊息和響應以非同步方式傳遞,以確保UI不阻塞。另外,PlatformChannel是雙工的,這意味著Flutter和Native可以交替做Client和Host。

Flutter定義了三種不同型別的PlatformChannel,它們分別是:

  • MethodChannel:用於傳遞方法呼叫,是比較常用的PlatformChannel。
  • EventChannel: 用於傳遞事件。
  • BasicMessageChannel:用於傳遞資料。

這幾個PlatformChannel的用法都不難,本文會以比較常用的MethodChannel來進行舉例。在此之前我們先要了解BinaryMessenger、Codec、Handler的概念。

BinaryMessenger
BinaryMessenger是PlatformChannel與Flutter端的通訊的工具,其通訊使用的訊息格式為二進位制格式資料,BinaryMessenger在Android中是一個介面,它的實現類為FlutterNativeView。

Codec
Codec是訊息編解碼器,主要用於將二進位制格式的資料轉化為Handler能夠識別的資料,Flutter定義了兩種Codec:MessageCodec和MethodCodec。MessageCodec用於二進位制格式資料與基礎資料之間的編解碼,BasicMessageChannel所使用的編解碼器是MessageCodec。MethodChannel和EventChannel所使用的編解碼均為MethodCodec。

Handler
Flutter定義了三種型別的Handler,它們與PlatformChannel型別一一對應,分別是MessageHandler、MethodHandler、StreamHandler。在使用PlatformChannel時,會為它註冊一個Handler,PlatformChannel會將該二進位制資料通過Codec解碼為轉化為Handler能夠識別的資料,並交給Handler處理。當Handler處理完訊息之後,會通過回撥函式返回result,將result通過編解碼器編碼為二進位制格式資料,通過BinaryMessenger傳送回Flutter端。

MethodChannel可以實現Flutter呼叫Android,也可以實現Android呼叫Flutter,這裡分別來進行舉例。

2.Flutter呼叫Android

這裡實現一個Android的簡單的功能:彈出一個AlertDialog,然後在Flutter中呼叫這一功能。

Android端實現
先在MainActivity中實現功能,如下所示。

package com.example.platform_channel;

import android.app.AlertDialog;
import android.os.Bundle;
import io.flutter.app.FlutterActivity;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugins.GeneratedPluginRegistrant;

public class MainActivity extends FlutterActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        GeneratedPluginRegistrant.registerWith(this);//1

        MethodChannel methodChannel = new MethodChannel(getFlutterView(), "com.example.platform_channel/dialog");//2
        methodChannel.setMethodCallHandler(new MethodChannel.MethodCallHandler() {//3
            @Override
            public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {
                if ("dialog".equals(methodCall.method)) {
                    if (methodCall.hasArgument("content")) {
                        showAlertDialog();
                        result.success("彈出成功");
                    } else {
                        result.error("error", "彈出失敗", "content is null");
                    }
                } else {
                    result.notImplemented();
                }
            }
            private void showAlertDialog() {
                AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
                builder.setPositiveButton("確定", null);
                builder.setTitle("Flutter呼叫Android");
                builder.show();
            }
        });
    }
}

註釋1處用於註冊外掛,這個是建立Flutter工程時MainActivity自帶的。
註釋2處建立一個MethodChannel,它有兩個引數,一個是getFlutterView方法,用於獲取FlutterView,FlutterView實現了BinaryMessenger介面。一個是MethodChannel的Name,這個Name要保證是唯一的,後面Flutter端實現中會用到這個Name。
註釋3處為methodChannel註冊一個MethodCallHandler,用於監聽回撥的資料。
onMethodCall方法中的result是Flutter端傳來的資料,我們需要對資料進行判斷,然後向Flutter端傳送資料。
向Flutter端傳送資料有以下方法:

result.success(Object result)
結果成功,將result返回給Flutter端。

result.error(String errorCode,String errorMsg,Object errorDetails)
結果失敗,將errorCode、errorMsg、errorDetails返回給Flutter端。

result.notImplemented()
Android端沒有實現Flutter端需要的方法,會將notImplemented返回給Flutter端。

Flutter端實現
在main.dart中加入如下程式碼。

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  static const platformChannel =
      const MethodChannel('com.example.platform_channel/dialog');//1

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "Flutter Demo",
      home: Scaffold(
        appBar: AppBar(
          title: Text("Flutter呼叫Android"),
        ),
        body: Padding(
          padding: EdgeInsets.all(40.0),
          child: RaisedButton(
            child: Text("呼叫Dialog"),
            onPressed: () {
              showDialog("Flutter呼叫AlertDialog");
            },
          ),
        ),
      ),
    );
  }

  void showDialog(String content) async {
    var arguments = Map();
    arguments['content'] = content;
    try {
      String result = await platformChannel.invokeMethod('dialog', arguments);//2
      print('showDialog ' + result);
    } on PlatformException catch (e) {
      print('showDialog ' + e.code + e.message + e.details);
    } on MissingPluginException catch (e) {
      print('showDialog ' + e.message);
    }
  }
}

註釋1處建立了MethodChannel,它需要傳入MethodChannel的Name,這個Name要保證和Android端設定的Name是一樣的。當點選按鈕時會觸發showDialog方法。註釋2處用於呼叫Android中的方法,第一個引數是方法的名稱,第二個引數arguments只能是Map或者JSON型別的,是我們需要傳遞給Android端的資料。
執行程式,當我們點選”呼叫Dialog”按鈕時,效果如下所示。
ZLhtVf.png

3.Android呼叫Flutter

有的時候Flutter呼叫Android後,Android還會將結果返回給Flutter,雖然有時可以用result來實現,但Android端的處理可能是非同步的,result物件也不能長期的持有,這時就需要Android來呼叫Flutter。
因為頁面UI是Flutter端繪製的,我們很難在頁面中控制Android端,要實現Android呼叫Flutter,可以利用Android的Activty的生命週期,如果將應用切到後臺再切回前臺,這樣Activty的onResume方法就會被呼叫,我們在onResume方法中實現呼叫Flutter的功能就可以了。

Android端的實現

package com.example.platform_channel;

import android.os.Bundle;
import android.util.Log;
import java.util.HashMap;
import java.util.Map;
import io.flutter.app.FlutterActivity;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugins.GeneratedPluginRegistrant;

public class MainActivity extends FlutterActivity {
    public static final String MAIN_ACTIVITY = "MainActivity";
    MethodChannel methodChannel;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        GeneratedPluginRegistrant.registerWith(this);

        methodChannel = new MethodChannel(getFlutterView(),"com.example.platform_channel/text");//1
    }

    @Override
    protected void onResume() {
        super.onResume();
        Map map = new HashMap();
        map.put("content","Android進階三部曲");
        methodChannel.invokeMethod("showText", map, new MethodChannel.Result() {//2
            @Override
            public void success(Object o) {
                Log.d(MAIN_ACTIVITY,(String)o);
            }
            @Override
            public void error(String errorCode, String errorMsg, Object errorDetail) {
                Log.d(MAIN_ACTIVITY,"errorCode:"+errorCode+" errorMsg:"+errorMsg+" errorDetail:"+(String)errorDetail);
            }
            @Override
            public void notImplemented() {
                Log.d(MAIN_ACTIVITY,"notImplemented");
            }
        });
    }
}

和Flutter呼叫Android的程式碼是類似的,在註釋1處建立MethodChannel,然後在註釋2處呼叫Flutter端的showText方法,並將資料以Map的形式傳遞過去。MethodChannel.Result() 的回撥裡有三個方法,通過這三個方法可以得到Android呼叫Flutter的結果。

Flutter端的實現

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    // TODO: implement createState
    return MyAppState();
  }
}

class MyAppState extends State<MyApp> {
  static const platformChannel =
      const MethodChannel('com.example.platform_channel/text');

  String textContent = 'Flutter端初始文字';

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    platformChannel.setMethodCallHandler((methodCall) async {
      switch (methodCall.method) {
        case 'showText':
          String content = await methodCall.arguments['content'];
          if (content != null && content.isNotEmpty) {
            setState(() {
              textContent = content;
            });
            return 'success';
          } else {
            throw PlatformException(
                code: 'error', message: '失敗', details: 'content is null');
          }
          break;
        default:
          throw MissingPluginException();
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "Flutter Demo",
      home: Scaffold(
        appBar: AppBar(
          title: Text('Android呼叫Flutter'),
        ),
        body: Padding(
          padding: EdgeInsets.all(40.0),
          child: Text(textContent),
        ),
      ),
    );
  }
}

因為要實現Flutter頁面的改變,就需要在initState方法中為MethodChannel新增回撥,如果Android端傳遞過來的方法名稱為showText,就獲取Android端傳來的content的值,賦值給Text來改變頁面的狀態。
執行程式後,將程式切到後臺再切回前臺,效果如下圖所示。

ZLy7lV.png


By: Laravel-China 寧澤林
MyBlog: nizer.in

相關文章