本文首发于公众号「刘望舒」

ReactNative入门系列
React Native组件
Flutter基础系列

前言

本来这篇文章应该讲一下Flutter的插件开发,但是在插件开发的基础是PlatformChannel,也就是Flutter与Android/iOS Native的通信,理解了这一个知识点,Flutter的插件开发也就不在话下。

1.PlatformChannel概述

Flutter不能完成所有Native的功能,因此需要Flutter与Native的通信,Flutter提供了一套Platform Channel的机制,来满足Flutter与Native通信的需求。 下面是PlatformChannel架构。

图中可以看到,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"按钮时,效果如下所示。

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 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来改变页面的状态。 运行程序后,将程序切到后台再切回前台,效果如下图所示。

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等技术,还有程序员成长类文章。

转载于:https://juejin.im/post/5d4837f35188255d352abcd7

更多相关文章

  1. SpringBoot 2.0 中 HikariCP 数据库连接池原理解析
  2. 一句话锁定MySQL数据占用元凶
  3. android Activity如何横屏显示?如何解决Activity在设置横屏时候会
  4. Android(安卓)多媒体应用——MediaRecorder录制音频
  5. Android(安卓)4.0 MutliMedia 流程分析
  6. android 图形系统requestLayout的流程
  7. Android(安卓)四大组件之Acticity
  8. android binder 进程间通信机制4-Service Manager
  9. Android(安卓)MediaPlayer 分析 - service端文件结构

随机推荐

  1. html5: 新特性(表单)
  2. CHtmlEditCtrl (3): More HTML Editor Op
  3. JQuery函数不能用于初始触发器
  4. 百度地图Api进阶教程-实例高级操作8.html
  5. HTML5新增标签与属性
  6. 如何确定在web页面上呈现的字符串的长度(
  7. telnet建立http连接获取网页HTML内容
  8. jQuery:流体同位素仅在调整大小后才工作
  9. html中显示div的时候,超出浏览器的宽,怎么
  10. 用Jsoup实现html中标签替换