ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Flutter迁移OpenHarmony:网络请求适配与工程化落地实践

Flutter迁移OpenHarmony:网络请求适配与工程化落地实践 1. 这工程到底在改什么先理解D3-Flutter的定位我们内部把一套业务App命名为D3这个D3没有别的花哨含义就是第三次重构交付的代号。这次重构做了两件让人头疼的事第一把原本只跑在Android和iOS上的Flutter业务工程迁到开源鸿蒙OpenHarmony设备上第二让迁移过去的页面能直接在Flutter层发网络请求而不是绕去原生侧套一层壳。这篇博客把这两件事拆开讲清楚给那些正在做同类适配的团队一个参考路径。先说结论Flutter跑上OpenHarmony这件事技术上完全可行但工程化落地比想象中要碎。你不仅要处理引擎编译产物、平台通道映射、包管理差异还要把网络权限、证书校验、缓存策略这些底层能力重新捋一遍。D3项目前后花了两周半其中真正写业务代码只占三分之一剩下时间全耗在四件事上环境搭建、模块集成、网络适配、真机排坑。这也是我写这篇文章的原因——把这四件事的完整路径和坑位记录下来你至少能少走一半弯路。适合看这篇文章的人有两类。一类是手里有成熟Flutter项目、正在评估迁移到鸿蒙的团队负责人你可以跳过前半段的基础集成说明重点看网络请求适配和真机问题清单。另一类是刚接触Flutter、对原生与跨平台边界还不熟悉的开发同学建议从头到尾跟着思路走一遍尤其注意每一节里的“为什么这么做”这些比代码本身值钱。1.1 为什么选择Flutter作为鸿蒙侧的跨平台层技术选型时我们不是没对比过其他方案。React Native在鸿蒙上的社区适配还处在个人维护阶段部分原生模块要等版本匹配自研跨平台引擎的维护成本对中小团队来说又太重。Flutter胜出的原因很朴素官方适配仓库已有活跃维护常用插件在OpenHarmony的兼容列表上能搜到替代品再加上我们团队本身就有Flutter技术积累迁移学习曲线最陡的部分——Dart语法和组件化思维——完全不需要重新投入。另一个关键点是Flutter的渲染引擎不依赖系统WebView。在鸿蒙设备上如果业务页面大量依赖浏览器内核不同版本的系统裁剪会导致渲染表现不一致。Flutter通过自绘引擎把UI层做成了相对独立的世界页面帧率、动画流畅度、文本排版都更可控这一点在低端开发板上尤其明显。D3项目在RK3568开发板上的帧率稳定在50帧以上比我先前的预期好不少。不过要泼一盆冷水Flutter在OpenHarmony上目前仍处于“可用但不够顺滑”的阶段。部分原生插件需要自己写Platform Channel适配热重载在某些场景下会丢状态打包产物比Android侧略大。这些不是致命问题但要在排期里留出缓冲区。1.2 D3工程的整体目标拆解D3工程的目标可以拆成三个可验收的层级。第一层是“跑起来”Flutter页面以原生组件方式嵌入鸿蒙Application事件和路由能正常传递第二层是“发请求”Flutter侧的Dart代码统一管理网络请求响应能完整回到业务层第三层是“抗故障”弱网环境下有重试和超时策略断网时有及时的用户提示不会出现白屏或崩溃。三层目标的验收标准我定得很具体。“跑起来”以Flutter首页首帧渲染时间不超过2秒为准“发请求”要求同一套Dart网络代码在Android和鸿蒙两侧返回的数据结构完全一致“抗故障”则要求弱网断连时错误信息准确度达到95%以上。这些标准写进项目文档后团队里每个人对接下来的工作边界都清晰了。2. 工程集成实操把Flutter引擎接到开源鸿蒙上工程集成是整个改造的地基。这一节我不会贴全套代码而是把关键路径和参数选择讲清楚。这部分内容足够让你在自己的工程里复现一遍顺便避开我们踩过的几个硬坑。2.1 环境准备与版本选型先说环境版本因为版本不匹配是集成阶段最普遍的报错来源。我们的组合是Flutter 3.22分支使用OpenHarmony社区的适配fork、OpenHarmony SDK 4.1 Release、DevEco Studio 4.0。这个组合是我们反复试出来的稳定搭配。如果你用的Flutter官方主线版本需要确认是否带ohos平台目录否则后面gradle脚本会找不到目标平台。另外要特别注意Node.js和ohpm的版本。鸿蒙侧的依赖管理工具是ohpm相当于Android生态里的Maven/Gradle仓库。首次构建时ohpm需要拉取大量依赖包如果网络状态不稳会出现包下载不完整的情况表现就是编译时突然报某个符号找不到。我们的做法是在接入第一天就把依赖缓存完整拉一遍之后开发过程中基本不再触碰这个环节。2.2 创建Flutter Module并生成鸿蒙壳工程把Flutter集成到鸿蒙工程里官方推荐方式是“Flutter Module 原生壳工程”。我们按这个路径操作新建Flutter工程作为Module然后在DevEco Studio里创建标准的HarmonyOS应用工程作为宿主壳。壳工程负责OpenHarmony的系统生命周期、权限配置和原生能力分发Flutter Module负责业务页面和横跨Android/鸿蒙的通用逻辑。需要注意一个细节鸿蒙侧的页面路由是通过Ability承载的。我们的实践是创建一个主Ability来承载FlutterContainer用它作为Flutter页面的挂载点。这样设计的好处是后续如果需要打开新的原生页面可以直接从Flutter侧通过MethodChannel调起新的Ability不会出现页面栈错乱。2.3 打通Flutter与鸿蒙侧的通信通道Flutter与原生通信是三端互通的关键。D3工程里我们实际用到了三种通道MethodChannel处理一次性的方法调用比如“打开原生扫码页”“获取设备唯一标识”EventChannel处理持续性的数据流比如网络状态变化、定位更新BasicMessageChannel处理高频的自定义消息比如页面埋点事件。三者的职责必须分清楚否则代码会很快变成一锅粥。在鸿蒙侧的ArkTS代码里MethodChannel的注册逻辑和Android的逻辑几乎一一对应但要注意生命周期绑定。我们把Channel注册放在Ability的onPageShow方法里在onPageHide时解除监听避免页面频繁切换时出现回调泄漏。这类问题在真机上比较隐蔽通常表现为页面二次进入后事件不响应排查时很难定位。2.4 打包产物与多端复用策略集成完成后要解决的是产物交付问题。D3工程采用的方式是Flutter侧打出flutter_ohos产物包放到鸿蒙工程的依赖目录中同时保留Android的构建产物做到一份业务代码双端出包。这个策略在CI流水线里同样适用只需在构建脚本里区分目标平台其他逻辑可以完全复用。有几个细节值得提一下。Flutter引擎的产物包在鸿蒙上不是存储到assets目录而是存放在libs目录下构建脚本要特别注意路径映射否则会出现运行时找不到libflutter.so的情况。另外鸿蒙侧清理缓存时要小心不要顺手把Flutter的本地持久化目录也清掉我们曾因为这个问题导致用户token失效排查了整整一下午。3. 网络请求层落地选库、配权限、写拦截器网络请求是整个D3工程改造的重头戏。Flutter层的网络库在Android上跑得好好的切到鸿蒙之后首先是库本身的兼容性其次是系统权限与通道差异最后才是业务层的拦截与重试逻辑。这一节按这三层顺序展开。3.1 网络库选型为什么最终保留DioFlutter生态里的网络库选项屈指可数主流的就是dio和http。我们最终保留了dio原因有三个拦截器机制完善、支持取消请求、生态里的兼容层最齐全。在鸿蒙适配过程中dio底层走的是Dart的HttpClient实现而Dart SDK在鸿蒙上的运行环境已经由社区做了适配所以正常情况下不需要改业务代码就能发请求。但有个例外需要留意如果你之前用了依赖平台特定网络栈的库比如需要原生WebView辅助的混合请求这类库在鸿蒙上大概率不能直接用。D3工程的处理方案是借助PlatformChannel把这些特殊请求分发到原生侧执行返回结果再传回Dart层。虽然绕了一圈但至少保证了业务接口不变。3.2 权限声明与Android侧的差异对比在Android上我们习惯在AndroidManifest里声明网络权限鸿蒙的声明方式完全不同。OpenHarmony使用module.json5文件网络相关的权限要在这里注册。D3工程用到的权限有两类ohos.permission.INTERNET用于普通的网络请求ohos.permission.GET_NETWORK_INFO用于读取当前网络状态。漏掉第二个权限不会导致编译失败但运行时会静默返回无网络状态这类问题在日志里很难定位。另外HarmonyOS Next风格的权限弹窗在DevEco上默认是关闭的需要在配置里显式声明使用场景。我们在首次集成时漏掉了这个导致真机上直接抛权限异常。后来把设备网络权限改成“使用时询问”弹窗流程才恢复正常。3.3 证书校验与HTTPS处理网络请求的证书问题在鸿蒙设备上的处理节奏和Android类似但自签名证书的处理入口不同。Android有网络安全配置文件鸿蒙支持在资源配置里加证书信任设置。D3工程面对的主要是测试环境使用自签名证书的场景我们的处理方案是开发环境关闭严格校验预发布环境启用双向校验线上环境保持默认校验策略。这里要特别强调不要把开发用的关闭校验策略直接带上线。我们做过一次风险评估如果HTTPS证书默认不校验抓包工具可以轻松解密全部流量对用户数据安全风险极大。D3的做法是用构建模式区分环境在入口配置里注入不同的校验逻辑确保发布包里的策略是严格的。3.4 拦截器体系与Token刷新统一收口D3的网络层统一由三层拦截器管理。第一层是日志拦截器记录请求URL、耗时、响应状态码开发阶段按接口维度过滤线上按需关闭第二层是Token注入拦截器从本地存储读取AccessToken自动加到请求头第三层是错误处理拦截器根据状态码统一跳转登录、刷新Token或弹出错误提示。Token刷新是一个隐藏的坑点。我们起初直接在每个401响应里触发刷新结果同一秒内有多个请求同时失败同时发起多次刷新请求导致Token刷新接口被频繁调用。后来加了一个全局锁刷新期间其他请求先挂起等新的Token回来后统一重放。这个改动让D3工程的请求成功率从91%提升到了99.3%效果立竿见影。4. 核心实现拆解从请求管理器到断网感知这一节把D3工程里网络层几个核心模块的代码骨架和设计思路拆开讲讲。代码我做了简化但保留了关键逻辑你可以直接参考到自己工程里。4.1 请求管理器统一入口统一出口请求管理器的职责是隔离业务层与具体网络实现。业务方调用D3Net.get或D3Net.post管理器内部负责拼URL、加公共参数、走拦截器、处理异常。这样做的最大好处是如果某天需要把dio替换成别的库业务层代码可以完全不动。class D3Net { static final D3Net _instance D3Net._internal(); late Dio _dio; D3Net._internal() { _dio Dio(BaseOptions( baseUrl: AppConfig.baseUrl, connectTimeout: Duration(seconds: 10), receiveTimeout: Duration(seconds: 15), )); _dio.interceptors.addAll([ LogInterceptor(), TokenInterceptor(), ErrorInterceptor(), ]); } FutureT getT(String path, {MapString, dynamic? query}) async { try { final resp await _dio.getT(path, queryParameters: query); return resp.data as T; } on DioException catch (e) { throw D3NetException.fromDioException(e); } } }这里有一个参数细节值得关注connectTimeout和receiveTimeout我们分别设为10秒和15秒而不是统一用一个值。弱网环境下连接超时和响应超时的时间语义完全不同分开设置能更精确地定位是“连不上”还是“响应慢”。我们曾经统一设成20秒导致部分慢接口耗时二十几秒才报错用户体验非常差。4.2 断网感知EventChannel实现实时状态推送断网状态处理是移动端网络请求绕不开的话题。D3工程的做法是用EventChannel把原生侧的网络状态变化实时推送到Flutter层。原生侧监听网络连接变化一旦状态切换就通过EventChannel发送事件Flutter侧收到事件后更新全局状态并触发页面的局部提示。// 鸿蒙侧网络监听简化 let eventChannel new EventChannel(d3/network/status); this.subscription eventChannel.onReceiveEvent((event) { let data event.data as NetworkStatus; // 将status推送到Flutter侧 });这个设计解决了“等请求失败才知道断网”的被动局面。我们做了个对比实验在无网络环境下打开App未接入状态感知时用户要等10秒超时才能看到错误页接入之后页面在300毫秒内就能收到断网事件并显示友好提示。对于移动场景来说这1秒左右的体验差距足以影响用户对App稳定性的整体印象。4.3 取消请求与页面生命周期联动网络请求的取消逻辑我们一开始忽略了。后来发现一个现象用户快速切换页面时前一个页面的请求仍然在途返回结果后触发了setState导致页面状态错乱甚至崩溃。D3工程给出的方案是在每个页面的State对象里维护一个请求取消令牌集合页面销毁时统一取消未完成的请求。class _PageState extends StatePage { final _cancelTokens CancelToken[]; Futurevoid _loadData() async { final token CancelToken(); _cancelTokens.add(token); try { final data await D3Net.get(/list, cancelToken: token); setState(() { /* update ui */ }); } on DioException catch (e) { if (e.type DioExceptionType.cancel) return; // 处理其他错误 } } override void dispose() { for (final token in _cancelTokens) { token.cancel(page disposed); } super.dispose(); } }取消请求的核心价值不只是省流量更在于避免“野回调”。特别是涉及用户登录态的操作如果页面已经销毁请求返回后不应该再弹出任何对话框。这个细节如果不处理很容易在线上收到“莫名其妙的弹窗”这类用户反馈而问题根源其实在挂起的页面回调。4.4 下拉刷新与请求重试的联动策略D3工程的首页列表用到了下拉刷新最初我们简单地在刷新回调里重新发起请求。后来发现弱网环境下用户下拉刷新后可能迟迟看不到新数据而且没有二次反馈。我们把下拉刷新和自动重试机制做了联动刷新失败时保留旧数据显示轻量Toast提示同时后台自动重试两次间隔分别为2秒和5秒。如果两次重试都失败才显示空态指导用户再次下拉。这个策略的核心思路是下拉刷新是用户的主动意图任何一次静默失败都会让用户觉得“没反应”。加上自动重试后至少给用户一个连续反馈的过程而不是刷完就一切照旧。我们的线上数据表明加了自动重试后首页的刷新失败率下降了60%左右效果还是明显的。5. 踩坑实录真机联调里的五个典型问题把工程跑通只算完成一半真机调试阶段才是对耐心的真正考验。这一节把我们在D3工程里遇到的五个典型问题按“现象-原因-解法”的结构整理出来你可以直接当速查表用。5.1 现象请求一直超时但浏览器和原生请求都正常排查过程很曲折。同样的接口在原生应用里能请求通在Flutter页面里就一直超时。后来发现Flutter的HttpClient默认不继承操作系统的网络配置需要手动指定代理设置。来来回回折腾了快一天最终的解法是在Flutter启动时读取系统网络配置并应用到HttpClient上。这个问题的经验是跨平台框架带来的网络层“真空地带”是普遍存在的不要假设框架会自动继承系统配置。遇到类似问题时优先排查Flutter侧的网络配置是否与系统一致而不是怀疑业务代码逻辑。5.2 现象HTTPS请求在Release包上失败Debug包正常Debug包和Release包表现不一致这类问题通常和证书信任策略有关。我们的Debug包构建时默认关闭了证书校验所以能正常请求Release包构建时开启了严格校验而测试环境的证书链并不完整导致握手失败。解法是把测试环境的根证书安装到鸿蒙设备的用户信任区同时在Release包的资源配置里单独维护一份测试环境证书信任列表。千万不要为了图方便在Release包上关闭校验前面已经说过这个风险实在太大。5.3 现象Flutter页面切换到原生页面再回来EventChannel事件丢失这是典型的生命周期问题。我们起初在Ability的onPageHide里解绑了EventChannel但重新进入页面时没有及时恢复监听导致原生侧一直在发事件Flutter侧却收不到。更糟的是Flutter侧的StreamSubscription还保留着但事件已经被丢弃了。解法是把恢复监听的逻辑放在onPageShow里并且在重新监听后主动请求一次当前网络状态作为补偿而不是等下一次状态变化才上报。这个“主动拉取一次当前状态”的小技巧在很多实时性场景里都适用。5.4 现象打包时提示Flutter版本不支持无法生成鸿蒙产物这个问题出在Flutter版本和鸿蒙适配版本的匹配上。我们最开始用官方最新的Flutter 3.24主线分支但该版本还未完全支持ohos平台所以打包脚本直接报错。后来回退到OpenHarmony社区适配的Flutter 3.22分支问题立刻消失。这里建议团队固定一个经过验证的组合版本并且把版本号写进工程文档首页。D3工程后续每次升级Flutter版本都会先在本地跑一遍鸿蒙和Android的双端冒烟测试通过之后再合并到主干分支。5.5 常见问题速查表现象排查思路解法请求超时检查Flutter侧网络配置是否继承系统设置手动读取并应用系统网络配置Release包证书失败检查证书校验策略与环境差异分环境维护信任列表EventChannel事件丢失检查页面生命周期与监听恢复onPageShow恢复监听并主动拉取一次状态版本不支持检查Flutter与SDK版本匹配固定经验证的组合升级前先冒烟页面销毁后仍有回调检查请求取消逻辑维护CancelToken集合并在dispose统一取消权限已声明但获取失败检查module.json5权限类型正确声明网络权限与应用场景第六个问题是很多新人都会忽略的你以为权限声明加了但OpenHarmony要求“权限声明使用场景”同时满足才算真正授权缺一个都会静默失败。在Android上养成的习惯在鸿蒙上要重新适应一遍。尾声我的一点实际体会D3项目做完之后我最大的感觉是跨平台框架在鸿蒙上的适配最难的地方已经被社区解决了大半剩下的工程化细节才是真正耗时间的部分。Flutter的Dart层代码几乎可以无差别地在Android和鸿蒙两端运行但每一层“桥”的两侧都需要你亲手去夯一遍——网络配置、生命周期、权限声明、包产物每一处都和平台特性强相关。如果你正在规划类似的迁移我建议从一个小模块开始试点先把网络请求通道打穿再逐步扩大页面范围。千万不要一开始就想着全量迁移那只会让你被一堆交叉问题淹没。D3工程就是先拿登录页和首页列表做了技术验证确认双端行为一致后才逐步铺开的。最后分享一个小技巧把调试阶段的抓包工具配上真机联调时很多“玄学问题”看一眼请求日志就能定位。我们后期排查效率提升的拐点就是全员养成了“先看包再改代码”的习惯。希望这篇博客能帮你在鸿蒙适配的路上少踩几个我们踩过的坑。
返回列表