Flutter与原生android通信

Flutter使用了一个灵活的系统,允许您调用特定平台的API,Flutter平台特定的API支持不依赖于代码生成,而是依赖于灵活的消息传递的方式:

应用的Flutter部分通过平台通道(platform channel)将消息发送到其应用程序的所在的宿主(iOS或Android)。

宿主监听的平台通道,并接收该消息。然后它会调用特定于该平台的API(使用原生编程语言) - 并将响应发送回客户端,即应用程序的Flutter部分。

平台通道(platform channel)

既然Flutter是通过平台通道(platform channel)实现Flutter和原生端的数据传递的。那么先看下官网的架构图


图一

由图一可以看出,在客户端,MethodChannel (API)可以发送与方法调用相对应的消息。 在宿主平台上MethodChannel 在Android((API) 和 FlutterMethodChannel iOS (API) 可以接收方法调用并返回结果。

Flutter中定义了几种不同的channel:


图二

根据图二可以看出几种channel 之间的区别

BasicMessageChannel:

通过异步传递message与平台进行通信

/// A named channel for communicating with platform plugins using asynchronous

/// message passing.

EventChannel:

通过流的方式与平台进行通信

/// A named channel for communicating with platform plugins using event streams.

MethodChannel:

通过异步方法调用的方式与平台进行通信

/// A named channel for communicating with platform plugins using asynchronous

/// method calls.

OptionalMethodChannel:

继承于MethodChannel 忽略了平台

/// A [MethodChannel] that ignores missing platform plugins.

暂时拿MethodChannel进行深入分析


图三

根据MethodChannel构造方法知道,MethodChannel对象的创建需要两个参数 name跟MethodCodec。

name是MethodChannel的唯一标识,可以理解成channel名字。

codec是用于方法调用和封装结果的编解码器,决定了我们能传递什么类型的数据。

标准的编解码器有如下规则:


图四

接下来先学习下MethodChannel的使用,边用边分析,Flutter和native间的通信,分为 Flutter主动发送 和 native主动发送 两种情况。

Flutter主动发送给原生

官网例子:https://github.com/flutter/flutter/tree/master/examples/platform_channel

在dart文件中的实现:

```

static const MethodChannel methodChannel =

  MethodChannel('samples.flutter.io/battery');

  Future<void> _getBatteryLevel() async {

    String batteryLevel;

    try {

      final int result = await methodChannel.invokeMethod('getBatteryLevel');

      batteryLevel = 'Battery level: $result%.';

    } on PlatformException {

      batteryLevel = 'Failed to get battery level.';

    }

    setState(() {

      _batteryLevel = batteryLevel;

    });

  }

```

```

package com.example.platformchannel;

import android.content.BroadcastReceiver;

import android.content.Context;

import android.content.ContextWrapper;

import android.content.Intent;

import android.content.IntentFilter;

import android.os.BatteryManager;

import android.os.Build.VERSION;

import android.os.Build.VERSION_CODES;

import android.os.Bundle;

import io.flutter.app.FlutterActivity;

import io.flutter.plugin.common.EventChannel;

import io.flutter.plugin.common.EventChannel.EventSink;

import io.flutter.plugin.common.EventChannel.StreamHandler;

import io.flutter.plugin.common.MethodChannel;

import io.flutter.plugin.common.MethodChannel.MethodCallHandler;

import io.flutter.plugin.common.MethodChannel.Result;

import io.flutter.plugin.common.MethodCall;

import io.flutter.plugins.GeneratedPluginRegistrant;

public class MainActivity extends FlutterActivity {

  private static final String BATTERY_CHANNEL = "samples.flutter.io/battery";

  private static final String CHARGING_CHANNEL = "samples.flutter.io/charging";

  @Override

  public void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    GeneratedPluginRegistrant.registerWith(this);

    new MethodChannel(getFlutterView(), BATTERY_CHANNEL).setMethodCallHandler(

        new MethodCallHandler() {

          @Override

          public void onMethodCall(MethodCall call, Result result) {

            if (call.method.equals("getBatteryLevel")) {

              int batteryLevel = getBatteryLevel();

              if (batteryLevel != -1) {

                result.success(batteryLevel);

              } else {

                result.error("UNAVAILABLE", "Battery level not available.", null);

              }

            } else {

              result.notImplemented();

            }

          }

        }

    );

  }

  private int getBatteryLevel() {

    if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {

      BatteryManager batteryManager = (BatteryManager) getSystemService(BATTERY_SERVICE);

      return batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);

    } else {

      Intent intent = new ContextWrapper(getApplicationContext()).

          registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));

      return (intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) * 100) /

          intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);

    }

  }

}

```

以上就是从Flutter端主动发起与native端的通信,dart通过await methodChannel.invokeMethod(‘getBatteryLevel’)发送消息,native端通过MethodChannel中MethodCallHandler的onMethodCall进行接收flutter的通信,并通过result.success进行回传信息。

有几点需要注意

dart中methodChannel 声明的时候的 name要保证与native端的一致,且唯一。

dart中methodChannel.invokeMethod(‘getBatteryLevel’),在native端要进行判断,只有方法名匹配才给予响应, if (call.method.equals(“getBatteryLevel”))。

这里flutter通过 invokeMethod与native进行通信


图五

查看invokeMethod()方法, method为 MethodCall的标识, arguments为参数,注意这里的参数必须要遵守上面的规则(默认情况下不能直接传自定义类,但是我们可以将其转为Json之后再传递,也可以去改造 MethodCode,以此进行复杂数据结构的传递)。

注:可以看到,其是一个 async标记的方法,返回值为Future。那么我们在接受这个方法的返回值的时候,就必须要使用 await进行修饰。要调用使用 await,必须在有 async标记的函数中运行。具体调用和使用的方式可以看官网的例子。在这我们先继续深入。

BinaryMessages.send()


图6

接下来_sendPlatformMessage()——window.sendPlatformMessage()


图七
图8

最终调用native方法与原生进行通信的。

在flutter engine中查看源码


图9


图10

看最关键的方法: dart_state->window()->client()->HandlePlatformMessage()


图11


图12

一步一步跟下来 到了 这里g_handle_platform_message_method

图13


图14

接下来就是 在FlutterJNI 与 FlutterNativeView 之间进行绑定了

图16


图17

这里就可以通过JNI调用Android 端了。

原生主动发送给Flutter

```

/**

    * native data to  dart

    */

    private void native2Dart() {

        /**

        * 数据流的通信(event streams)

        */

        EventChannel eventChannel = new EventChannel(getFlutterView(), EVENT_CHANNEL);

        EventChannel.StreamHandler streamHandler = new EventChannel.StreamHandler() {

            @Override

            public void onListen(Object arguments, EventSink eventSink) {

                Log.e("plateform_channel", "arguments: " + arguments.toString());

                eventSink.success(data);

            }

            @Override

            public void onCancel(Object arguments) {

                Log.e("plateform_channel", "arguments: " + arguments.toString());

            }

        };

        eventChannel.setStreamHandler(streamHandler);

    }

```

Flutter端:

```

static const EventChannel eventChannel =

  EventChannel(FlutterChannel.CHANNEL_RECEIVE_DATA);

  @override

  void initState() {

    super.initState();

    eventChannel.receiveBroadcastStream(['arg1', 'arg2']).listen(_onEvent,

        onError: _onError);

  }

  void _onEvent(Object event) {

    var animal = json.decode(event);

    print(AnimalsRoot.fromJson(animal).animals.cat.name);

    setState(() {

      _receive_data = '$event';

    });

  }

  void _onError(Object error) {

    setState(() {

      _receive_data = 'Receive  failed';

    });

  }

```

用图表示其中的关联就是


图18
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269