Skip to content

鸿蒙 NEXT 开发:Flutter 插件开发

相关文档和产物

重要

  • 需要下载JDK17,并配置环境变量,保证 java 命令可用
  • 需要下载构建产物

环境变量

HarmonyOS SDK,解压开发套件包中 sdk/xxSDK.zip 之后的目录

sh
export HOS_SDK_HOME=/home/<user>/ohos/sdk

解压开发套件包中 commandline/commandline-tools-xxxx.zip 之后 bin 子目录

sh
export PATH=$PATH:/home/<user>/ohos/command-line-tools/bin

Flutter pub 国内镜像

sh
export PUB_HOSTED_URL=https://mirrors.tuna.tsinghua.edu.cn/dart-pub
# export FLUTTER_STORAGE_BASE_URL=https://mirrors.tuna.tsinghua.edu.cn/flutter
# Flutter 鸿蒙镜像
export FLUTTER_STORAGE_BASE_URL=https://flutter-ohos.obs.cn-south-1.myhuaweicloud.com

# 鸿蒙 tools
export PATH=$TOOL_HOME/tools/ohpm/bin:$PATH # command-line-tools/ohpm/bin
export PATH=$TOOL_HOME/tools/hvigor/bin:$PATH # command-line-tools/hvigor/bin
export PATH=$TOOL_HOME/tools/node/bin:$PATH # command-line-tools/tool/node/bin

拉取下来的 flutter_flutter/bin 目录

sh
export PATH=$PATH:/home/<user>/ohos/flutter_flutter/bin

Flutter 依赖缓存目录

sh
export PUB_CACHE=/<your_path>

常用命令

为现有的插件项目加入 ohos 平台的支持

sh
flutter create . --template=plugin --platforms=ohos

现有 flutter 项目增加 ohos 平台:

sh
flutter create --platforms ohos .

调试:

sh
flutter run --local-engine=<your_path>\ohos_debug_unopt_arm64-1806-windows-AMD64\src\out\ohos_debug_unopt_arm64 -d <device-id>

flutter ohos 插件通用模板

ts
import { AbilityAware, AbilityPluginBinding, FlutterPlugin, Log, MethodCall, MethodChannel } from "@ohos/flutter_ohos";
import { MethodCallHandler, MethodResult } from "@ohos/flutter_ohos/src/main/ets/plugin/common/MethodChannel";
import { FlutterPluginBinding } from "@ohos/flutter_ohos/src/main/ets/embedding/engine/plugins/FlutterPlugin";
import { common, UIAbility, Want } from "@kit.AbilityKit";
import deviceInfo from "@ohos.deviceInfo";
import bundleManager from "@ohos.bundle.bundleManager";

const bundleFlags =
  bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION | bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_SIGNATURE_INFO;
const TAG: string = "FlutterSmsPlugin";
const CHANNEL_NAME = "recognition_qrcode";

export class XPlugin implements FlutterPlugin, MethodCallHandler, AbilityAware {
  getUniqueClassName(): string {
    return "XPlugin";
  }

  onAttachedToAbility(binding: AbilityPluginBinding): void {
    // 获取 ability
    this.ability = binding.getAbility();
  }

  onDetachedFromAbility(): void {
    this.ability = null;
  }

  private bundleManage = bundleManager.getBundleInfoForSelfSync(bundleFlags);
  private methodChannel: MethodChannel | null = null;
  private applicationContext: Context | null = null;
  private ability: UIAbility | null = null;

  onAttachedToEngine(binding: FlutterPluginBinding): void {
    this.applicationContext = binding.getApplicationContext();
    this.methodChannel = new MethodChannel(binding.getBinaryMessenger(), CHANNEL_NAME);
    this.methodChannel.setMethodCallHandler(this);
  }

  onDetachedFromEngine(binding: FlutterPluginBinding): void {
    this.applicationContext = null;
    this.methodChannel?.setMethodCallHandler(null);
    this.methodChannel = null;
  }

  onMethodCall(call: MethodCall, result: MethodResult): void {
    if (call.method === "getPlatformVersion") {
      // call.argument("xxx") 获取参数,如果是复合参数注意使用Map
      Log.d(TAG, deviceInfo.osFullName);
      result.success("HarmonyOS " + deviceInfo.sdkApiVersion);
    } else {
      result.notImplemented();
    }
  }
}

arkts 读取 flutter assets 图片

ts
this.flutterAssets = binding.getFlutterAssets(); // 此处 binding 为 FlutterPluginBinding,从 plugin 的生命周期中获取
// ...
this.flutterAssets.getAssetFilePathBySubpath(path);
const fileData = this.ability?.context.resourceManager.getRawFileContentSync(path);
const buffer = fileData?.buffer;
const source: image.ImageSource = image.createImageSource(buffer);

EventChannel 用法

ts
onAttachedToEngine(binding: FlutterPluginBinding): void {
  this.eventChannel = new EventChannel(binding.getBinaryMessenger(), "demo_plugin_event");
  this.eventChannel.setStreamHandler({
    onListen: (args: Object, eventSink: EventSink) => {
      // eventSink.success(xxx)
    },
    onCancel: () => {
    }
  });
}

onDetachedFromEngine(binding: FlutterPluginBinding): void {
  this.eventChannel?.setStreamHandler(null);
  this.eventChannel = null;
}
dart
final eventChannel = const EventChannel('demo_plugin_event');
Stream<dynamic> e = eventChannel.receiveBroadcastStream();
e.listen((event) {
  //...
});

flutter 外接纹理

首先,Flutter 的渲染机制与 Native 渲染完全隔离,这样的好处是 Flutter 可以完全控制 Flutter 页面的绘制和渲染,但坏处是,Flutter 在获取一些 Native 的高内存数据时,通过 Channel 来进行传递就会导致浪费和性能压力,所以 Flutter 提供了外接纹理,来处理这种场景。

在 Flutter 中,系统提供了一个特殊的 Widget——Texture Widget。Texture 在 Flutter 的 Widget Tree 中是一个特殊的 Layer,它不参与其它 Layer 的绘制,它的数据全部由 Native 提供,Native 会将动态渲染数据,例如图片、视频等数据,写入到 PixelBuffer,而 Flutter Engine 会从 GPU 中拿到相应的渲染数据,并渲染到对应的 Texture 中。

Texture 方案来加载图片的过程实际上是比较长的,涉及到 Flutter 和 Native 的双端合作,所以,我们需要创建一个 Flutter Plugin 来完成这个功能的调用。

flutter 鸿蒙插件中获取 textureId 和 surfaceId

Flutter 和 Native 之间,通过外接纹理的方式来共享内存数据,它们之间相互关联的纽带,就是一个 TextureID,通过这个 ID,我们可以分别关联到 Native 侧的内存数据,也可以关联到 Flutter 侧的 Texture Widget,所以,一切的故事,都是从 TextureID 开始的。

Flutter 加载图片的起点,从 Texture Widget 开始,Widget 初始化的时候,会通过 Channel 请求 Native,创建一个新的 TextureID,并将这个 TextureID 返回给 Flutter,将当前 Texture Widget 与这个 ID 进行绑定。 接下来,Flutter 侧将要加载的图片 Url 通过 Channel 请求 Native,Native 侧通过 TextureID 找到对应的 Texture,并在 Native 侧通过 Glide,用传递的 Url 进行图片加载,将图片资源写入 Texture,这个时候,Flutter 侧的 Texture Widget 就可以实时获取到渲染信息了。 最后,在 Flutter 侧的 Texture Widget 回收时,需要对当前的 Texture 进行回收,从而将这部分内存释放。

ts
import { BinaryMessenger } from "@ohos/flutter_ohos/src/main/ets/plugin/common/BinaryMessenger";
import { TextureRegistry } from "@ohos/flutter_ohos/src/main/ets/view/TextureRegistry";

export class XPlugin implements FlutterPlugin, AbilityAware {
  onAttachedToEngine(binding: FlutterPluginBinding): void {
    let flutterRenderer = binding.getTextureRegistry();
    let textureId = flutterRenderer.getTextureId(); 
    let surfaceTextureEntry: SurfaceTextureEntry = flutterRenderer.registerTexture(textureId);
    let surfaceId = surfaceTextureEntry.getSurfaceId().toString(); 
  }
}

flutter 利用 Texture 渲染

外接纹理文档传送门

dart
@override
Widget build(BuildContext context) {
  return SizedBox(
    height: 200,
    width: 200,
    child: textureId >= 0 ? Texture(textureId: textureId) : Text('loading...')
  );
}

注销

ts
// unregisterTexture
binding.getTextureRegistry().unregisterTexture(this.textureId);

解决外接纹理旋转问题

dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:math' as math;

// ...

class _CameraPageState extends State<CameraPage> {
  // ...

  Widget getTextureBody(BuildContext context) {
    return Container(
      width: 500,
      height: 500,
      child: Texture(
        textureId: textureId,
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    Widget body = textureId>=0 ? getTextureBody(context) : Text('loading...');
    print('build textureId :$textureId');

    return Scaffold(
      appBar: AppBar(
        title: Text("daex_texture"),
      ),
      body: AspectRatio(
        aspectRatio: 1,
        child:Transform.rotate( 
          angle: 90 * math.pi / 180, // 角度修正
          child: Container(
            color: Colors.white,
            height: 500,
            width: 500,
            child: Center(
              child: body,
            ),
          ),
        ),
      ),
    );
  }
}

参考文献

判断是否是鸿蒙

由于 devSDK 还未加入判断方法,使用下面的语句临时解决。

dart
import 'dart:io';
Platform.operatingSystem == 'ohos'

最后编辑时间:

Version 4.2 (core-1.3.4)