Flutter原生功能调用插件dart_native的使用

发布于 1周前 作者 ionicwang 来自 Flutter

Flutter原生功能调用插件dart_native的使用

简介

DartNative 是一个用于在 Dart 和原生 API 之间进行通信的桥梁。它取代了性能较低的 Flutter Channel,提供了更快速和简洁的代码。DartNative 支持动态同步和异步通道,并且可以在不同语言接口之间直接调用,无需序列化参数和返回值。此外,DartNative 还支持 Dart finalizer 和自动生成简洁的桥接代码。

特性

  • 动态同步与异步通道:DartNative 可以动态调用任何原生 API,支持同步和异步通道。
  • 多语言接口直接调用:无需序列化参数和返回值,提供直接调用和自动对象marshalling。
  • Dart finalizer:Dart finalizer 在 Flutter 3 (Dart 2.17) 及以上版本中可用,但通过 DartNative 可以在 Flutter 2.2.0 (Dart 2.13.0) 及以上版本中使用。
  • 自动生成简洁的桥接代码:DartNative 支持自动类型转换,使得桥接代码更短、更简单。

支持平台

  • iOS
  • macOS
  • Android

使用方法

基本用法:接口绑定
  1. 添加依赖: 在 pubspec.yaml 中添加 dart_nativebuild_runner

    dependencies:
      dart_native: ^0.7.0
    
    dev_dependencies:
      build_runner: ^2.0.0
    
  2. Dart 调用原生

    • Dart 代码

      import 'package:dart_native/dart_native.dart';
      
      final interface = Interface("MyFirstInterface");
      
      String helloWorld() {
        return interface.invokeMethodSync('hello', args: ['world']);
      }
      
      Future<int> sum(int a, int b) {
        return interface.invokeMethod('sum', args: [a, b]);
      }
      
    • Objective-C 代码

      @implementation DNInterfaceDemo
      
      // 注册接口名称
      InterfaceEntry(MyFirstInterface)
      
      // 注册方法 "hello"
      InterfaceMethod(hello, myHello:(NSString *)str) {
        return [NSString stringWithFormat:@"hello %@!", str];
      }
      
      // 注册方法 "sum"
      InterfaceMethod(sum, addA:(int32_t)a withB:(int32_t)b) {
        return @(a + b);
      }
      
      @end
      
    • Java 代码

      // 加载 libdart_native.so
      DartNativePlugin.loadSo();
      
      @InterfaceEntry(name = "MyFirstInterface")
      public class InterfaceDemo extends DartNativeInterface {
      
        @InterfaceMethod(name = "hello")
        public String hello(String str) {
          return "hello " + str;
        }
      
        @InterfaceMethod(name = "sum")
        public int sum(int a, int b) {
          return a + b;
        }
      }
      
  3. 原生调用 Dart

    • Dart 代码

      interface.setMethodCallHandler('totalCost',
          (double unitCost, int count, List list) async {
        return {'totalCost: ${unitCost * count}': list};
      });
      
    • Objective-C 代码

      [self invokeMethod:@"totalCost"
          arguments:@[@0.123456789, @10, @[@"testArray"]]
            result:^(id _Nullable result, NSError * _Nullable error) {
              NSLog(@"%@", result);
            }];
      
    • Java 代码

      invokeMethod("totalCost", new Object[]{0.123456789, 10, Arrays.asList("hello", "world")},
          new DartNativeResult() {
            @Override
            public void onResult(@Nullable Object result) {
              Map retMap = (Map) result;
              // do something
            }
      
            @Override
            public void error(@Nullable String errorMessage) {
              // do something
            }
          }
      );
      
  4. Dart finalizer

    final foo = Bar(); // 自定义实例
    unitTest.addFinalizer(() { // 注册 finalizer 回调
      print('The instance of \'foo\' has been destroyed!'); // 当 `foo` 被 GC 销毁时,执行此行代码
    });
    
高级用法:动态调用方法
  1. 步骤 1:添加 dart_native 到依赖项,并将 build_runner 添加到开发依赖项。

  2. 步骤 2:使用 @dartnative/codegen 或手动编写 Dart 代码生成 Dart 包装器代码。

  3. 步骤 3:使用 dart_native_gen 生成自动类型转换代码:

    • 3.1:使用 @native 注解 Dart 包装器类:

      @native
      class RuntimeSon extends RuntimeStub {
        RuntimeSon([Class isa]) : super(Class('RuntimeSon'));
        RuntimeSon.fromPointer(Pointer<Void> ptr) : super.fromPointer(ptr);
      }
      
    • 3.2:使用 [@nativeRoot](/user/nativeRoot) 注解入口(如 main()):

      [@nativeRoot](/user/nativeRoot)
      void main() {
        runApp(App());
      }
      
    • 3.3:运行以下命令生成文件:

      flutter packages pub run build_runner build --delete-conflicting-outputs
      

      推荐先运行 clean

      flutter packages pub run build_runner clean
      
  4. 步骤 4:在生成的文件 <generated-name>.dn.dart 中调用自动生成的函数。函数名称由 pubspec.yaml 中的 name 决定。

    [@nativeRoot](/user/nativeRoot)
    void main() {
      // 函数名称由 pubspec.yaml 中的 name 生成
      runDartNativeExample();
      runApp(App());
    }
    
  5. 步骤 5:编写代码。以下是一些示例:

    • iOS 示例

      • Dart 代码(生成)

        // 创建 Objective-C 对象
        RuntimeStub stub = RuntimeStub();
        
        // Dart 函数将被转换为 Objective-C block
        stub.fooBlock((NSObject a) {
          print('hello block! ${a.toString()}');
          return 101;
        });
        
        // 支持内置结构体
        CGRect rect = stub.fooCGRect(CGRect(4, 3, 2, 1));
        print(rect);
        
      • 对应的 Objective-C 代码

        typedef int(^BarBlock)(NSObject *a);
        
        @interface RuntimeStub
        
        - (CGRect)fooCGRect:(CGRect)rect;
        - (void)fooBlock:(BarBlock)block;
        
        @end
        
    • Android 示例

      • Dart 代码(生成)

        // 创建 Java 对象
        RuntimeStub stub = RuntimeStub();
        
        // 获取 Java 列表
        List list = stub.getList([1, 2, 3, 4]);
        
        // 支持接口
        stub.setDelegateListener(DelegateStub());
        
      • 对应的 Java 代码

        public class RuntimeStub {
        
          public List<Integer> getList(List<Integer> list) {
            List<Integer> returnList = new ArrayList<>();
            returnList.add(1);
            returnList.add(2);
            return returnList;
          }
        
          public void setDelegateListener(SampleDelegate delegate) {
            delegate.callbackInt(1);
          }
        }
        

完整示例 Demo

以下是一个完整的示例应用,展示了如何使用 dart_native 插件进行 Dart 和原生代码之间的通信。

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:dart_native/dart_native.dart';
import 'package:dart_native_example/main.dn.dart';
import 'package:dart_native_gen/dart_native_gen.dart';
import 'log.dart';
import 'dn_unit_test.dart';

[@nativeRoot](/user/nativeRoot)
void main() {
  Log.setLevel(LogLevel.verbose);
  runDartNativeExample();
  runApp(const DartNativeApp());
}

final interface = Interface("MyFirstInterface");

class DartNativeApp extends StatefulWidget {
  const DartNativeApp({Key? key}) : super(key: key);

  [@override](/user/override)
  State createState() => _DartNativeAppState();
}

class _DartNativeAppState extends State<DartNativeApp> {
  final TextEditingController _controllerA = TextEditingController();
  final TextEditingController _controllerB = TextEditingController();
  String result = '';

  [@override](/user/override)
  void initState() {
    super.initState();
    initPlatformState();
  }

  // 平台消息是异步的,因此我们在异步方法中初始化
  Future<void> initPlatformState() async {
    // 示例:原生调用 Dart
    interface.setMethodCallHandler('totalCost',
        (double unitCost, int count, List list) async {
      return {'totalCost: ${unitCost * count}': list};
    });
    result = helloWorld();

    final data = getUTF8Data(result);
    // 字节数等于 uint8 列表的长度
    final utf8Result =
        data.bytes.cast<Utf8>().toDartString(length: data.lengthInBytes);
    // 它们应该相等
    assert(utf8Result == result);

    final unitTest = DNUnitTest();

    /// 运行所有测试用例
    await unitTest.runAllUnitTests();
    // 测试 finalizer
    unitTest.addFinalizer(() {
      print('The instance of \'unitTest\' has been destroyed!');
    });

    // 示例回调
    testCallback();
  }

  String helloWorld() {
    return interface.invokeMethodSync('hello', args: ['world']);
  }

  Future<T> sum<T>(T a, T b) {
    return interface.invokeMethod('sum', args: [a, b]);
  }

  NativeByte getUTF8Data(String str) {
    return interface.invokeMethodSync('getUTF8Data', args: [str]);
  }

  void testCallback() {
    interface.invokeMethodSync('testCallback', args: [
      (bool success, String result) {
        if (success) {
          print(result);
        }
      }
    ]);
  }

  Future<void> calculate() async {
    final aStr = _controllerA.text;
    final bStr = _controllerB.text;
    String r;
    if (aStr.isNotEmpty && bStr.isNotEmpty) {
      int aPlusB = await sum(int.parse(aStr), int.parse(bStr));
      r = aPlusB.toString();
    } else {
      r = helloWorld();
    }
    setState(() {
      result = r;
    });
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('DartNative example app'),
        ),
        body: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            TextField(
              textAlign: TextAlign.center,
              controller: _controllerA,
              keyboardType:
                  const TextInputType.numberWithOptions(decimal: true),
              decoration: const InputDecoration(
                hintText: 'Input integer A',
              ),
            ),
            TextField(
              textAlign: TextAlign.center,
              controller: _controllerB,
              keyboardType:
                  const TextInputType.numberWithOptions(decimal: true),
              decoration: const InputDecoration(
                hintText: 'Input integer B',
              ),
            ),
            Text(
              result,
              style: const TextStyle(fontSize: 20),
            ),
            ElevatedButton(
              onPressed: () {
                calculate();
              },
              child: const Text('SUM'),
            ),
          ],
        ),
      ),
    );
  }
}

更多关于Flutter原生功能调用插件dart_native的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter原生功能调用插件dart_native的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,dart_native 是一个用于在 Flutter 中调用原生功能(如 Android 的 Java/Kotlin 代码和 iOS 的 Swift/Objective-C 代码)的插件。以下是如何在 Flutter 项目中使用 dart_native 的一个基本示例。

1. 添加依赖

首先,你需要在 pubspec.yaml 文件中添加 dart_native 的依赖:

dependencies:
  flutter:
    sdk: flutter
  dart_native: ^最新版本号  # 请替换为实际的最新版本号

然后运行 flutter pub get 来获取依赖。

2. 配置原生代码

Android

android/app/src/main/java/com/yourapp/MainActivity.kt(或对应的 Java 文件)中,添加一个原生方法:

package com.yourapp

import android.os.Bundle
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugins.GeneratedPluginRegistrant

class MainActivity: FlutterActivity() {
    private val CHANNEL = "com.yourapp/channel"

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
            if (call.method == "getBatteryLevel") {
                val batteryManager = applicationContext.getSystemService(Context.BATTERY_SERVICE) as BatteryManager
                val batteryLevel: Int = batteryManager.intProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
                result.success(batteryLevel)
            } else {
                result.notImplemented()
            }
        }
    }
}

iOS

ios/Runner/AppDelegate.swift 中,添加一个原生方法:

import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    GeneratedPluginRegistrant.register(with: self)
    
    let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
    let channel = FlutterMethodChannel(name: "com.yourapp/channel", binaryMessenger: controller.binaryMessenger)
    channel.setMethodCallHandler {
      (call: FlutterMethodCall, result: @escaping FlutterResult) in
      if call.method == "getBatteryLevel" {
        UIDevice.current.batteryLevel? { batteryLevel in
          result(batteryLevel!)
        }
      } else {
        result(FlutterMethodNotImplemented)
      }
    }
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}

3. 在 Dart 中调用原生方法

在你的 Flutter Dart 代码中,你可以使用 dart_native 插件来调用上面定义的原生方法:

import 'package:flutter/material.dart';
import 'package:dart_native/dart_native.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  String _batteryLevel = 'Unknown battery level.';

  @override
  void initState() {
    super.initState();
    _getBatteryLevel();
  }

  Future<void> _getBatteryLevel() async {
    try {
      final NativeInterface nativeInterface = NativeInterface();
      var result = await nativeInterface.invokeMethod<int>('getBatteryLevel');
      setState(() {
        _batteryLevel = 'Battery level at $result % .';
      });
    } catch (e) {
      setState(() {
        _batteryLevel = 'Failed to get battery level: \'$e\'.';
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Battery Level'),
        ),
        body: Center(
          child: Text(_batteryLevel),
        ),
      ),
    );
  }
}

注意事项

  1. 权限:在调用某些原生功能时,可能需要申请相应的权限(如电池信息、相机等)。
  2. 平台特定代码:确保在相应的平台(Android 和 iOS)中分别实现原生方法。
  3. 错误处理:在实际项目中,应添加更多的错误处理逻辑以应对各种可能的异常情况。

通过以上步骤,你可以在 Flutter 项目中成功调用原生功能。如果有更复杂的需求,可以参考 dart_native 的官方文档获取更多信息。

回到顶部