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

劉望舒發表於2019-08-05

本文首發於公眾號「劉望舒」

ReactNative入門系列
React Native元件
Flutter基礎系列

前言

本來這篇文章應該講一下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

Flutter基礎系列
Flutter基礎(一)移動開發的跨平臺技術演進
Flutter基礎(二)Flutter開發環境搭建和Hello World
Flutter基礎(三)Dart快速入門
Flutter基礎(四)開發Flutter應用前需要掌握的Basic Widget
Flutter基礎(五)Material元件之MaterialApp、Scaffold、AppBar
Flutter基礎(六)Material元件之BottomNavigationBar、TabBar、Drawer
Flutter基礎(七)Scrolling Widget之ListView、GridView、PageView
Flutter基礎(八)手勢相關Widget:GestureDetector和Dismissible
Flutter基礎(九)資源和圖片
Flutter基礎(十)佈局Widget快速入門
Flutter基礎(十一)網路請求(Dio)與JSON資料解析
Flutter基礎(十二)路由(頁面跳轉)與資料傳遞
Flutter基礎(十三)Flutter與Android的相互通訊


這裡不僅分享大前端、Android、Java等技術,還有程式設計師成長類文章。
Flutter基礎(十三)Flutter與Android的相互通訊

相關文章