flutter中通过插件调用C++代码
系列文章
- 使用NDK编译C/C++项目
- flutter调用C/C++
- 编写flutter插件
- flutter中通过插件调用C++代码【当前文章】
在《2.flutter调用C/C++》中我们提到,flutter可以通过ffi库直接调用C/C++的函数。但是,很可惜,无法在C/C++中回调flutter的函数。
本文就来探讨一下如何在C/C++中调用flutter的函数。中间需要有一个桥梁,那就是flutter的插件。在flutter插件中可以实现原生代码(java、kotlin等)与flutter的相互调用。而这些语言又可以与C/C++实现互调。
创建插件工程
可以通过下面的命令创建一个插件工程:
flutter create -t plugin --org top.mydata --platforms android demo_plugin_with_ndk
其中,
-t
指定创建的是一个插件,--org
参数指定组织,--platforms
参数指定平台类型。- 最后一个参数是插件的名称。
注:如果后面需要多个平台,多次执行该命令,同时--platforms
使用不同的参数。例如:
flutter create -t plugin --org top.mydata --platforms ios demo_plugin_with_ndk
flutter create -t plugin --org top.mydata --platforms macos demo_plugin_with_ndk
flutter create -t plugin --org top.mydata --platforms linux demo_plugin_with_ndk
flutter create -t plugin --org top.mydata --platforms windows demo_plugin_with_ndk
flutter create -t plugin --org top.mydata --platforms web demo_plugin_with_ndk
C/C++代码
源码目录
创建目录android/src/main/cpp
,并在该目录中创建两个文件:
-
CMakeLists.txt
cmake_minimum_required(VERSION 3.22.1) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY BOTH) project("demo_hello") add_library(demo_hello SHARED hello.cpp)
-
hello.cpp
#include <jni.h> #include <string> extern "C" JNIEXPORT void JNICALL Java_top_mydata_demo_1plugin_1with_1ndk_DemoPluginWithNdkPlugin_hello( JNIEnv *env, jobject thiz, jstring m1) { jclass cls = env->FindClass("top/mydata/demo_plugin_with_ndk/DemoPluginWithNdkPlugin"); jmethodID jmid = env->GetMethodID(cls, "onShowMsg", "(Ljava/lang/String;)V"); if (!jmid) { return; } std::string strM1 = env->GetStringUTFChars(m1, 0); strM1 += "\nhello in c++(Java_top_mydata_demo_1plugin_1with_1ndk_DemoPluginWithNdkPlugin_hello)"; env->CallObjectMethod(thiz, jmid, env->NewStringUTF(strM1.c_str())); }
这个jni函数的实现非常简单,调用类DemoPluginWithNdkPlugin
的onShowMsg
函数。具体的规则可以查看jni的相关资料。
库文件
创建目录android/src/main/libs
,并在该目录中创建两个文件:
libs
├── arm64-v8a
│ └── libc++_shared.so
└── armeabi-v7a
└── libc++_shared.so
需要留意的是在flutter中必须要带上libc++_shared.so
,不然,会导致so库加载失败。
另外,需要留意的是,如果有so在CMake中导入了,那个就不能放在这个目录中。而需要放在另外的目录里面。否则,编译时会报so重复错误。比如CMake里面:
... ...
add_library( libcrypto
SHARED
IMPORTED )
set_target_properties(libcrypto
PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/../libs2/${ANDROID_ABI}/libcrypto.so)
... ...
这样编译时就会自动输出libcrypto.so
。这个so就不应该放在libs
,而需要创建一个新的目录,如libs2
。
配置build.gradle
在android/build.gradle
中新增以下内容:
... ...
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
// 指定C++文件的目录
main.jni.srcDirs += 'src/main/cpp'
// 指定库文件所在目录,这儿的库文件,不参与编译。但是,会被打包到最终的apk中。
main.jniLibs.srcDirs 'src/main/libs'
}
defaultConfig {
minSdkVersion 16
// 指定ndk的版本号。
ndkVersion "25.1.8937393"
// 指定需要编译哪些CPU指令集。
ndk {
abiFilters "arm64-v8a", "armeabi-v7a"
}
}
// 指定cmake配置文件的位置和版本号。
externalNativeBuild {
cmake {
path file('src/main/cpp/CMakeLists.txt')
version '3.22.1'
}
}
... ...
Kotlin代码
package top.mydata.demo_plugin_with_ndk
import android.app.Activity
import androidx.annotation.NonNull
import android.util.Log
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import java.io.IOException
import java.lang.Exception
import java.util.*
/** DemoPluginWithNdkPlugin */
class DemoPluginWithNdkPlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
/// The MethodChannel that will the communication between Flutter and native Android
///
/// This local reference serves to register the plugin with the Flutter Engine and unregister it
/// when the Flutter Engine is detached from the Activity
private lateinit var channel : MethodChannel
private lateinit var activity: Activity
// 在构造函数中加载so
init {
try {
System.loadLibrary("demo_hello")
}
catch(e: Exception){
Log.d("TAG", "load demo_hello failed! ${e.localizedMessage}")
}
}
// { 这4个函数是继承自ActivityAware,是为了支持多线程。其实,在本例中并非必需。
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
activity = binding.activity
}
override fun onDetachedFromActivity() {
// no op
}
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
activity = binding.activity
}
override fun onDetachedFromActivityForConfigChanges() {
// no op
}
// }
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "demo_plugin_with_ndk")
channel.setMethodCallHandler(this)
}
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
// 根据函数并调用对应的处理函数
when(call.method){
"hello" -> handleHello(call, result)
}
}
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
}
fun handleHello(@NonNull call: MethodCall, @NonNull result: Result){
// 获取函数参数。
val m1 = call.argument<String>("m1")!!
// 调用jni函数。
hello(m1 + "\nhello in kotlin(DemoPluginWithNdkPlugin::handleHello)");
// 返回成功
result.success(null)
}
external fun hello(@NonNull m1: String)
// C++中回调的函数
fun onShowMsg(msg: String){
// 为了支持多线程,我们需要切换到UI线程再回调
activity.runOnUiThread {
channel.invokeMethod("onShowMsg",
mapOf("msg" to msg))
}
}
}
注意:
-
需要继承
ActivityAware
,这是因为,回调的时候可能在工作线程中,只能在UI线程中操作UI,所以,我们需要切换到UI线程再回调。不过,在本例子中,C++中并没有使用线程,所以,这些并不是必须的。 -
在调用jni前,需要使用
System.loadLibrary("demo_hello")
加载so库。通常,调用一次就可以了。
Flutter插件代码
这个部分的代码逻辑在《编写flutter插件》一文中有详细的解说,完全相同,不再重复讲述,代码如下:
lib/demo_plugin_with_ndk.dart
:
import 'demo_plugin_with_ndk_platform_interface.dart';
class DemoPluginWithNdk {
// 启动函数,目录是注册一个回调函数。
Future<void> start({required DemoShowMsg onShowMsg}) {
return DemoPluginWithNdkPlatform.instance.start(onShowMsg);
}
// 功能函数,在函数中最终会回调会App的代码。
Future<void> hello(String m1) {
return DemoPluginWithNdkPlatform.instance.hello("$m1\nhello in dart(DemoPluginWithNdk::hello)");
}
}
lib/demo_plugin_with_ndk_method_channel.dart
:
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'demo_plugin_with_ndk_platform_interface.dart';
/// An implementation of [DemoPluginWithNdkPlatform] that uses method channels.
class MethodChannelDemoPluginWithNdk extends DemoPluginWithNdkPlatform {
/// The method channel used to interact with the native platform.
@visibleForTesting
final methodChannel = const MethodChannel('demo_plugin_with_ndk');
DemoShowMsg? _onShowMsg;
MethodChannelDemoPluginWithNdk() {
methodChannel.setMethodCallHandler(_handleMethodCall);
}
// 处理Kotlin调用过来的函数调用请求。
Future<void> _handleMethodCall(MethodCall call) async {
switch (call.method) {
case 'onShowMsg':
_handleOnShowMsg(call);
break;
default:
throw ('Not implemented: ${call.method}');
}
}
void _handleOnShowMsg(MethodCall call) async {
final msg = call.arguments["msg"];
await _onShowMsg?.call(msg+"\nhello in dart(_handleOnShowMsg)");
}
@override
Future<void> start(DemoShowMsg onShowMsg) async {
_onShowMsg = onShowMsg;
}
@override
Future<void> hello(String m1) async {
methodChannel.invokeMethod<void>('hello', {
"m1": m1
});
}
}
lib/demo_plugin_with_ndk_platform_interface.dart
:
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
import 'demo_plugin_with_ndk_method_channel.dart';
typedef DemoShowMsg = Future<void> Function(String msg);
abstract class DemoPluginWithNdkPlatform extends PlatformInterface {
/// Constructs a DemoPluginWithNdkPlatform.
DemoPluginWithNdkPlatform() : super(token: _token);
static final Object _token = Object();
static DemoPluginWithNdkPlatform _instance = MethodChannelDemoPluginWithNdk();
/// The default instance of [DemoPluginWithNdkPlatform] to use.
///
/// Defaults to [MethodChannelDemoPluginWithNdk].
static DemoPluginWithNdkPlatform get instance => _instance;
/// Platform-specific implementations should set this with their own
/// platform-specific class that extends [DemoPluginWithNdkPlatform] when
/// they register themselves.
static set instance(DemoPluginWithNdkPlatform instance) {
PlatformInterface.verifyToken(instance, _token);
_instance = instance;
}
Future<void> start(DemoShowMsg onShowMsg) {
throw UnimplementedError('start() has not been implemented.');
}
Future<void> hello(String m1) {
throw UnimplementedError('hello() has not been implemented.');
}
}
App代码
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:demo_plugin_with_ndk/demo_plugin_with_ndk.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String _message = "";
final _demoPluginWithNdkPlugin = DemoPluginWithNdk();
@override
void initState() {
super.initState();
initPlatformState();
}
// 回调函数
Future<void> _onShowMsg(String msg) async {
setState(() {
_message = "$msg\nhello in dart(_onShowMsg)";
});
}
Future<void> initPlatformState() async {
try {
// 注册回调函数。
await _demoPluginWithNdkPlugin.start(onShowMsg: _onShowMsg);
// 调用功能函数。
await _demoPluginWithNdkPlugin.hello("hello in dart(initPlatformState)");
} on PlatformException {
_message = 'Failed in platform.';
}
if (!mounted) return;
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
),
body: Center(
child: Text(_message),
),
),
);
}
}
最终效果

系列文章
- 使用NDK编译C/C++项目
- flutter调用C/C++
- 编写flutter插件
- flutter中通过插件调用C++代码【当前文章】