PHP开源Hub
致力于开发者的提升

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
赞(1) 打赏
未经允许不得转载PHP开源Hub » Flutter 基础(十三)Flutter 与 Android 的相互通信

评论 2

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
  1. #1

    好久没来了

    林林林1个月前 (09-19)回复
  2. #2

    。◕‿◕。

    cai lei4周前 (09-25)回复

PHP开源Hub-致力于互联网开发者的成长

技术群聊软文发表

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏