Flutter到底怎么在鸿蒙上跑通,这个问题我前后折腾了两周才彻底理顺。当时手头正好要做一个地理知识速记APP,需求很明确:既要覆盖大部分用户手里的安卓机,又得兼顾越来越多的鸿蒙设备,还不能接受用两套原生代码分别维护的成本。最终方案就是用Flutter统一开发,再通过鸿蒙的Flutter适配层做编译打包。这篇就完整记录一下整个开发流程,从环境搭建到数据模型,从卡片记忆算法到多端打包,把该避开的坑全部摊开来讲。
这篇内容适合谁看?一是准备入坑Flutter跨平台开发的新手,二是已经在用Flutter但需要适配鸿蒙的团队,三是对地理类学习工具感兴趣、想自己动手做一款速记App的产品型开发者。整篇文章会围绕“跨平台怎么落地”和“速记类App的工程结构怎么搭”两条主线展开,没有多余废话。
1. 项目缘起与整体设计思路
1.1 为什么选Flutter做鸿蒙适配
做这个选择之前,我把三条路都摆在桌面上对比过。第一条路是鸿蒙原生开发,用ArkTS加ArkUI去写,体验肯定是最好的,但代价是要单独维护一套代码,而且原来的安卓团队得全员学习新语言,时间成本根本压不下来。第二条路是React Native,生态虽然成熟,但在鸿蒙上的适配层一直没有Flutter那么被官方上心,很多原生模块要靠自己去找社区补丁。第三条路就是Flutter,它的渲染引擎是自绘的,不依赖系统原生控件,这套机制天然对多平台更友好一点。
当时让我下决心的关键点有两个。第一,Flutter的鸿蒙适配已经有官方或半官方的维护分支,环境变量、编译链、构建产物都已经通到了鸿蒙的应用市场体系,不是靠野路子硬塞进去的。第二,Flutter的插件生态里,存储、网络、状态管理这些纯逻辑依赖基本是平台无关的,也就是说把这些插件在鸿蒙适配层走一遍,绝大多数不需要改业务代码。实测下来,项目里的核心功能模块确实能保持一套代码,只在编译配置和少量平台通道上做了区分。
当然也要说实话,Flutter在鸿蒙上并不是零成本适配。部分涉及系统能力的插件,比如地图SDK、推送服务、权限弹窗,需要针对鸿蒙单独接一遍原生层。所以项目里我把这些系统相关的功能单独抽了一层接口,方便在不同平台上做不同实现。这个设计在后面打包调试的时候省了不少事。
1.2 地理速记APP的核心需求拆解
地理知识速记,听上去是个很垂直的需求,但拆开来看其实包含了好几条功能链路。第一是知识卡片展示,用户要能快速看到一条地理知识点,比如“亚洲最长的河流是哪条”对应答案“长江”,卡片要做得清爽好看,重点信息一眼能捕捉到。第二是分类体系,地理知识不能揉成一锅粥,得按自然地理、世界地理、中国地理、气候植被这些维度拆开,用户才能按需选择。第三是记忆强化,我这边参考了间隔重复的机制,用户看过一张卡片后要自己给出“记住了”“模糊”“忘了”的评价,系统根据评价动态调整这张卡片再次出现的时间。
第四是测验模式,光看卡片容易产生虚假熟悉感,必须有一个闭卷输出的环节。低成本的实现方式是选择题和填空题混排,每次出题从题库里随机抽取,答完立即给解析。第五是学习进度可视化,用户需要知道今天学了几个知识点、复习了几个、正确率多少,进度条和统计图能给到最直观的反馈。第六是收藏和错题本,这两个是知识类App的基本盘,用户自己标记的重点题和答错的题可以随时回看。
把需求理完之后我发现,这个产品本质上不是一个大型业务系统,而是一个数据模型加四个核心页面:卡片页、分类页、测验页、统计页。复杂点主要在两个地方:一是记忆算法的逻辑规则,二是题库数据的结构设计。这两块设计好了,App就成功了一大半。
1.3 技术选型与方案对比
技术选型方面,我先把搜索范围锁定在Flutter框架内。状态管理从Provider、Riverpod、Bloc里选了Riverpod,理由是它比Provider更轻,依赖注入和自动取消机制对学习类小应用来说刚刚好,不需要引入Bloc那种偏重的架构。本地数据库在sqflite和Hive之间犹豫了一阵,最终选了sqflite,因为知识题库需要写SQL做多条件筛选,结构化查询比键值对存储更合适,而且后面如果要和远端题库同步,SQLite的迁移方案也更成熟。
题库数据直接塞进asset目录,用JSON格式维护,编译时打包进App。这样做的好处是离线可用,没网也能刷题,对地理速记这种工具型App来说是刚需。后续如果要做题库更新,再单独走一次接口拉取差量数据就行。
地图模块是这次开发里最让我纠结的部分,因为地理知识App天然需要在地图上定位到具体地方。但鸿蒙端的地图SDK目前适配成熟度有限,为了不阻塞主流程,我用了折中方案:先用静态地图资源加坐标点标注,把“位置感知”这个功能跑通,等地图第三方SDK的鸿蒙适配稳定后再平滑替换。项目里抽象了一个LocationMarker接口,静态实现和真实地图实现都能指向同一个接口,替换成本可控。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 开发环境准备与项目骨架搭建
2.1 Flutter与鸿蒙开发环境配置
环境配置这块是整个流程里第一个容易卡住的环节,我把自己踩通的步骤整理出来。本机系统是macOS,Flutter SDK先装了最新的稳定版,这一步正常执行官方文档就行,重点在后面。
鸿蒙相关的适配链需要在Flutter SDK之外单独准备一套。目前常见的实践是拉取鸿蒙适配分支的Flutter SDK,把它作为独立的工具链放到单独目录,再通过环境变量切换指向。我是把官方稳定版Flutter和鸿蒙适配版Flutter的路径分开写在环境变量里,日常开发用通用版,做鸿蒙构建时切到适配版。实测下来两个版本共存没问题,关键是fvm这类Flutter版本管理工具,可以更规范地做版本切换,建议团队协作时直接上fvm。
还需要安装华为官方提供的鸿蒙开发工具链,包括DevEco Studio和配套的SDK、命令行工具。Flutter编译鸿蒙应用时,底层要调用鸿蒙的构建框架,所以DevEco Studio里需要把SDK路径配置好。这个路径在Flutter适配版编译时要通过环境变量告诉构建链路,路径写错会在很后面的构建阶段才报错,排查起来不太直观。
具体的步骤顺序是:先安装DevEco Studio并打开任意一个新工程让SDK自动下载完,再用fvm拉取Flutter的鸿蒙适配分支,最后把项目里flutter工程的ohos目录和DevEco的SDK路径对接上。这里有个容易忽略的点:鸿蒙工程需要先开通“Sign In”和自动签名,否则真机调试时安装不上去。第一次跑通这个流程后,后面编译就顺了。
2.2 项目初始化和目录结构设计
初始化Flutter项目这一步很常规,执行项目创建命令后会自动生成android、ios、web这些平台目录。鸿蒙适配版SDK的工具链会在创建或后续命令中额外生成一个ohos目录,这个目录就是鸿蒙工程的实体,包含entry模块、配置文件、资源文件等,结构上和安卓的app目录对应。
项目骨架不能只依赖默认目录结构,我直接把业务代码按功能模块拆开。lib目录下分了几个一级子目录:models放数据模型,providers放Riverpod的状态和业务逻辑,pages放页面级Widget,widgets放复用组件,services放数据库、题库导入等底层服务,utils放工具类和常量。这样拆的好处是找文件特别快,哪一层出了问题直接钻进对应目录就能定位,不用在整个lib目录里翻来翻去。
还有一个很容易被忽略的东西是主题和国际化配置。地理知识App要做的是清爽、信息密度高的界面,我在主题文件里预置了卡片配色、字号阶梯和间距规范,所有页面统一从这个文件取样式代币,避免每个新人写页面时又自己定义一遍颜色和字号导致视觉混乱。
2.3 依赖管理与基础配置
依赖管理用的是pubspec.yaml,这个文件直接决定了项目的“口粮”。核心依赖我在前面选型时已经定了,这里补充几个实际开发中很实用的库。一个是flutter_animate,用来做卡片的切换动画和答题反馈动画,代码量不大但视觉效果提升非常明显。另一个是intl,日期格式化和统计页面里“连续学习天数”这类计算都靠它。
版本锁定的问题多人协作时会碰到。我建议pubspec.yaml中不要全部写死版本,但关键依赖务必锁定大版本,比如Riverpod统一用2.x,避免成员A升级后成员B还在用老API导致编译错误。再加上pubspec.lock文件必须提交到代码仓库,这样才能保证每个人的依赖树一致。
android、ios、ohos三个平台各自有配置文件。安卓的调试和发布需要在build.gradle里把应用ID配好,iOS要处理签名和权限声明,鸿蒙那边则要在Module配置文件里声明权限模块和API版本。这些配置看起来繁琐,但都是一次性的,前期配好后后面几乎不会再去动。
3. 地理知识卡片的数据模型与存储设计
3.1 数据模型设计
数据模型是整个知识速记App的地基,我花了不少心思把题库结构定义清楚。一张卡片的模型命名为GeoCard,核心字段包括卡片ID、所属分类ID、题目文本、答案文本、解析文本、难度等级、标签列表,以及一个用于素材扩展的图片路径字段。题目和答案分别存储,答错或者点击“查看解析”时才展示答案和解析,这样能强迫用户先做输出再对照。
分类模型用Category来承接,主键是分类ID,再加父级分类ID、分类名称、排序值。用自关联父子结构是为了方便以后扩展细节分类,比如“世界地理”下面可以挂“河流湖泊”“山脉高原”“国家首都”这些子类,UI上做两级展开不会遇到结构瓶颈。
还要有一个专门记录用户学习行为的模型StudyRecord,字段包括卡片ID、掌握状态、复习次数、上次学习时间、下次到期时间、难度自评分数。这个模型是间隔重复内存的关键,每次学习完成后都要更新它,算法逻辑正是基于这份记录来计算下一次卡片出现的时机。整个数据链路其实就是“题库卡片”和“学习记录”两张表在围绕时间轴做交互。
3.2 本地数据库选型与建表
数据库选了sqflite之后,建表结构就要贴合前面的模型设计。我建了四张表:category分类表、geo_card题库卡片表、study_record学习记录表、favorite收藏表。前两张存储静态内容和内置题库,后两张存用户的动态数据。
建表时有三件事值得注意。一是索引一定要建,geo_card表的category_id字段和study_record表的card_id、next_review_at字段都要加索引,否则数据量上来后联表查询会很慢,实际刷几百张卡片时体感差异就很明显。二是外键约束必须开,SQLite里默认外键关闭,需要在数据库打开的回调里执行一行PRAGMA语句才能启用。三是时间字段统一存时间戳秒数,方便做排序和区间筛选,不要混用字符串和数字格式。
表结构设计完之后,我写了一个数据库初始化的服务类,负责打开数据库、执行建表语句、处理版本迁移。迁移策略是每次升级版本号,在onUpgrade回调里根据旧版本号增量执行ALTER语句,这套机制对后续题库扩展和字段调整非常必要,千万别图省事不做迁移逻辑。
3.3 题库数据导入方案
题库数据我采用了两级导入策略。第一级是内置种子数据,App首次安装启动时,程序从asset目录读取一个JSON格式的题库文件,解析后批量写入geo_card表。第二级是增量题库,通过接口拉取后插入本地数据库,更新时以卡片ID做幂等判断,已存在的卡片跳过或覆盖。
JSON题库的格式维护起来很直观,我直接在Git仓库里单独建了一个数据目录,由运营或者自己整理知识点时以JSON条目加进去。每个数据条目包含分类路径、题目、答案、解析、难度这些字段。导入逻辑首先要按分类路径递归创建分类,再插入卡片,这样能保证分类表和卡片表的ID关联正确。
导入过程中有一个细节容易踩坑:asset资源在Flutter里读取时是异步的,所以导入操作不能放在UI启动流程的同步阶段硬等。我的做法是做一个初始化页面加数据库预热逻辑,进度条走完后才进入首页。预热过程本身很快,几千条数据只占一两秒,用户感知不明显。批量插入时用事务包裹,避免逐条提交带来的性能损耗和数据不一致风险。
4. 核心功能模块的实现细节
4.1 卡片学习页与间隔重复逻辑
间隔重复作为本项目的核心算法,机制上参考了经典的SM-2思路。用户每次学习一张卡片时,对自己记忆的牢固程度给出三个档位的评价:记住了、模糊、忘了。系统根据评价和当前复习次数,计算出下一轮复习的间隔时间:初次学习后间隔一天,模糊的话间隔缩短到半天,记住的话间隔拉长到三天,复习次数越多,间隔递增的幅度越大。
代码层面上我维护了一个SpacedRepetitionService,核心逻辑是输入卡片的学习记录和用户评价,输出新的复习间隔并更新记录表。这个服务被设计成纯函数式的,不直接依赖数据库,方便后面写单元测试验证算法逻辑。实际体验下来,这样设计的好处是调参特别方便,想调整间隔倍率只改一个配置常量,不需要在业务代码里东找西找。
卡片学习页的实现也有不少讲究。第一次进入学习页时,从当前分类下筛选所有“下次复习时间小于当前时间”的卡片,按到期时间排序生成一个待学队列。学习过程是一张卡片占满屏幕,前半部分显示题目,答案被遮住,用户点击“显示答案”后翻转卡片,露出答案和答案解析,同时弹出三个记忆评分按钮。用户选择之后卡片自动切换到队列里的下一张,切换动画用翻转过渡,比简单滑动切换更有仪式感,潜意识里会让用户更认真去做评价。
4.2 分类浏览与搜索
分类页采用了类似文件管理器的两级展示结构。第一级展示最上层分类,用卡片网格排布,每个分类显示名称和包含的卡片数量。点击进入第二级后,展示该分类下的子分类和直接关联的卡片列表。卡片列表里的每一行都简洁显示题目、难度标签和上次学习状态,用户点击可进入详情做快速预览。
搜索功能覆盖了题目、答案、分类名称三个维度。SQL的实现上用了LIKE模糊匹配,并把三个字段的匹配结果做拼接。这里要注意LIKE语法在SQLite里对中文是友好的,不需要额外做分词处理,对速记类App来说体验完全够用。搜索结果页沿用了卡片列表的组件,只是数据源换成搜索条件,这样代码复用很干净。
搜索还有一个体验细节值得提:输入框的防抖。我监听TextEditingController的变化,等待300毫秒没有新输入才触发数据库查询,避免每敲一个字符就做一次全表模糊搜索。配上加载状态和空状态提示,整个搜索过程很顺滑,不会出现卡顿和中途跳字。
4.3 测验模式与错题本
测验模块和卡片学习是互补关系。我把测验分成两种形式,快速随机测试和分类专项测试。快速测试从全部题库里随机抽取10或20道题,分类专项测试则限定在选定的分类下面。题目类型目前实现了选择题和判断题,这两种题型判题逻辑简单,对用户也更友好,输入成本低,更适合碎片化时间刷题。
题目生成逻辑是先从题库抽卡片,每张卡片根据内容生成一道选择题:题干是卡片题目,正确答案是卡片答案,错误选项从同分类的其它卡片答案里随机抽取。这个设计比人工维护选项要省力得多,同时因为错误选项都来自同一分类,干扰项的质量也在线,不会出现选项和题目明显不搭的情况。
答题完成后立即进入结果页,展示答对数量、答错数量、正确率和建议复习分类。答错的题自动写入错题本表,同时把对应卡片的study_record复习状态置为“忘了”,这样下次间隔重复会把这道题提前捞出来。错题本页面按时间倒序遍历错题记录,支持一键“重新测验”,把错过的题再考一遍,直到全部答对。这套流程跑下来,用户的学习闭环是完整的:学卡片、做测验、发现薄弱点、回到错题强化。
4.4 学习进度统计与提醒
统计页是整个App给用户反馈学习价值的面板。核心展示三个维度的数据,今日学习概览、七日趋势、分类掌握度。今日学习概览统计当天的新学卡片数、复习卡片数、答对率;七日趋势用柱状图展示每天的学习量和正确率变化;分类掌握度用横向进度条展示每个分类下所有卡片的平均持有分数,分数越高的分类进度条越长。
图表没有引第三方库,直接用手绘Widget实现的柱状图。原因是统计页只有这一个图,引一个图表库显得小题大做。手绘方案也很成熟:用Container的宽度比例映射数值,外层加一个带圆角的灰色背景条,数值对应的比例用主题色填充,最后在右边标注百分比数字。这套实现在视觉效果和性能上都非常稳定,也不会因为升级图表库导致渲染问题。
提醒功能用的是系统本地通知,每天固定时间推送一条学习提醒。Flutter侧通过本地通知插件注册每日定时任务,点击通知跳转到App的学习页。这里要处理权限申请逻辑,如果用户拒绝了通知权限,设置页里会显示引导开启的提示。需要注意鸿蒙端的通知渠道概念和安卓的不太一样,需要在鸿蒙的原生配置里单独声明渠道,否则通知可能弹不出来,这是一个比较容易忽略的跨平台差异点。
5. 跨平台适配与鸿蒙打包发布
5.1 平台差异处理的几个关键点
跨平台开发最怕的不是写代码,而是“在我机器上能跑,换台机器就崩”。差异主要出在系统API、存储路径、文件格式这些底层细节上。我的处理原则是:所有平台差异一律收敛到独立的PlatformAdapter接口里,业务代码只面向接口编程,不在页面里到处写if判断平台类型的代码。
举个实际的例子,数据库文件的持久化路径,安卓和鸿蒙的推荐路径并不完全一致。我用path_provider插件统一获取应用文档目录,再拼接数据库文件名,这样无论哪个平台拿到的都是合法的可写路径。再比如系统返回键的拦截逻辑,安卓原生是监听返回事件,鸿蒙也有自己的一套路由返回机制,Flutter层的PopScope组件可以统一处理,但底层的注册方式在不同平台有小差别。这类问题解决时先查插件是否已兼容,没兼容的话就走Platform Channel自己写原生侧。
另外一个容易出问题的点是图片资源的格式和解码。如果App里有大量PNG或WebP素材,要确认鸿蒙适配对这两种格式的解码支持完整。实测下来常规格式都没问题,遇到解码报错时优先考虑把资源压缩后重新生成,或者改用网络图片加载,这样能绕过平台解码器的差异。
5.2 鸿蒙端构建配置与真机调试
鸿蒙端的构建和调试逻辑与安卓有类似之处,但细节上有很多自己的规矩。项目里的ohos模块下有一个核心的构建配置文件,需要在这个文件里声明应用包名、版本信息、API版本、模块依赖。和安卓的gradle配置不同,鸿蒙用自家的构建命令行工具,DevEco Studio里也内置了图形化的构建和调试按钮。
第一次真机调试时,最容易卡在签名环节。鸿蒙真机要求应用必须签名才能安装,签名在DevEco的Project Structure里配置,需要先用华为账号登录,再自动生成调试证书。这里建议团队里每个成员都用自己的账号做一次签名初始化,否则会出现A签名的包装不到B的设备上的情况。我在项目中直接建了一份签名配置的备注文档,把步骤截图和踩坑点都写了进去,新成员入职后照着文档半小时就能跑通。
调试时还有一个很顺手的工具是DevEco的日志面板。Flutter端print输出的日志在鸿蒙的调试面板里能看到,但格式会带一些原生过滤前缀。更推荐用Flutter自带的debug日志查看器,两个窗口配合起来,定位问题效率高很多。真机调试时如果发现UI渲染卡顿,优先检查是不是系统动画和Flutter动画叠加产生了冲突,必要情况下在鸿蒙原生侧关掉系统过度动画设置。
5.3 打包发布中的注意事项
鸿蒙应用打包发布和安卓的流程差异比较大,不能直接套用gradle打包的经验。我用的是DevEco Studio的Release构建方案,先生成签名包,再通过构建菜单产出HAP格式的应用包。HAP类似于安卓的APK,是鸿蒙应用的安装包格式。构建完成后,产物会直接输出到专门的store目录下。
发布到应用市场之前,最好在本地把Release包完整测一遍。Release包和Debug包的行为可能不同,尤其是数据库文件路径和日志输出,Release包会对日志做裁剪,如果业务里有依赖日志输出调试的功能要提前处理好。我遇到过一次情况是Debug包能正常加载题库,Release包首次启动后题库是空的,排查后发现是资源文件在Release模式下没有正确打包进HAP,需要检查构建配置里的资源目录声明。
打包体积也要关注。Flutter本身的引擎体积就占了不小比例,如果题库资源再塞得比较大,很容易突破应用市场的体积限制。我用了一个简单有效的方案:题库JSON用gzip压缩后放入资源目录,首次启动时解压到缓存再导入数据库。实测压缩率在70%左右,对几百K的小题库效果不明显,但题库膨胀到几兆时差异就出来了。
6. 常见问题与排查技巧实录
6.1 运行期崩溃类问题
先说说一个非常常见的问题:数据库路径访问不到。这个问题在鸿蒙上表现尤其明显,因为鸿蒙对应用沙盒目录的管理和安卓并不完全一致。最初的代码里我直接在文档目录下拼接数据库路径,启动时偶发抛异常。后来改成先通过getDatabasesPath()明确获取数据库目录,再在此目录下创建文件,问题彻底消失。这背后的原因是插件对不同系统的默认目录做了不同的映射,直接拼接很容易踩到系统保留目录。
还有一类崩溃是页面销毁后还在更新状态导致的内存问题。速记App里有大量的异步操作,比如从数据库查卡片、加载网络图片。如果在页面关闭后才返回数据,此时更新State就会抛出异常。解决方案很简单:页面生命周期里对异步任务做可取消封装,或者在每次回调前检查当前页面的mounted状态。这个检查逻辑我写成了一个统一的工具函数,用在所有异步回调入口。
内存泄漏在长列表加载时也出现过。卡片列表如果一次性加载上千条,即使有ListView的懒加载机制,也会因为图片资源过多导致内存飙升。解决办法是卡片列表中只保留当前视口附近的Widget,滑动时做图片的懒加载和释放。配合Flutter的缓存机制,卡顿和崩溃一起解决了。
6.2 构建报错类问题
构建期报错最常见的是插件兼容性问题。鸿蒙适配链路还不像安卓那样所有插件都开箱即用,有些插件依赖了特定平台的原生代码,在鸿蒙上编译时会报找不到符号类错误。排查方法是先把插件列表逐个禁用,二分定位到有问题的那一个,再去仓库里看有没有鸿蒙适配的issue或PR。我在项目中遇到过插件和鸿蒙不兼容的情况,最终的处理是fork一份源码,把缺失的平台方法用自己的鸿蒙实现补上,通过git依赖直接指向fork分支。
如果你在编译鸿蒙工程时遇到“找不到工具链”的错误,大概率是环境变量里的HarmonyOS SDK路径没配对。这时候去DevEco的SDKManager里看一眼实际路径,再把这个路径在Flutter侧的配置文件对一遍。这类问题通常不会在第一时间暴露,等到构建的某一步才突然报错,没有一行日志提示是SDK路径的问题,排查起来很考验耐心。
还有一个坑是构建缓存。多次更改了平台配置文件后,如果发现改动不生效,十有八九是构建缓存没有清理。鸿蒙的构建系统有自己独立的缓存目录,可以手动删除后重新构建。我在处理这类问题时习惯把“清理缓存+删除build目录+重新构建”三步连做,绝大多数顽固的构建问题都能靠这招解决。
6.3 性能优化与体验建议
开发后期我做了一轮性能优化,重点放在首屏加载和页面切换流畅度上。首屏加载优化的核心思路是冷启动阶段的数据库预热尽量轻量,不要一次性导入全部题库,而是先导入高频分类,其余分类等用户访问到时再懒加载。这样既缩短了启动时间,也不影响后续使用体验。
页面切换卡顿的元凶经常是无脑重建Widget。我在列表页和详情页之间做了页面级缓存,详情页返回后列表页的状态依然保留,不再重新拉数据库。对于学习页的卡片翻转动画,使用RepaintBoundary把动画层和静态背景层隔离开,避免每次动画都触发整页重绘。实测帧率从偶发掉帧稳定到了60帧满帧。
还有一个体验细节:触摸反馈。学习页的评分按钮如果区域太小,用户很容易点错。我把三个按钮的高度统一设为56逻辑像素,同时加上点击时的水波纹效果和轻微缩放,交互手感会明显提升。这种细节不体现在功能列表里,但对用户留存的影响是真实的。
表格:常见问题排查速查
| 问题类别 | 典型现象 | 排查思路 | 解决方案 |
|---|---|---|---|
| 数据库崩溃 | 启动闪退,查询数据库时抛异常 | 检查数据库目录和文件是否可写 | 通过getDatabasesPath()获取目录后拼接路径 |
| 通知不弹 | 本地通知不显示 | 检查鸿蒙通知渠道是否注册 | 在原生配置文件里增加通知渠道声明 |
| 构建失败 | 找不到符号或工具链 | 检查插件鸿蒙兼容性和SDK路径 | fork插件补全原生实现,重新配置SDK路径 |
| 渲染卡顿 | 列表滑动掉帧 | 检查是否有重绘和大型Widget重建 | 使用RepaintBoundary和懒加载机制 |
| Release功能缺失 | 资源加载不到 | 检查构建配置中的资源目录声明 | 在HAP构建配置里增加资源路径 |
| 签名安装失败 | 包无法安装到真机 | 检查签名账号是否匹配设备 | 用同一账号重新生成签名 |
最后我再分享一个扩展思路。现在这套题库和记忆算法完全是本地的,后续可以加一个账号系统和服务端同步,把用户的学习记录、错题本在不同设备间打通。技术方案也已经想好了——在services层抽象一个远程同步接口,本地数据库做增量变更记录,同步时只传增量,这样服务端压力小,客户端体验也快。整个项目从最初的“只想做一个鸿蒙上的刷题工具”慢慢变成了一个可扩展的跨平台学习平台,这个过程中最大的收获其实不是代码,而是“如何把一件看似简单的事,在多个平台上做稳妥”的工程经验。希望这份流程复盘对你有用。
