Flutter三方库适配OpenHarmony【apple_product_name】插件注册与生命周期管理

前言

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net


Flutter插件的注册和生命周期管理 是确保插件正常工作的关键环节,它决定了插件何时被创建、何时开始接收消息以及何时被销毁。在OpenHarmony平台上,插件注册流程与Android和iOS平台有所不同,但核心思想是一致的------通过框架提供的注册机制 将原生插件与Flutter引擎绑定在一起。本文将结合 apple_product_name 库的真实源码,详细介绍插件从创建到销毁的完整过程。

提示:本文所有源码均来自 apple_product_name 仓库,建议对照源码阅读以获得最佳学习效果。

一、插件注册概述

1.1 注册流程总览

插件注册是Flutter框架在应用启动阶段自动完成的一个关键过程。整个流程可以概括为以下步骤:

  1. 应用启动,系统创建 EntryAbility 实例
  2. EntryAbility.configureFlutterEngine() 被调用
  3. 内部调用 GeneratedPluginRegistrant.registerWith()
  4. 遍历所有插件,依次调用 new XxxPlugin() 创建实例
  5. 框架调用每个插件的 onAttachedToEngine() 完成初始化
  6. 插件进入正常工作状态,开始处理 Dart 端的方法调用

1.2 注册时机

注册发生在引擎配置阶段------此时引擎已完成基本初始化但尚未开始运行 Dart 代码,这是注册插件的最佳时机。注册过程是同步执行的,如果某个插件的注册耗时过长,可能会影响应用的启动速度。

阶段 时机 引擎状态 插件状态
引擎创建 应用启动 初始化中 未创建
引擎配置 configureFlutterEngine 基本就绪 注册中
Dart 启动 runApp() 完全就绪 工作中
引擎销毁 应用退出 销毁中 分离中

注意:开发者无需手动干预注册时机,只需确保插件在 GeneratedPluginRegistrant 中被正确声明即可。详见 Flutter 平台通道文档

二、GeneratedPluginRegistrant 文件解析

2.1 真实源码

以下是项目中 GeneratedPluginRegistrant.ets完整源码

typescript 复制代码
// example/ohos/entry/src/main/ets/plugins/GeneratedPluginRegistrant.ets
import { FlutterEngine, Log } from '@ohos/flutter_ohos';
import AppleProductNamePlugin from 'apple_product_name';

const TAG = "GeneratedPluginRegistrant";

export class GeneratedPluginRegistrant {

  static registerWith(flutterEngine: FlutterEngine) {
    try {
      flutterEngine.getPlugins()?.add(new AppleProductNamePlugin());
    } catch (e) {
      Log.e(
        TAG,
        "Tried to register plugins with FlutterEngine ("
          + flutterEngine
          + ") failed.");
      Log.e(TAG, "Received exception while registering", e);
    }
  }
}

2.2 逐行解析

这个文件虽然只有短短十几行,但每一行都有明确的职责:

行号 代码 作用
1 import { FlutterEngine, Log } 从 Flutter 引擎库导入核心类
2 import AppleProductNamePlugin 从插件包导入插件类
4 const TAG = "GeneratedPluginRegistrant" 定义日志标签,便于过滤
8 flutterEngine.getPlugins()?.add(...) 获取插件注册表并添加插件实例
9-13 catch (e) { Log.e(...) } 捕获注册异常,记录错误日志

2.3 关键设计要点

这个文件有几个值得注意的设计细节:

  • 使用 ?. 可选链操作符 调用 getPlugins(),防止引擎未就绪时空指针
  • try-catch 包裹整个注册过程,单个插件注册失败不会导致应用崩溃
  • 使用 Log.e() 而非 console.log() 记录错误,符合 OpenHarmony 的日志规范
  • 文件头部注释标注 "Generated file. Do not edit",提示这是自动生成的文件

提示:在 OpenHarmony 适配场景下,这个文件可能需要手动维护 。每当项目中添加或移除 Flutter 插件时,都需要相应更新此文件。详见 OpenHarmony Flutter 适配指南

三、EntryAbility 中的注册调用

3.1 真实源码

以下是项目中 EntryAbility.ets完整源码

typescript 复制代码
// example/ohos/entry/src/main/ets/entryability/EntryAbility.ets
import { FlutterAbility, FlutterEngine } from '@ohos/flutter_ohos';
import { GeneratedPluginRegistrant } from '../plugins/GeneratedPluginRegistrant';

export default class EntryAbility extends FlutterAbility {
  configureFlutterEngine(flutterEngine: FlutterEngine) {
    super.configureFlutterEngine(flutterEngine)
    GeneratedPluginRegistrant.registerWith(flutterEngine)
  }
}

3.2 调用链路

复制代码
应用启动
  → 系统创建 EntryAbility
    → configureFlutterEngine(flutterEngine)
      → super.configureFlutterEngine(flutterEngine)  // 父类基础配置
      → GeneratedPluginRegistrant.registerWith(flutterEngine)  // 插件注册
        → flutterEngine.getPlugins()?.add(new AppleProductNamePlugin())
          → AppleProductNamePlugin.onAttachedToEngine(binding)

3.3 调用顺序的重要性

super.configureFlutterEngine 必须在 registerWith 之前调用,原因如下:

  1. 父类方法完成 BinaryMessenger 的初始化
  2. 父类方法设置 Dart 入口点
  3. 插件注册依赖已初始化的 BinaryMessenger 来创建 MethodChannel

如果颠倒顺序,binding.getBinaryMessenger() 可能返回未就绪的对象,导致 MethodChannel 创建失败

注意:EntryAbility 继承自 FlutterAbility,这是 OpenHarmony 平台的 Flutter 容器基类,类似于 Android 的 FlutterActivity

四、插件生命周期四阶段

4.1 阶段概览

插件的生命周期分为四个明确的阶段,每个阶段都有特定的职责:

阶段 触发方法 职责 对应源码位置
创建 constructor() 初始化成员变量 AppleProductNamePlugin.ets 第78行
附加 onAttachedToEngine() 创建 MethodChannel、设置处理器 第84行
运行 onMethodCall() 处理 Dart 端方法调用 第93行
分离 onDetachedFromEngine() 释放资源、清理引用 第88行

4.2 生命周期流转

复制代码
constructor()  →  onAttachedToEngine()  →  onMethodCall() × N  →  onDetachedFromEngine()
   创建               附加                    运行(多次)              分离

每个阶段的转换都由 Flutter 框架驱动 ,开发者需要在对应的回调方法中实现正确的逻辑。特别是在分离阶段,如果资源没有被正确释放,可能会导致内存泄漏甚至应用崩溃。

五、构造函数实现

5.1 真实源码

typescript 复制代码
export default class AppleProductNamePlugin implements FlutterPlugin, MethodCallHandler {
  private channel: MethodChannel | null = null;

  constructor() {
  }
}

5.2 设计原则

apple_product_name 的构造函数是空实现,这是一种刻意的设计选择:

  • channel 初始化为 null,因为此时 BinaryMessenger 尚未就绪
  • 不进行任何复杂操作或资源分配
  • 所有依赖引擎资源的初始化推迟到 onAttachedToEngine

5.3 为什么构造函数要保持简单

构造函数保持简单有三个重要原因:

  1. 避免空指针 :构造时引擎资源未就绪,访问 BinaryMessenger 会失败
  2. 支持多引擎 :每个引擎实例提供独立的 BinaryMessenger,不能在构造时绑定
  3. 快速启动:构造函数在注册阶段同步执行,耗时过长会阻塞应用启动

提示:这种延迟初始化 模式是 Flutter 插件开发的通用最佳实践,不仅适用于 OpenHarmony 平台,在 Android 和 iOS 平台同样适用。

六、附加阶段详解(onAttachedToEngine)

6.1 真实源码

typescript 复制代码
onAttachedToEngine(binding: FlutterPluginBinding): void {
  this.channel = new MethodChannel(binding.getBinaryMessenger(), "apple_product_name");
  this.channel.setMethodCallHandler(this);
}

6.2 两行代码的深层含义

这两行代码完成了插件初始化的全部工作

typescript 复制代码
// 第一行:创建 MethodChannel
this.channel = new MethodChannel(
  binding.getBinaryMessenger(),  // 获取引擎提供的消息传递器
  "apple_product_name"           // 通道名称,必须与 Dart 端一致
);

// 第二行:设置消息处理器
this.channel.setMethodCallHandler(this);  // this 实现了 MethodCallHandler 接口

6.3 通道名称匹配

通道名称 "apple_product_name" 必须与 Dart 端定义的完全一致:

dart 复制代码
// lib/apple_product_name_ohos.dart
class OhosProductName {
  static const MethodChannel _channel = MethodChannel('apple_product_name');
  //                                                   ^^^^^^^^^^^^^^^^^^^^^^^^
  //                                                   必须与原生端完全一致
}
文件 通道名称
Dart lib/apple_product_name_ohos.dart 'apple_product_name'
原生 AppleProductNamePlugin.ets "apple_product_name"

如果两端名称不一致,Dart 端调用 invokeMethod 时会抛出 MissingPluginException

注意:通道名称是大小写敏感 的,"Apple_Product_Name""apple_product_name" 是两个不同的通道。建议使用全小写加下划线的命名风格。

七、分离阶段详解(onDetachedFromEngine)

7.1 真实源码

typescript 复制代码
onDetachedFromEngine(binding: FlutterPluginBinding): void {
  if (this.channel != null) {
    this.channel.setMethodCallHandler(null);
    this.channel = null;
  }
}

7.2 资源释放三步骤

分离阶段的代码虽然简短,但严格遵循了三步释放的顺序:

  1. 移除消息处理器this.channel.setMethodCallHandler(null) --- 停止接收新消息
  2. 清理引用this.channel = null --- 帮助 GC 回收 MethodChannel 对象
  3. 空值保护if (this.channel != null) --- 防止重复调用导致空指针

7.3 为什么顺序很重要

typescript 复制代码
// ✓ 正确顺序:先停止接收消息,再清理引用
this.channel.setMethodCallHandler(null);  // 步骤1:不再接收新消息
this.channel = null;                       // 步骤2:释放引用

// ✗ 错误顺序:先清理引用会导致无法移除处理器
this.channel = null;                       // channel 已为 null
this.channel.setMethodCallHandler(null);   // 空指针异常!

如果颠倒顺序,channel 被置为 null 后就无法再调用 setMethodCallHandler(null),消息处理器将永远不会被移除,可能导致内存泄漏。

提示:apple_product_name 插件没有数据库连接、网络请求等额外资源需要释放,因此分离逻辑非常简洁。如果你的插件持有其他资源,应在移除处理器之后、清理引用之前释放它们。

八、运行阶段详解(onMethodCall)

8.1 真实源码

typescript 复制代码
onMethodCall(call: MethodCall, result: MethodResult): void {
  switch (call.method) {
    case "getMachineId":
      this.getMachineId(result);
      break;
    case "getProductName":
      this.getProductName(result);
      break;
    case "lookup":
      this.lookup(call, result);
      break;
    default:
      result.notImplemented();
      break;
  }
}

8.2 方法路由表

onMethodCall 是插件的消息路由器,根据方法名分发到对应的处理函数:

Dart 端调用 call.method 原生处理方法 参数
getMachineId() "getMachineId" this.getMachineId(result)
getProductName() "getProductName" this.getProductName(result)
lookup(id) "lookup" this.lookup(call, result) machineId

8.3 default 分支的作用

typescript 复制代码
default:
  result.notImplemented();
  break;

当 Dart 端调用了一个原生侧未实现的方法时,result.notImplemented() 会在 Dart 端抛出 MissingPluginException。这是 Flutter 插件的标准约定,确保调用方能明确知道某个方法不被支持。

九、插件导出入口

9.1 index.ets 源码

typescript 复制代码
// ohos/index.ets
import AppleProductNamePlugin from './src/main/ets/components/plugin/AppleProductNamePlugin';
export default AppleProductNamePlugin;
export { AppleProductNamePlugin };

9.2 导出链路

复制代码
GeneratedPluginRegistrant.ets
  → import AppleProductNamePlugin from 'apple_product_name'
    → oh-package.json5 中 main: "index.ets"
      → ohos/index.ets
        → export default AppleProductNamePlugin
          → ohos/src/main/ets/components/plugin/AppleProductNamePlugin.ets

9.3 双重导出的原因

index.ets 同时使用了 default exportnamed export

typescript 复制代码
export default AppleProductNamePlugin;   // 支持 import Plugin from 'xxx'
export { AppleProductNamePlugin };       // 支持 import { AppleProductNamePlugin } from 'xxx'

这种双重导出方式兼容了两种导入风格,提高了模块的易用性。

提示:oh-package.json5 中的 "main": "index.ets" 字段指定了模块入口文件,详见本系列第22篇:oh-package配置详解。

十、getUniqueClassName 方法

10.1 源码

typescript 复制代码
getUniqueClassName(): string {
  return TAG;  // 返回 "AppleProductNamePlugin"
}

10.2 作用说明

getUniqueClassNameFlutterPlugin 接口要求实现的方法,它返回插件的唯一标识符。Flutter 框架使用这个标识符来:

  • 防止同一个插件被重复注册
  • 在日志中标识插件来源
  • 在插件注册表中作为查找键

10.3 命名约定

插件 getUniqueClassName 返回值 TAG 常量
apple_product_name "AppleProductNamePlugin" 与类名一致

建议 getUniqueClassName 返回值与类名保持一致,使用 PascalCase 命名风格。apple_product_name 通过复用 TAG 常量实现了这一点,同时保证了日志标签和唯一标识的一致性。

十一、FlutterPlugin 接口

11.1 接口定义

AppleProductNamePlugin 实现了两个核心接口:

typescript 复制代码
export default class AppleProductNamePlugin 
  implements FlutterPlugin, MethodCallHandler {
  // ...
}

11.2 接口职责对比

接口 来源 核心方法 职责
FlutterPlugin @ohos/flutter_ohos onAttachedToEngine, onDetachedFromEngine, getUniqueClassName 管理插件生命周期
MethodCallHandler @ohos/flutter_ohos onMethodCall 处理方法调用

11.3 为什么分成两个接口

将生命周期管理和消息处理分成两个接口,体现了单一职责原则

  • FlutterPlugin 关注插件与引擎的绑定关系
  • MethodCallHandler 关注具体的业务逻辑处理

这种设计使得一个插件可以将消息处理委托给其他对象,而不必自己实现所有逻辑。apple_product_name 选择让同一个类实现两个接口(this.channel.setMethodCallHandler(this)),因为插件逻辑足够简单,不需要额外的委托。

提示:FlutterPluginMethodCallHandler 都来自 @ohos/flutter_ohos 包,这是 OpenHarmony 平台的 Flutter 引擎运行时库。

十二、多引擎场景

12.1 多引擎的工作方式

在混合开发模式下,一个应用可能同时运行多个 Flutter 引擎实例。每个引擎都会独立创建插件实例:

typescript 复制代码
// 引擎 A 创建的插件实例
const pluginA = new AppleProductNamePlugin();
pluginA.onAttachedToEngine(bindingA);  // 使用引擎 A 的 BinaryMessenger

// 引擎 B 创建的插件实例
const pluginB = new AppleProductNamePlugin();
pluginB.onAttachedToEngine(bindingB);  // 使用引擎 B 的 BinaryMessenger

12.2 实例变量 vs 静态变量

apple_product_name 使用实例变量 存储 channel,这是支持多引擎的关键:

typescript 复制代码
export default class AppleProductNamePlugin {
  // ✓ 实例变量:每个实例独立
  private channel: MethodChannel | null = null;
  
  // ✗ 如果用静态变量:所有实例共享,会导致冲突
  // private static channel: MethodChannel | null = null;
}

使用静态变量的风险:

  • 引擎 B 的注册会覆盖引擎 A 的 channel
  • 引擎 A 的 Dart 端调用会被路由到引擎 B
  • 引擎 A 分离时会误清理引擎 B 的 channel

十三、热重载与插件状态

13.1 热重载对插件的影响

Flutter 热重载时,Dart 代码会被重新加载,但原生插件实例不会被销毁和重新创建。这意味着:

  • constructor() 不会被再次调用
  • onAttachedToEngine() 不会被再次调用
  • onDetachedFromEngine() 不会被调用
  • 插件实例的内存状态保持不变

13.2 apple_product_name 的天然兼容

apple_product_name 插件天然兼容热重载,因为:

  • 插件不持有任何可变状态(没有缓存、没有计数器)
  • 每次 onMethodCall 都是无状态的------直接读取系统 API 或查询映射表
  • deviceInfo.productModelHUAWEI_DEVICE_MAP 都是不变数据

13.3 需要注意热重载的场景

如果你的插件持有可变状态,需要考虑热重载的影响:

typescript 复制代码
// 有状态的插件需要防御性编程
onAttachedToEngine(binding: FlutterPluginBinding): void {
  if (this.channel != null) {
    // 热重载场景:channel 已存在,跳过重复初始化
    return;
  }
  this.channel = new MethodChannel(binding.getBinaryMessenger(), "xxx");
  this.channel.setMethodCallHandler(this);
}

注意:apple_product_name 的源码中没有这个防护,因为 OpenHarmony 平台的 Flutter 框架保证了 onAttachedToEngine 在热重载时不会被重复调用。但在编写自己的插件时,添加这个防护是一种安全的做法

十四、错误处理机制

14.1 注册阶段的错误处理

GeneratedPluginRegistrant 中的 try-catch 确保单个插件注册失败不会影响其他插件:

typescript 复制代码
static registerWith(flutterEngine: FlutterEngine) {
  try {
    flutterEngine.getPlugins()?.add(new AppleProductNamePlugin());
  } catch (e) {
    Log.e(TAG, "Tried to register plugins with FlutterEngine (" 
      + flutterEngine + ") failed.");
    Log.e(TAG, "Received exception while registering", e);
  }
}

14.2 运行阶段的错误处理

每个业务方法内部都有独立的 try-catch:

typescript 复制代码
private getMachineId(result: MethodResult): void {
  try {
    result.success(deviceInfo.productModel);
  } catch (e) {
    const errorMsg = e instanceof Error ? e.message : String(e);
    result.error("GET_MACHINE_ID_ERROR", errorMsg, null);
  }
}

14.3 错误传递链路

复制代码
原生异常 → catch(e) → result.error(code, message, details)
  → MethodChannel 传递
    → Dart 端 PlatformException(code, message, details)
      → 业务代码 try-catch 捕获

错误码遵循大写下划线命名规范:

  • GET_MACHINE_ID_ERROR --- 获取型号标识符失败
  • GET_PRODUCT_NAME_ERROR --- 获取产品名称失败
  • LOOKUP_ERROR --- 查询映射表失败
  • INVALID_ARGUMENT --- 参数缺失或无效

十五、日志记录最佳实践

15.1 TAG 常量

typescript 复制代码
const TAG = "AppleProductNamePlugin";

apple_product_name 在文件顶部定义了 TAG 常量,这个常量被复用于:

  • getUniqueClassName() 的返回值
  • GeneratedPluginRegistrant 中的日志标签
  • 错误日志的前缀标识

15.2 日志级别建议

场景 日志级别 方法 示例
生命周期事件 Debug Log.d(TAG, ...) "onAttachedToEngine called"
方法调用 Info Log.i(TAG, ...) "Method called: getMachineId"
注册失败 Error Log.e(TAG, ...) "Tried to register plugins failed"
业务异常 Error Log.e(TAG, ...) "getMachineId error: ..."

15.3 在 DevEco Studio 中过滤日志

在 DevEco Studio 的 Log 窗口中,可以使用 TAG 过滤插件相关日志:

复制代码
过滤条件: AppleProductNamePlugin

这样可以在大量系统日志中快速定位到插件的运行信息,大幅提升调试效率。

提示:生产环境中建议通过编译开关控制日志输出量,避免敏感信息泄露和性能开销。详见 OpenHarmony 日志管理文档

十六、与 Android/iOS 插件注册的对比

16.1 跨平台对比

维度 OpenHarmony Android iOS
入口文件 EntryAbility.ets MainActivity.kt AppDelegate.swift
注册文件 GeneratedPluginRegistrant.ets GeneratedPluginRegistrant.java GeneratedPluginRegistrant.m
插件基类 FlutterPlugin FlutterPlugin FlutterPlugin
语言 ArkTS Kotlin/Java Swift/ObjC
生成方式 手动/半自动 自动生成 自动生成

16.2 核心差异

OpenHarmony 平台与 Android/iOS 的主要差异:

  • 注册文件维护方式 :Android/iOS 由 flutter pub get 自动生成,OpenHarmony 可能需要手动维护
  • 容器基类 :OpenHarmony 使用 FlutterAbility(对应 Android 的 FlutterActivity
  • 消息传递 :底层都使用 BinaryMessenger,但 OpenHarmony 的实现基于 ArkTS 运行时

16.3 生命周期方法一致性

三个平台的插件生命周期方法完全一致

复制代码
onAttachedToEngine(binding)  --- 三平台统一
onMethodCall(call, result)   --- 三平台统一
onDetachedFromEngine(binding) --- 三平台统一

这种一致性使得有 Android 或 iOS 插件开发经验的开发者可以快速上手 OpenHarmony 插件开发。

十七、完整注册链路图

17.1 从 Dart 到原生的完整链路

dart 复制代码
// Dart 端
final name = await OhosProductName().getProductName();
复制代码
OhosProductName().getProductName()
  → _channel.invokeMethod('getProductName')
    → MethodChannel('apple_product_name')
      → BinaryMessenger 编码消息
        → 跨平台传递
          → AppleProductNamePlugin.onMethodCall(call, result)
            → case "getProductName": this.getProductName(result)
              → deviceInfo.productModel → HUAWEI_DEVICE_MAP 查找
                → result.success(productName)
                  → BinaryMessenger 编码响应
                    → 跨平台传递
                      → Future<String> 完成

17.2 涉及的文件清单

完整的注册和调用链路涉及以下文件:

文件 语言 职责
lib/apple_product_name_ohos.dart Dart Dart 端 API 封装
EntryAbility.ets ArkTS 应用入口,触发插件注册
GeneratedPluginRegistrant.ets ArkTS 插件注册表
ohos/index.ets ArkTS 模块导出入口
AppleProductNamePlugin.ets ArkTS 插件核心实现

十八、常见问题排查

18.1 MissingPluginException

现象 :Dart 端调用时抛出 MissingPluginException

排查步骤:

  1. 检查 GeneratedPluginRegistrant.ets 是否导入了插件
  2. 检查 EntryAbility 是否调用了 registerWith
  3. 检查通道名称是否两端一致("apple_product_name"
  4. 清理构建缓存后重新编译

18.2 插件注册但方法调用无响应

现象:不抛异常但 Future 永远不完成

排查步骤:

  1. 检查 onMethodCall 中的 switch-case 是否覆盖了该方法名
  2. 检查是否每个分支都调用了 result.success()result.error()
  3. 检查 default 分支是否调用了 result.notImplemented()

18.3 快速诊断代码

dart 复制代码
Future<void> diagnosePlugin() async {
  try {
    final ohos = OhosProductName();
    final id = await ohos.getMachineId();
    print('✓ 插件注册正常,machineId: $id');
  } on MissingPluginException {
    print('✗ 插件未注册,请检查 GeneratedPluginRegistrant');
  } on PlatformException catch (e) {
    print('✗ 原生异常: ${e.code} - ${e.message}');
  } catch (e) {
    print('✗ 未知错误: $e');
  }
}

提示:更多调试技巧请参考本系列第29篇:调试技巧与常见问题排查。

十九、最佳实践清单

19.1 构造函数

  • 保持空实现或仅初始化基本变量
  • 不访问引擎资源,不创建 MethodChannel
  • 不执行耗时操作

19.2 onAttachedToEngine

  • 创建 MethodChannel 并设置处理器
  • 通道名称与 Dart 端严格一致
  • 添加异常处理防止注册失败影响应用

19.3 onMethodCall

  • 使用 switch-case 路由方法调用
  • 每个分支必须调用 result 的某个方法(success/error/notImplemented)
  • default 分支调用 result.notImplemented()

19.4 onDetachedFromEngine

  • 先移除处理器,再清理引用
  • 添加空值检查防止重复调用
  • 释放所有在 onAttachedToEngine 中创建的资源

总结

插件注册与生命周期管理是 Flutter 插件开发的基石。apple_product_name 通过简洁的实现展示了一个标准的插件生命周期:空构造函数 → onAttachedToEngine 创建通道 → onMethodCall 处理调用 → onDetachedFromEngine 释放资源。理解这个流程,是开发自己的 OpenHarmony Flutter 插件的第一步。

下一篇文章将介绍 oh-package.json5 配置详解。

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!


相关资源:

相关推荐
早點睡3902 小时前
进阶实战 Flutter for OpenHarmony:AnimatedBuilder 组件实战 - 自定义动画系统
flutter
程序员老刘2 小时前
跨平台开发地图:React Native 0.84 强力发布,Hermes V1 登顶 | 2026年2月
flutter·客户端
松叶似针4 小时前
Flutter三方库适配OpenHarmony【doc_text】— .docx 解析全流程:从 ZIP 解压到 XML 提取
xml·flutter·harmonyos
lqj_本人4 小时前
Flutter三方库适配OpenHarmony【apple_product_name】MethodCallHandler消息处理机制
flutter
西西学代码4 小时前
Flutter---事件处理
flutter
lqj_本人7 小时前
Flutter三方库适配OpenHarmony【apple_product_name】deviceInfo系统API调用
flutter
littlegnal7 小时前
Flutter Android如何延迟加载代码
android·flutter
松叶似针7 小时前
Flutter三方库适配OpenHarmony【doc_text】— onMethodCall 分发与文件路径参数提取
flutter