最近我把一款植物识别类小工具从最初的单端Demo扩展成了支持 iOS、Android 和鸿蒙三个平台的 Flutter 应用,整个过程比预想的有意思,也踩了不少和平台适配有关的坑。这个项目表面上是“拍照识植物”,实际上把 Flutter 跨平台能力、图像预处理、深度模型调度、原生平台权限管理全串在了一起。如果你正打算用 Flutter 开发鸿蒙应用,或者想做图像识别类工具但不知道从哪下手,这篇教程应该能帮你省下不少摸索时间。
项目代号就叫“植识助手”,核心功能很简单:拍照或从相册选一张植物照片,上传到识别服务后返回植物名称、置信度和百科描述,并自动留存历史记录。难的地方在于,这套流程在三个平台上都要跑得顺,尤其是鸿蒙端,既有它自己的工程结构,又有权限和网络栈上的差异。我下面的内容按“整体设计 → 工程搭建 → 界面实现 → 识别能力接入 → 鸿蒙适配 → 排错经验”的顺序展开,每一步都给出可抄的配置和代码,也尽量把当时的取舍逻辑讲清楚。
1. 先想清楚:为什么用 Flutter 做鸿蒙应用,植物识别又该怎么拆模块
1.1 Flutter 上鸿蒙的现状和理解
先说结论:现在 Flutter 开发鸿蒙应用已经不是实验性质了。OpenHarmony 生态里已经有 Flutter 引擎的适配实现,主流版本下可以通过官方工具链把奥哈斯(ohos)作为一个 target platform 来构建产物。你在项目里用 Dart 写一套 UI 和业务逻辑,最后能同时产出 APK、IPA 和 HAP 三种安装包,这一点对于小团队和个人开发者来说价值非常大。
我当时的判断依据很简单:植物识别这种工具类应用,产品形态以表单、列表、图片预览和结果展示为主,几乎没有需要极致原生渲染的复杂动效。Flutter 的 Skia 引擎在鸿蒙上跑这类页面完全够用,开发效率却比维护三套原生代码高很多。另一个原因是团队里没有专门的原生开发同学,Dart 的上手成本明显低于 ArkTS 之外再各写一套 Java / Kotlin 和 Objective-C,所以 Flutter 是当时最合理的路线。
不过要提醒一句:Flutter 支持鸿蒙不是“装个插件就自动全部兼容”。鸿蒙端调试时用的命令、权限声明、应用签名和 Android 差别不小,部分第三方 Flutter 插件也可能没有适配 ohos 平台。这不是 Flutter 的短板,而是任何跨端方案在接入新系统时都会遇到的生态成熟度问题。理解这一点,后面遇到插件报错时心态就不会崩。
1.2 植物识别应用的功能模块拆解
开发之前我把“植识助手”拆成了五个明确的功能块:图片采集、图像预处理、识别请求、结果展示、历史记录。很多人做类似应用时喜欢一上来就调识别 API,忽略了图片质量和体积对接口成功率的影响。实际上识别服务的耗时和准确率,有相当一部分取决于上传图片是否经过压缩、旋转校正和尺寸统一。
- 图片采集:支持调用系统相机和相册选择,这部分由 image_picker 类插件完成。
- 图像预处理:包含 EXIF 方向校正、等比缩放、JPEG 质量压缩,这一步直接决定接口是否会报“图片过大”或“格式错误”。
- 识别请求:采用“原生插件取图 + Dart 层统一封装 + 云端识别服务”的架构。
- 结果展示:展示置信度、候选植物列表、百科描述,并提供相似植物对比。
- 历史记录:本地持久化,用户可回看过往识别结果,给应用增加留存场景。
设计时我特意保留了“前后端分离”的模型:App 端只负责拍照、预处理和渲染,识别逻辑全部放到 API 后面。这样后续如果想把识别服务从云端 API 换成端侧推理模型,App 端只需要改动网络请求层,UI 和业务逻辑完全不用动。这个分层带来的灵活性,在后来调试鸿蒙端网络问题时帮了大忙。
2. 工程搭建与鸿蒙运行环境准备
2.1 Flutter、Dart 与鸿蒙工具链的版本选型
跨端开发最忌讳版本不统一,尤其是鸿蒙这种新平台。我当时的组合是:Flutter 3.22+ 版本分支,Dart 3.x,配合支持 OpenHarmony 的 SDK 和鸿蒙开发者工具。使用命令flutter --version可以快速确认当前 Flutter 版本,如果版本过低,需要先升级再继续。
开启鸿蒙目标支持也有一个关键开关。在终端里执行:
flutter config --enable-ohos执行完成后,再用flutter doctor -v检查输出,正常情况下会出现 ohos toolchain 相关条目。这一步要是没做,后面创建项目时根本看不到ohos平台选项。我用的是 macOS 环境,Windows 下的配置流程类似,重点是把鸿蒙 SDK 路径配到环境变量里,让 Flutter 能定位到编译工具。
有一点需要特别说明:鸿蒙开发工具和 Flutter 的集成度,目前还不是“装完待命”的状态。建议把鸿蒙开发工具先更新到较新版本,同时确认命令行工具hdc(鸿蒙设备连接工具)可用。我开发时遇到过一个奇怪问题:flutter doctor显示一切正常,但运行时检测不到鸿蒙设备,后来发现是hdc的工具路径没进入 PATH。这个问题当时排查了很久,先记在这里。
2.2 创建支持鸿蒙的 Flutter 项目
环境就绪后,创建项目时要把目标平台显式声明出来:
flutter create --org com.example --platforms ohos,android,ios plant_app--platforms参数里带上了ohos,Flutter 就会生成一个名为ohos的目录。这个目录和 Android 工程下的android目录、iOS 工程下的ios目录是对等的,里面是鸿蒙原生工程结构。你可以直接把它理解为鸿蒙版的“宿主壳”,Flutter 的 Dart 代码最终会跑在这个壳里面。
刚生成的工程还不能直接联网和调用相机,需要在鸿蒙模块的配置文件里声明权限。入口文件通常是ohos目录下类似module.json5的文件,在里面配置如下权限:
{ "module": { "requestPermissions": [ { "name": "ohos.permission.INTERNET" }, { "name": "ohos.permission.CAMERA" }, { "name": "ohos.permission.READ_IMAGEVIDEO" } ] } }INTERNET权限用于网络请求,CAMERA用于拍照,READ_IMAGEVIDEO用于读取相册图片。Android 端的权限由 Flutter 插件自身处理,iOS 端则需要在 Info.plist 里补NSCameraUsageDescription和NSPhotoLibraryUsageDescription文案。三条平台的权限模型不同,但业务代码里完全不用感知,因为权限申请统一发生在插件层。
2.3 模拟器与真机调试
鸿蒙应用调试建议优先用真机。原因是模拟器环境对相机的模拟支持比较弱,而植物识别应用最核心的链路就是“摄像头取图→上传→识别”,模拟器里这一步容易失真。连接真机时先用hdc list targets确认设备是否被发现,再执行flutter run -d <deviceId>。首次运行时间会比较长,因为要编译鸿蒙产物并安装到设备上。
日志查看方面,flutter run的控制台会同步输出 Dart 层日志和部分原生层日志,大部分 UI 问题和网络请求异常都能直接看到。如果碰到原生层崩溃,需要通过hdc shell进入设备抓取更底层的日志。整体来说,真机调试流程和 Android 的 adb 思路很接近,但命令关键字不同,熟练之后切换成本并不高。
3. 核心界面与相机/相册能力实现
3.1 首页信息架构与状态管理
“植识助手”的界面结构不复杂:首页是拍照入口和识别历史列表,识别中有一个全屏 loading 状态,识别完成跳到结果页。我用的状态管理方案比较朴素,就是StatefulWidget加FutureBuilder,没有引入 Riverpod 或 Bloc。原因很直接:页面状态只有“空闲 / 识别中 / 成功 / 失败”四种,用 Future 异步流就能表达清楚,引入重量级状态库反而增加了鸿蒙端的依赖风险。
首页布局我用的是 CustomScrollView,顶部是拍摄预览区,下面挂历史记录列表。历史记录从本地读取,列表项显示植物名称缩略图、识别时间和置信度条。用户点击历史记录可以重新进入结果页,这样即使识别接口临时不可用,用户也能看到之前成功的结果,不至于打开应用一无所获。
3.2 拍照与相册选择:image_picker 的集成细节
Flutter 生态里最常用的取图插件是 image_picker,我选用它是因为接口简单,跨平台统一。在pubspec.yaml里添加依赖:
dependencies: flutter: sdk: flutter image_picker: ^1.0.5 image: ^4.1.3 dio: ^5.4.0 shared_preferences: ^2.2.2拍照逻辑的典型写法:
final ImagePicker _picker = ImagePicker(); final XFile? photo = await _picker.pickImage( source: ImageSource.camera, imageQuality: 85, maxWidth: 1600, maxHeight: 1600, );这里imageQuality和maxWidth参数在 Android、iOS 上由插件底层生效,图片在原生层就被压缩,能有效减少内存压力。但要注意,并不是所有平台的 image_picker 实现都支持这些参数,鸿蒙端如果版本较老,可能拿到的是原图。所以我在业务层又做了一层兜底的图像预处理,代码放在 3.3 节。
用户在 iOS 上首次调用相机时会看到权限弹窗,Android 13 以上则依赖系统照片选择器,不需要单独申请存储权限。鸿蒙端的权限我在模块配置里已经声明了,实际弹窗逻辑由系统处理。开发时很容易漏掉的一个问题:一些第三方插件在鸿蒙端没有实现平台通道,直接调用取图接口会抛出 MissingPluginException。遇到这种情况,优先换一个原生能力更完整的插件,而不是硬写一套平台通道去兼容旧版本。
3.3 图片预处理:压缩、旋转与统一尺寸
识别接口对图片体积通常有硬性限制,即使没有限制,大图上传也会拖慢请求速度。我在实现里用 image 库做了一整套预处理,核心步骤是:解码 → 修正 EXIF 方向 → 等比缩放 → 编码 JPEG。这一步相当重要,因为手机照片动不动就是几 MB,原始尺寸可能是 4000x3000,直接上传既慢又容易被服务端拒绝。
import 'package:image/image.dart' as img; Future<File> prepareImage(File input) async { final bytes = await input.readAsBytes(); final decoded = img.decodeImage(bytes); if (decoded == null) { throw Exception('图片解码失败,请检查文件格式'); } final fixed = img.bakeOrientation(decoded); final scaled = img.copyResize( fixed, width: fixed.width > 1080 ? 1080 : null, height: fixed.height > 1080 ? 1080 : null, interpolation: img.Interpolation.linear, ); final encoded = img.encodeJpg(scaled, quality: 85); final output = File('${Directory.systemTemp.path}/plant_preview_${DateTime.now().millisecondsSinceEpoch}.jpg'); await output.writeAsBytes(encoded); return output; }bakeOrientation是最容易被忽略的一步。手机拍摄的 JPEG 文件通常带有 EXIF 方向信息,如果不处理,识别服务拿到的图可能是旋转 90 度的。植物识别模型对旋转敏感,叶片纹理方向错了准确率会明显下降。尺寸方面我以 1080 像素为上限,这个阈值在“保留识别细节”和“压缩体积”之间比较平衡。压缩后的图片我实测一般控制在 150KB 到 400KB 之间,识别服务对这种体积的图片处理速度要快得多。
3.4 识别中与结果页的交互状态
交互状态的设计虽然看起来是 UI 细节,但直接影响用户对“识别是否靠谱”的感知。识别中的 loading 层我用了全屏遮罩加转圈动画,转圈文案用“正在识别叶片纹理”,而不是干巴巴的“加载中”。结果页的核心区域是置信度条和候选植物列表。
置信度条我用自定义进度条实现,数值来自接口返回的 confidence 字段。候选植物列表展示前三个匹配项,每项都有置信度百分比和“最可能”标签。植物百科描述放在折叠区域,默认收起,保持页面清爽。这样设计的好处是:用户第一眼看到的是“识别出了什么”,往下滑才看细节,信息层级清晰。
如果识别失败,结果页会显示错误原因,比如“图片上传超时”或“没有识别到植物特征”。失败时我会额外提供一个“重试”按钮,并把上一张图片保留在预览区域,方便用户直接重试而不是再拍一次。这个体验细节在真实使用中非常加分。
4. 植物识别能力接入:从协议设计到后端调用
4.1 识别服务选型与评价标准
植物识别能力的来源是整条链路里最关键的决策。我梳理了几种方案:公有云植物识别 API、自训练模型部署、端侧推理模型。三者的对比可以总结成下面这张表:
| 方案 | 准确率天花板 | 开发成本 | 离线能力 | 鸿蒙兼容性 |
|---|---|---|---|---|
| 公有云识别 API | 较高,依赖服务商样本库 | 低 | 无 | 好,纯网络调用 |
| 自训练云端模型 | 最高,可定制 | 很高 | 无 | 好,纯网络调用 |
| 端侧推理模型 | 一般,受模型体积限制 | 中 | 有 | 一般,需验证推理库 |
我最终选择了公有云识别 API。原因很实际:自训练模型需要采集大量带标注的植物图片,以我的时间和样本量根本凑不够;端侧模型虽然帅气,但植物种类成千上万,小模型很难同时保证覆盖率和准确率。公有云 API 只要关注网络调用和协议解析,开发量最小。
选服务商的评价标准我也列几条:植物库覆盖度要够大,常见的园艺植物和野草最好都能认;返回字段里必须包含置信度和候选列表,否则前端很难展示丰富结果;接口延迟最好控制在 3 秒以内;还要关注价格模式和是否支持批量请求。我当时把市面上几个主流图像识别服务的免费额度都用了一遍,用同一组测试图片跑对比,最后才确定正式接入哪家。
4.2 接口协议约定与数据结构
正式开发前,我先和识别服务方对了一套接口协议。我习惯把所有对接文档整理成一个 JSON 示例,作为前后端共同的“契约”。植物识别接口的请求约定如下:
- 请求地址:
POST https://api.example.com/v1/plant/recognize - 请求头:
Content-Type: multipart/form-data - 请求参数:
image为二进制文件,top_k为返回候选数,language为 zh/ en
成功响应的典型结构:
{ "code": 0, "message": "ok", "data": { "name": "月季", "scientific_name": "Rosa chinensis", "confidence": 0.93, "candidates": [ { "name": "月季", "confidence": 0.93 }, { "name": "玫瑰", "confidence": 0.04 }, { "name": "蔷薇", "confidence": 0.02 } ], "features": { "leaf_type": "羽状复叶", "flower_color": "红色系" }, "description": "蔷薇科蔷薇属植物,常绿或半常绿灌木……" } }协议里我特别要求返回scientific_name(学名),因为中文名存在地域习惯差异,同一个植物在不同地区叫法不同,学名是唯一标识。判断一段协议设计得好不好,就看字段能否支撑多种 UI 需求。这里的 design 里包含了“名称展示、候选对比、特征标签、百科描述”,结果页所有组件都能直接映射到字段上。
对应 Dart 数据模型:
class PlantRecognition { final String name; final String scientificName; final double confidence; final List<PlantCandidate> candidates; final String description; final Map<String, String> features; PlantRecognition.fromJson(Map<String, dynamic> json) : name = json['name'], scientificName = json['scientific_name'] ?? '', confidence = (json['confidence'] as num?)?.toDouble() ?? 0, candidates = (json['candidates'] as List? ?? []) .map((e) => PlantCandidate.fromJson(e as Map<String, dynamic>)) .toList(), description = json['description'] ?? '', features = Map<String, String>.from(json['features'] ?? {}); }我把模型类放在独立文件中,禁止在 UI 代码里直接解析 JSON。这样测试时可以用本地 JSON 文件 mock 数据,不依赖网络,跑 UI 测试和做界面调试都非常方便。
4.3 Flutter 端网络请求封装
网络层我用了 Dio。Dio 在鸿蒙端的适配情况比我预期好,常规网络请求、超时配置、拦截器都能正常工作。识别请求的封装代码:
class RecognizeApi { final Dio _dio; RecognizeApi(this._dio); Future<PlantRecognition> recognize(String imagePath) async { final formData = FormData.fromMap({ 'image': await MultipartFile.fromFile(imagePath, filename: 'plant.jpg'), 'top_k': 3, 'language': 'zh', }); final response = await _dio.post( '/v1/plant/recognize', data: formData, options: Options( contentType: 'multipart/form-data', sendTimeout: const Duration(seconds: 15), receiveTimeout: const Duration(seconds: 30), ), ); if (response.statusCode != 200) { throw ApiException('识别服务返回异常状态码: ${response.statusCode}'); } final json = response.data as Map<String, dynamic>; if (json['code'] != 0) { throw ApiException(json['message'] ?? '识别失败'); } return PlantRecognition.fromJson(json['data'] as Map<String, dynamic>); } }几个容易踩的点:一是超时时间不能设太短,弱网环境下图片上传本身就要花时间,15 秒发送超时、30 秒接收超时是实测下来的合理值;二是失败重试时不要把整个表单原样重发,合理做法是保留已经压缩好的图片文件,重新构造 FormData,避免重复读取大文件;三是多个请求并发时要用独立的 Dio 实例或请求取消令牌,否则用户快速连续拍照,前面未完成的请求可能后返回,把旧结果显示在新页面上。
CancelToken _cancelToken; void startRecognize() { _cancelToken?.cancel(); _cancelToken = CancelToken(); }这一个简单的取消逻辑,就避免了我后来在真机上才发现的“旧请求覆盖新结果”的经典 Bug。
4.4 缓存与离线兜底策略
识别历史记录我用 SharedPreferences 存 JSON 字符串,字段结构为:图片本地路径、识别结果、时间戳。这样用户在不知道网络好不好时也能回看纪录。缓存写入时我会做长度限制,最多保留最近 30 条,超出时移除最老数据,避免本地存储无限膨胀。
更有意思的兜底方案是内置一个精简植物库。我在 assets 里放了一份plant_lite.json,包含约 30 种常见植物的特征关键词,比如叶形、花色、花期、生长环境。当网络不可用或识别接口返回空结果时,App 会根据用户填写的观察描述做关键词打分匹配,给一个“可能是”的结果列表。这个功能虽然准确率不高,但让应用在极端条件下不是完全不可用,对用户留存有正向帮助。
离线兜底的逻辑要跟主识别流程解耦。我在架构里定义了一个抽象接口PlantRecognizer,线上实现走 API,离线实现走关键词打分。UI 层只面向抽象接口编程,后续想换识别引擎都不用改页面代码。这个设计思想不复杂,但在实际项目里价值极高。
5. 鸿蒙平台适配与打包发布经验
5.1 权限与隐私声明
鸿蒙应用的权限体系有自己的约束,应用市场审核时对权限用途说明要求比较细。在module.json5里声明了CAMERA和READ_IMAGEVIDEO后,还需要在应用内添加隐私政策文本,解释为什么需要调用相机和读取相册。我的做法是首次进入应用时弹一个自定义的权限预说明弹窗,把拍照识别植物这个核心用途讲清楚,再引导用户授予权限。
这里有个细节:鸿蒙的READ_IMAGEVIDEO权限涉及相册图片读取能力,如果你只使用系统照片选择器由用户主动挑选单张图片,部分场景下不需要申请整套读取权限。但植物识别应用需要用户连续浏览并挑选图片,为兼容老年用户和系统版本差异,我保留了该权限声明。上架审核时,如果审核方对权限用途有疑问,会要求补充说明,提前写好录音和截屏证据能避免不必要的来回沟通。
5.2 鸿蒙网络模块与图片上传的兼容处理
Dio 在鸿蒙端底层的实现与 Android 的 OkHttp、iOS 的 URLSession 不同,但应用层代码完全不用感知。我唯一需要额外处理的点是:鸿蒙系统默认对明文 HTTP 请求有限制,因此正式环境的识别接口必须使用 HTTPS。开发阶段如果服务商提供的是 HTTP 测试地址,可以临时在鸿蒙工程里配置网络安全例外,但上架前一定记得移除。为了不给自己留隐患,我从一开始就让识别的接口使用 HTTPS,本地 mock 服务则走 Flutter 的过路代理调试。
图片上传方面,MultipartFile.fromFile在鸿蒙端实测可用,上传速度和 Android 差别不大。如果遇到某些网络栈插件不兼容的情况,可以将图片转成 Base64 放在 JSON 里传输,牺牲一点体积换取兼容性。不过这个方案我最终没有采用,因为 Base64 会让请求体增加约 33% 的体积,在弱网下体验更差。
5.3 打包 HAP 与上架前检查
构建鸿蒙安装包的命令是:
flutter build hap --release构建产物通常在build/ohos/release/目录下,格式为.hap文件。与 Android 需要签名 APK 类似,HAP 也需要配置签名才能在真机安装。开发阶段可以在鸿蒙开发者工具里申请自动签名,发布前则要配置正式签名信息。签名配置错误时,安装阶段会直接报错,而且报错信息非常隐晦,像是install failed due to invalid signature,我第一次遇到时完全摸不着头脑,后来才发现是签名文件路径配错了。
上架前我列了一个可视化检查清单:应用名称和图标是否完整、隐私政策链接是否可访问、权限说明是否与代码中的调用一一对应、HAP 包体积是否过大、在多分辨率设备上是否有布局溢出。表格形式记录每条检查项的状态,能避免发布前夜手忙脚乱。小团队没有专职测试时,这套清单是保证质量的最后防线。
6. 开发中的典型问题与调试记录
6.1 识别接口返回慢或被频繁超时
现象:点击识别后 loading 持续十秒以上,最终提示超时。
排查路径:先用curl直接测试识别服务的接口连通性,排除服务端故障;再对比同一张图片在 Android 和鸿蒙设备上的上传耗时;最后查看请求日志,判断是发送阶段慢还是接收阶段慢。
解决办法:把图片压缩逻辑前置到 1080 像素以内、JPEG 质量 85,确保上传体在 400KB 以下;同时把 Dio 的发送超时设为 15 秒、接收超时设 30 秒。经过压缩和参数调整后,识别耗时从原来的 8 秒左右降到 2 到 3 秒,体感改善非常明显。
弱网下频繁超时时,我加了指数退避重试:第一次失败等 1 秒,第二次等 2 秒,第三次等 4 秒,最多重试两次。重试时要取消旧请求,避免出现乱序结果。
6.2 结果中文显示乱码
现象:返回的植物名称在页面上显示为乱码。
原因:服务端返回的字符编码与默认解析方式不一致,Dart 默认按 UTF-8 处理部分响应体。
解决办法:在 Dio 中设置responseType: ResponseType.bytes,然后用utf8.decode(response.data)手动解码。实际调试中定位这个问题花了不少时间,因为服务端返回 JSON 的头部没有显式声明编码,不同网络库的默认行为又不一样,建议在对接协议时就明确要求统一 UTF-8。
6.3 真机连不上或 HAP 安装失败
现象:flutter run -d找不到鸿蒙设备,或者 HAP 无法安装到真机。
排查路径:先用hdc list targets检查设备连接,确认设备是否开启开发者模式和 USB 调试;如果设备在线但安装失败,优先看签名错误。
解决办法:把hdc所在目录加入 PATH;重新生成并配置签名文件;确认设备的鸿蒙版本高于应用targetSdkVersion要求。这类问题多和设备环境有关,不是 Flutter 或 Dart 代码的问题。
6.4 识别准确率不满意怎么办
现象:常见植物识别经常出错,候选列表第一名不是正确结果。
改善思路:拍照质量是首要因素,光线不足、植物占比太小、背景杂乱都会影响模型判断。我在应用里增加了“拍摄引导”浮层,提醒用户让植物居中、背景简洁、尽量拍叶片和花朵的特写。技术调整:把请求参数中的top_k从默认 1 调到 3,前端展示候选列表,用户手动选择正确项后可以反馈给服务端用于后续优化。如果对某个植物类别的准确率特别不满意,可以单独收集这个类别的样本,在服务方后台做针对性模型微调。
我把这些典型问题整理成一组速查表,放在项目文档里作为维护手册的一部分:
| 问题 | 常见原因 | 快速处理 |
|---|---|---|
| 接口超时 | 图片未压缩 / 超时太短 | 统一压缩到 1080px、85% 质量,调整超时参数 |
| 中文乱码 | 编码不一致 | 设置 ResponseType.bytes,手动 UTF-8 解码 |
| 找不到设备 | hdc 未配置 / 未开调试 | 检查 hdc 路径,确认设备授权 |
| 安装失败 | 签名错配 / 版本不匹配 | 重新签名,核对 targetSdkVersion |
| 识别不准 | 拍照质量差 / top_k 太小 | 加拍摄引导,调大 top_k,收集反馈样本 |
关于鸿蒙适配和 Flutter 开发,我自己最大的体会是:不要被“新平台”三个字吓住,把应用拆成采集、预处理、识别、展示、记录这几个独立环节后,鸿蒙适配的工作量其实非常聚焦。真正花时间的不是编写 Dart 代码,而是处理权限声明、网络请求兼容和打包签名这些原生层面的细节。如果你按照上面的模块拆分来动手,会发现鸿蒙端的坑数量远远少于预期。
最后分享一个我一直在用的小技巧:把 API 地址和接口 Key 通过--dart-define传入,而不是硬编码在 Dart 文件里,比如flutter run --dart-define=API_BASE_URL=https://api.example.com。这样开发、测试、生产环境可以随时切换,不用反复改代码;配合debugPrint统一开关,上线前关闭所有调试日志也能在同一个地方完成。希望这篇记录对你的 Flutter 鸿蒙开发之路有一点参考价值。