Flutter和Native之间通讯,是日常开发中经常需要用到的功能。
本文介绍了Flutter和Native如何进行通讯,以及在引入FlutterBoost的情况下,如何进行Flutter和Native的通讯。
如果想下载Demo源码,可以直接拉到最后面。

Flutter和Native通信基础

Flutter和Native之间通信的常见场景
  • 初始化Flutter时Native向Dart传递数据
  • Native发送数据给Dart
  • Dart发送数据给Native
  • Dart发送数据给Native,然后Native回传数据给Dart
Flutter和Native通信的机制

Flutter和Native通信使用Channel(平台通道)在客户端(UI)和主机(平台)之间传递

Channel支持的数据类型

flutter java交互 flutter native交互_Native

Channel

Flutter定义了三种不同类型的Channel

  • BasicMessageChannel:用于使用指定的编解码器对消息进行编码和解码,属于双向通信,可以 Native 端主动调用,也可以Flutter主动调用。
  • MethodChannel:Flutter 与 Native 端相互调用,调用后可以返回结果,可以 Native 端主动调用,也可以Flutter主动调用,属于双向通信。此方式为最常用的方式, Native 端调用需要在主线程中执行。
  • EventChannel:用于数据流(event streams)的通信, Native 端主动发送数据给 Flutter,通常用于状态的监听,比如网络变化、传感器数据等。

Flutter和Native通信示例

首先,我们要新建一个Android原生项目,和一个Flutter module,并使其在同一个文件夹下

flutter java交互 flutter native交互_Android_02


并将其配置,使其能够进行混合开发,具体详见 Flutter与Android Native进行混合开发

在Flutter Module中,新建BatteryChannel.dart
class BatteryChannel(flutterEngine: BinaryMessenger, context: Context) :
    MethodChannel.MethodCallHandler {
    private val batteryChannelName = "com.liubike/battery"
    private var channel: MethodChannel
    private var mContext: Context

    companion object {
        private const val TAG = "BatteryChannel"
    }

    init {
        Log.d(TAG, "init")
        channel = MethodChannel(flutterEngine, batteryChannelName)
        channel.setMethodCallHandler(this)
        mContext = context;
        Log.d(TAG, "init2")
    }

    override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
        Log.d(TAG, "onMethodCall: " + call.method)
        if (call.method == "getBatteryLevel") {
            val batteryLevel = getBatteryLevel()
            if (batteryLevel != -1) {
                result.success(batteryLevel)
            } else {
                result.error("UNAVAILABLE", "Battery level not available.", null)
            }
        } else {
            result.notImplemented()
        }
    }

    private fun getBatteryLevel(): Int {
        val batteryLevel: Int
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            val batteryManager =
                mContext.getSystemService(Context.BATTERY_SERVICE) as BatteryManager
            batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
        } else {
            val intent = ContextWrapper(mContext).registerReceiver(
                null,
                IntentFilter(Intent.ACTION_BATTERY_CHANGED)
            )
            batteryLevel = intent!!.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) *
                    100 / intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
        }

        return batteryLevel
    }
}
在Flutter Module中,进行调用
import 'package:flutter/material.dart';
import 'BatteryChannel.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);
  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  String _batteryLevel = 'Unknown battery level.';

  // 异步获取到电量,然后重新渲染页面
  void getBatteryLevel() async{
    _batteryLevel = await BatteryChannel.getBatteryLevel();
    setState(() {
    });
  }

  @override
  void initState() {
    super.initState();
    BatteryChannel.initChannels();  // 初始化通道
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),Text(
              '$_batteryLevel',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: getBatteryLevel,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), 
    );
  }
}
在原生项目中,新建BatteryChannel.kt
package com.liubike.fluttercommunicatenative

import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.content.IntentFilter
import android.os.BatteryManager
import android.os.Build
import android.util.Log
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel

class BatteryChannel(flutterEngine: BinaryMessenger, context: Context) :
    MethodChannel.MethodCallHandler {
    private val batteryChannelName = "com.liubike/battery"
    private var channel: MethodChannel
    private var mContext: Context

    companion object {
        private const val TAG = "BatteryChannel"
    }

    init {
        channel = MethodChannel(flutterEngine, batteryChannelName)
        channel.setMethodCallHandler(this)
        mContext = context;
    }

    override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
        Log.d(TAG, "onMethodCall: " + call.method)
        if (call.method == "getBatteryLevel") {
            val batteryLevel = getBatteryLevel()
            if (batteryLevel != -1) {
                result.success(batteryLevel)
            } else {
                result.error("UNAVAILABLE", "Battery level not available.", null)
            }
        } else {
            result.notImplemented()
        }
    }

    private fun getBatteryLevel(): Int {
        val batteryLevel: Int
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            val batteryManager =
                mContext.getSystemService(Context.BATTERY_SERVICE) as BatteryManager
            batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
        } else {
            val intent = ContextWrapper(mContext).registerReceiver(
                null,
                IntentFilter(Intent.ACTION_BATTERY_CHANGED)
            )
            batteryLevel = intent!!.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) *
                    100 / intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
        }

        return batteryLevel
    }
}
在原生项目中,创建AgentActivity.kt

需要在configureFlutterEngine方法中,实例化BatteryChannel

package com.liubike.fluttercommunicatenative

import android.os.Bundle
import android.widget.Toast
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine

class AgentActivity : FlutterActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        Toast.makeText(this, "agrent", Toast.LENGTH_SHORT).show()
    }

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)

        BatteryChannel(flutterEngine.dartExecutor.binaryMessenger,this)
    }
}

不要忘了在AndroidManifest.xml中注册

<activity
    android:name=".AgentActivity"
    android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
    android:hardwareAccelerated="true"
    android:windowSoftInputMode="adjustResize">
</activity>
最后,在MainActivity.kt中跳转到Flutter页面

跳转到Flutter页面后,再去获取电量

package com.liubike.fluttercommunicatenative

import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        var btn01 = findViewById<Button>(R.id.btn_01)
        btn01.setOnClickListener {
            /*
            //跳转到Flutter默认页面
			startActivity(
            	FlutterActivity.createDefaultIntent(this)
            )*/

            /*
            //跳转到Flutter指定页面
			startActivity(
                    FlutterActivity
                            .withNewEngine()
                            .initialRoute("/home")
                            .build(this)
            )*/

            startActivity(Intent(this,AgentActivity::class.java))
        }
    }
}
效果如下

flutter java交互 flutter native交互_Flutter_03

引入FlutterBoost,进行Flutter和Native的通讯

其他地方,和Flutter官方是一样的,主要就是Channel实例化改到了FlutterBoost初始化的地方。

关于FlutterBoost的接入,可以看我的另一篇博客Flutter接入FlutterBoost进行跳转

在Flutter Module中,新建BatteryChannel.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

class BatteryChannel {
  static const _batteryChannelName = "com.liubike/battery";  // 1.方法通道名称
  static MethodChannel _batteryChannel;

  static void initChannels(){
    _batteryChannel = MethodChannel(_batteryChannelName);  // 2. 实例化一个方法通道
  }

  // 3. 异步任务,通过平台通道与特定平台进行通信,获取电量,这里的宿主平台是 Android
  static getBatteryLevel() async {
    initChannels();
    String batteryLevel;
    try {
      final int result = await _batteryChannel.invokeMethod('getBatteryLevel');
      batteryLevel = 'Battery level at $result % .';
    } on PlatformException catch (e) {
      batteryLevel = "Failed to get battery level: '${e.message}'.";
    }

    return batteryLevel;
  }
}
在Flutter Module中,进行调用
import 'package:flutter/material.dart';
import 'package:flutter_boost/flutter_boost.dart';
import 'package:flutter_module/BatteryChannel.dart';

class SecondPage extends StatelessWidget {
  const SecondPage({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  String _batteryLevel = 'Unknown battery level.';
  // 异步获取到电量,然后重新渲染页面
  getBatteryLevel() async{
    _batteryLevel = await BatteryChannel.getBatteryLevel();
    setState(() {
    });
  }

  @override
  void initState() {
    super.initState();
    BatteryChannel.initChannels();  // 初始化通道
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Second"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            MaterialButton(
                color: Colors.green,
                onPressed: () {
                  getBatteryLevel();  // 2.调用通道方法
                },
                child: const Text("获取电量")),
            new Text(_batteryLevel),
          ],
        ),
      ),
    );
  }
}
在Android端新建BatteryChannel.kt
package com.liubike.boosttestandroid

import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.content.IntentFilter
import android.os.BatteryManager
import android.os.Build
import android.util.Log
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel

class BatteryChannel(flutterEngine: BinaryMessenger, context: Context) :
    MethodChannel.MethodCallHandler {
    private val batteryChannelName = "com.liubike/battery"
    private var channel: MethodChannel
    private var mContext: Context

    companion object {
        private const val TAG = "BatteryChannel"
    }

    init {
        channel = MethodChannel(flutterEngine, batteryChannelName)
        channel.setMethodCallHandler(this)
        mContext = context;
    }

    override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
        Log.d(TAG, "onMethodCall: " + call.method)
        if (call.method == "getBatteryLevel") {
            val batteryLevel = getBatteryLevel()
            if (batteryLevel != -1) {
                result.success(batteryLevel)
            } else {
                result.error("UNAVAILABLE", "Battery level not available.", null)
            }
        } else {
            result.notImplemented()
        }
    }

    private fun getBatteryLevel(): Int {
        val batteryLevel: Int
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            val batteryManager =
                mContext.getSystemService(Context.BATTERY_SERVICE) as BatteryManager
            batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
        } else {
            val intent = ContextWrapper(mContext).registerReceiver(
                null,
                IntentFilter(Intent.ACTION_BATTERY_CHANGED)
            )
            batteryLevel = intent!!.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) *
                    100 / intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
        }

        return batteryLevel
    }
}

在Application中初始化FlutterBoost,并实例化Channel

FlutterBoost.instance().setup(this, new FlutterBoostDelegate() {
    @Override
    public void pushNativeRoute(FlutterBoostRouteOptions options) {
        String pageName = options.pageName();
        Log.i("NativeApp", "pageName:" + pageName);
        //这里根据options.pageName来判断你想跳转哪个页面
        Intent intent = null;
        if ("native_main".equals(pageName)){
            intent = new Intent(FlutterBoost.instance().currentActivity(), MainActivity.class);
        }else if("native_second".equals(pageName)){
            intent = new Intent(FlutterBoost.instance().currentActivity(), SecondActivity.class);
        }
        FlutterBoost.instance().currentActivity().startActivityForResult(intent, options.requestCode());
    }

    @Override
    public void pushFlutterRoute(FlutterBoostRouteOptions options) {
        Intent intent = new FlutterBoostActivity.CachedEngineIntentBuilder(FlutterBoostActivity.class)
                .backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.transparent)
                .destroyEngineWithActivity(false)
                .uniqueId(options.uniqueId())
                .url(options.pageName())
                .urlParams(options.arguments())
                .build(FlutterBoost.instance().currentActivity());
        FlutterBoost.instance().currentActivity().startActivity(intent);
    }
}, engine -> {
    //Channel在这里进行实例化
    new BatteryChannel(engine.getDartExecutor(), this);
});
最后,在MainActivity.kt中跳转到Flutter页面

跳转到Flutter页面后,再去获取电量

Intent intent = new FlutterBoostActivity.CachedEngineIntentBuilder(FlutterBoostActivity.class)
        .backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.opaque)
        .destroyEngineWithActivity(false)
        .url("flutterPage") //在main.dart的routerMap中有注册 flutterPage
        .urlParams(params)
        .build(this);
startActivity(intent);
效果如下

flutter java交互 flutter native交互_flutter java交互_04

相关源码下载

Flutter与Android Native进行混合开发,相互跳转,进行通信_示例Demo