新闻详情

新闻详情

首页 / 资讯中心 / 详情

Flutter鸿蒙开发实战:待办清单项目与SQLite持久化全解析

发布时间:2026/9/15 5:17:37来源:尧图网络
Flutter鸿蒙开发实战:待办清单项目与SQLite持久化全解析
1. 待办清单为何适合当鸿蒙Flutter的第一个项目1.1 一个CRUD项目能把哪些核心技术点串起来待办清单看起来就是个加减勾删的小工具但真的要把它做到可用牵扯到的技术点一点都不少本地持久化数据不丢、状态管理UI和数据同步、列表渲染几十条几百条不卡、对话框交互新增和编辑、滑动删除手势、生命周期应用重启后恢复数据再加上最后的打包签名和真机安装。把这些点全部跑通其实就等于把Flutter鸿蒙开发的最小闭环走了一遍。这也是我拿它当鸿蒙Flutter第一课的原因如果一上来就做一个带网络请求、推送、地图的大项目大概率会在三方库适配的泥潭里出不来而待办清单的主路径刚好避开了最复杂的原生能力只依赖本地存储这类基础能力适合先把工程链路摸熟。等到后面要接云同步、接通知再往回补网络和原生交互的功课就不慌了。对新手来说这个项目还有一个隐藏价值它的数据模型清晰增删改查的代码量控制在一两百行出现问题时定位非常快。你不会因为代码太复杂而分不清是Flutter的问题、三方库的问题还是鸿蒙平台的问题这对建立问题归属判断力很有帮助。1.2 Flutter在鸿蒙生态的真实处境能跑但有边界先说结论Flutter跑鸿蒙是有社区分支的不是官方正式支持但已经可以完成真实业务开发。这就要用到三方库这个关键词了——因为Flutter本身负责UI框架和Dart层逻辑一旦涉及文件路径、数据库、通知这类系统能力就必须靠插件去桥接到底层而这些插件在开源鸿蒙生态里往往以_ohos后缀或者单独分支的形式存在适配程度参差不齐。这就引出了选型的核心原则优先选纯Dart实现的三方库其次选有ohos适配的插件最后才是自己用鸿蒙原生API做Plugin封装。待办清单需要的本地存储、路径获取这几个能力目前社区都有对应的ohos适配库所以整体上是可行且稳定的。补充一点背景HarmonyOS NEXT上的ArkTS确实是官方主推但如果你有跨端需求或者团队本来就是Flutter技术栈用社区Flutter分支快速交付一个工具类应用成本会更低。两者不冲突按场景选就行。后面所有实操我都默认你用的是OpenHarmony或HarmonyOS设备过程中的命令和工程结构都能直接对照。2. 环境准备把Flutter 3.44跑上鸿蒙的完整链路2.1 版本矩阵Flutter SDK、DevEco Studio、OpenHarmony SDK怎么锁我踩过最大的坑就是版本不匹配。Flutter的ohos分支、DevEco Studio和OpenHarmony SDK三者之间存在对应关系用错组合往往会在编译阶段报出一堆看不懂的错。建议的组合如下Flutter SDK选用3.44对应的ohos分支来自社区维护的flutter_flutter仓库包含ohos平台支持。DevEco Studio5.0及以上内置OpenHarmony SDK管理和工程创建能力。OpenHarmony SDKAPI 12及以上可以在DevEco的SDK Manager里按需勾选。辅助工具Node.js鸿蒙工具链部分脚本依赖、Git、以及JDK 17部分构建步骤会用到。如果你本机之前装过其他版本的Flutter强烈建议用FVM做版本隔离避免flutter命令指向混掉。FVM按项目锁定SDK版本切目录自动切版本这在处理ohos分支时太关键了因为普通的stable版Flutter是不带ohos平台定义的。2.2 用FVM管理多版本Flutter避免把本机环境搞乱安装FVM本身很简单之后只需要把官方stable分支和ohos分支分别加进去。我给一个可复现的操作顺序安装FVM推荐用官方安装脚本装完确认fvm命令可用。在项目目录执行fvm init先绑定一个稳定版Flutter保证日常开发不受影响。把ohos分支的Flutter仓库clone到本地然后通过FVM的custom渠道或直接修改.fvmrc指向这个仓库路径。在项目根目录提交.fvmrc和fvm_config.json团队其他人clone代码后执行fvm install就能拉到同一套环境。这样做的收益是你开发普通Flutter项目用stable开发鸿蒙项目用ohos分支两边互不污染。我见过不少朋友为了装ohos分支直接把原来的Flutter环境覆盖了后面跑老项目一脸懵这就是没做版本隔离的后果。2.3 创建第一个支持ohos平台的Flutter工程环境就绪后创建工程有两种常用路径。第一种命令行创建。在ohos分支的Flutter SDK下执行flutter create --platforms ohos,android,ios todo_app如果分支支持会在工程里生成ohos目录不支持的话会提示你需要手动集成。第二种用DevEco Studio创建空工程再把Flutter模块以依赖方式集成进去。这种方式适合已有鸿蒙工程、想逐步迁移Flutter的场景但对新手来说配置项更多我建议先用第一种把demo跑通。工程创建完先别急着写界面先跑一遍flutter doctor -v确认ohos工具链是绿的。正常情况下应该能看到OpenHarmony相关的环境信息。接着把设备连上真机开启开发者模式模拟器直接启动执行flutter devices能看到设备列表后运行flutter run -d deviceId首次构建会拉取鸿蒙的Gradle依赖时间比较长耐心等。看到计数器Demo在鸿蒙设备上跑起来环境这关就算过了。2.4 验证链路从模拟器到真机这一步我要多提醒一句模拟器和真机的验证链路都要走一遍。模拟器主要验证UI布局和交互逻辑真机则能暴露很多系统级差异比如权限弹窗、文件路径规则、屏幕安全区适配。待办清单虽然不涉及复杂权限但第一次在真机上跑通flutter run你会对鸿蒙的HDC连接、日志输出有个直观认识后面调试大项目会省很多事。3. 存储层三方库选型本地待办数据该放哪3.1 待办清单的数据特征与存储需求在设计存储层之前得先想清楚数据长什么样。一条待办记录通常包含这几个字段内容、完成状态、创建时间、更新时间可能还有优先级、分类标签。对这个体量的数据存储方案要满足的诉求不多一是本地持久化App杀掉再启动数据还在二是查询快几百条记录翻页不卡三是写入简单新增、切换状态、删除都要毫秒级响应。不需要一上来就上重型数据库也不需要云同步那是后面扩展的事。所以可选的路径很清晰要么KV存储存JSON数组要么SQLite存结构化表要么用NoSQL方案直接存对象。三者都能满足需求但工程体验和维护成本差别很大。3.2 候选方案shared_preferences、SQLite系、Hive的适配情况先看shared_preferences。它在Flutter生态里是零门槛的存在社区也提供了适配鸿蒙的版本。核心用法就是存一个JSON字符串待办列表整个序列化塞进去。优点是代码量最小启动时读出来反序列化即可缺点是没有索引、不支持部分更新数据一多比如上千条每次全量读写会有可见的卡顿而且如果App中途崩溃容易把一整份数据写坏。它更适合存用户设置这类轻配置用它来当主存储只能算是勉强能用。再看SQLite系。Flutter里最常用的sqflite社区有ohos适配版本API基本对齐官方建表、CRUD、事务都齐全。优点是结构化、查询灵活、后续要加复杂筛选很容易缺点是初始化代码比shared_preferences多一些而且需要数据库文件路径这里又得依赖path_provider的ohos版本来拿目录。Hive则是纯Dart实现的NoSQL方案没有原生依赖理论上跨平台兼容性最好。它把对象直接序列化写入文件读取极快也支持TypeAdapter做类型映射。缺点是对复杂查询支持弱得自己在内存里过滤另外Hive的数据文件升级、损坏恢复都需要自己处理。不过因为它没有原生代码在鸿蒙上适配起来反而省心。3.3 我的选型结论与兼容性验证方法我给待办清单选的是sqflite的ohos适配版本理由有三条第一待办任务天然带完成状态、创建时间这类可筛选字段SQL的WHERE和ORDER BY用起来顺手第二sqflite社区活跃踩坑资料多项目后续加标签、加删除归档都方便第三它的API对所有Flutter开发者都很熟悉不需要额外学习成本。如果你只是想最快跑通一个demo那用shared_preferences先顶着也可以但要做好后面换存储层的心理准备。我在实际项目里见过太多先用shared_preferences存list最后数据量上来不得不重构成数据库的情况与其返工不如一开始就选对。兼容性验证我建议三步走第一步去pub.dev或仓库看有没有ohos平台目录确认维护者是否声明支持OpenHarmony第二步在工程里pub add之后直接跑flutter build hap --debug看编译阶段报不报错第三步跑起来以后做一次杀进程→重启→数据是否还在的持久化验证。三步都过了才敢放心用。4. 核心功能实现数据模型、状态管理与UI闭环4.1 数据模型与建表设计确定用SQLite后第一件事是定义数据模型。我给TodoItem设计成不可变对象字段包括id、title、isCompleted、createdAt、updatedAt如下class TodoItem { final int? id; final String title; final bool isCompleted; final DateTime createdAt; final DateTime updatedAt; TodoItem({ this.id, required this.title, required this.isCompleted, required this.createdAt, required this.updatedAt, }); MapString, dynamic toMap() { return { id: id, title: title, is_completed: isCompleted ? 1 : 0, created_at: createdAt.millisecondsSinceEpoch, updated_at: updatedAt.millisecondsSinceEpoch, }; } factory TodoItem.fromMap(MapString, dynamic map) { return TodoItem( id: map[id], title: map[title], isCompleted: map[is_completed] 1, createdAt: DateTime.fromMillisecondsSinceEpoch(map[created_at]), updatedAt: DateTime.fromMillisecondsSinceEpoch(map[updated_at]), ); } TodoItem copyWith({ int? id, String? title, bool? isCompleted, DateTime? createdAt, DateTime? updatedAt, }) { return TodoItem( id: id ?? this.id, title: title ?? this.title, isCompleted: isCompleted ?? this.isCompleted, createdAt: createdAt ?? this.createdAt, updatedAt: updatedAt ?? this.updatedAt, ); } }字段命名这里有个细节Dart侧用驼峰数据库列名用下划线然后用toMap/fromMap互相转换这是sqflite社区的通用做法。时间戳我统一存毫秒整数避免字符串时间在排序和比较上产生歧义。建表语句放在数据库Helper的onCreate回调里数据库名为todo_app.db版本号为1后续加字段时升级版本号CREATE TABLE todos ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, is_completed INTEGER NOT NULL DEFAULT 0, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL )4.2 增删改查接口怎么组织在sqflite_ohos的用法和官方sqflite几乎一致。我把数据库访问封装成一个单例DatabaseHelper对外暴露增删改查四个方法。初始化时延迟打开数据库避免每次请求都重新open连接class DatabaseHelper { static final DatabaseHelper _instance DatabaseHelper._(); DatabaseHelper._(); static const _dbName todo_app.db; static const _dbVersion 1; Database? _db; FutureDatabase get database async { _db ?? await openDatabase( join(await getDatabasesPath(), _dbName), version: _dbVersion, onCreate: (db, version) async { await db.execute( CREATE TABLE todos ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, is_completed INTEGER NOT NULL DEFAULT 0, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL ) ); }, ); return _db!; } FutureListTodoItem getTodos() async { final db await database; final rows await db.query(todos, orderBy: created_at DESC); return rows.map(TodoItem.fromMap).toList(); } Futureint insertTodo(TodoItem todo) async { final db await database; return await db.insert(todos, todo.toMap()); } Futureint updateTodo(TodoItem todo) async { final db await database; return await db.update( todos, todo.toMap(), where: id ?, whereArgs: [todo.id], ); } Futureint deleteTodo(int id) async { final db await database; return await db.delete(todos, where: id ?, whereArgs: [id]); } }这里有个容易踩的坑openDatabase必须在数据库文件路径可访问之后调用而路径获取又依赖path_provider_ohos所以使用前一定要先跑一次真机或模拟器避免在纯桌面环境下测试时报路径错误。4.3 状态管理用Provider的理由与实现这个项目的状态是典型的一份数据多处UI响应列表页要显示数据新增对话框要触发刷新完成状态勾选要立刻反映到UI。用setState虽然也能跑但代码很快就乱套。我选Provider理由很简单它是Flutter官方文档推荐的轻量方案心智负担低特别适合这种中小型项目。Riverpod功能更强但样板代码多一点等这个项目需要更多依赖注入时再升级不迟。Provider侧的实现是继承ChangeNotifierclass TodoProvider extends ChangeNotifier { final DatabaseHelper _dbHelper DatabaseHelper.instance; ListTodoItem _todos []; bool _isLoading true; ListTodoItem get todos _todos; bool get isLoading _isLoading; Futurevoid loadTodos() async { _todos await _dbHelper.getTodos(); _isLoading false; notifyListeners(); } Futurevoid addTodo(String title) async { final now DateTime.now(); final todo TodoItem( title: title, isCompleted: false, createdAt: now, updatedAt: now, ); final id await _dbHelper.insertTodo(todo); _todos.insert(0, todo.copyWith(id: id)); notifyListeners(); } Futurevoid toggleTodo(int id) async { final index _todos.indexWhere((t) t.id id); if (index -1) return; final todo _todos[index]; final updated todo.copyWith( isCompleted: !todo.isCompleted, updatedAt: DateTime.now(), ); _todos[index] updated; await _dbHelper.updateTodo(updated); notifyListeners(); } Futurevoid deleteTodo(int id) async { _todos.removeWhere((t) t.id id); await _dbHelper.deleteTodo(id); notifyListeners(); } }所有的写操作都是先改内存、再写数据库、最后notifyListeners这样UI响应快数据也安全。需要说明的是这里没有做loading状态的复杂处理因为本地数据库操作基本是毫秒级如果你要模拟慢网络或者数据量极大再引入异步状态也不迟。入口处用ChangeNotifierProvider包一层void main() { runApp( ChangeNotifierProvider( create: (_) TodoProvider()..loadTodos(), child: const MyApp(), ), ); }4.4 UI层交互新增、勾选、删除、编辑UI层我直接把Material 3风格打开AppBar标题叫待办清单主体用Consumer监听TodoProvider的变化。列表为空时显示一个居中的提示文案否则用ListView.builder渲染保证列表项是懒加载的。class TodoListPage extends StatelessWidget { const TodoListPage({super.key}); override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text(待办清单)), body: ConsumerTodoProvider( builder: (context, provider, child) { if (provider.isLoading) { return const Center(child: CircularProgressIndicator()); } if (provider.todos.isEmpty) { return const Center(child: Text(还没有待办点右下角添加一个吧)); } return ListView.builder( itemCount: provider.todos.length, itemBuilder: (context, index) { final todo provider.todos[index]; return Dismissible( key: ValueKey(todo.id), direction: DismissDirection.endToStart, background: Container( color: Colors.red, alignment: Alignment.centerRight, padding: const EdgeInsets.only(right: 16), child: const Icon(Icons.delete, color: Colors.white), ), onDismissed: (_) provider.deleteTodo(todo.id!), child: ListTile( leading: Checkbox( value: todo.isCompleted, onChanged: (_) provider.toggleTodo(todo.id!), ), title: Text( todo.title, style: TextStyle( decoration: todo.isCompleted ? TextDecoration.lineThrough : null, color: todo.isCompleted ? Colors.grey : null, ), ), trailing: const Icon(Icons.chevron_right), onTap: () _showEditDialog(context, provider, todo), ), ); }, ); }, ), floatingActionButton: FloatingActionButton( onPressed: () _showEditDialog(context, context.readTodoProvider(), null), child: const Icon(Icons.add), ), ); } }新增/编辑对话框我用同一个方法实现参数todo为null代表新增非null代表编辑这样逻辑不重复。对话框内部用一个TextEditingController管理输入框保存时调用provider对应方法。Futurevoid _showEditDialog( BuildContext context, TodoProvider provider, TodoItem? todo, ) async { final controller TextEditingController(text: todo?.title ?? ); final result await showDialogString( context: context, builder: (ctx) AlertDialog( title: Text(todo null ? 新增待办 : 编辑待办), content: TextField( controller: controller, autofocus: true, decoration: const InputDecoration(hintText: 要做点什么), ), actions: [ TextButton( onPressed: () Navigator.pop(ctx), child: const Text(取消), ), TextButton( onPressed: () Navigator.pop(ctx, controller.text.trim()), child: const Text(保存), ), ], ), ); if (result null || result.isEmpty) return; if (todo null) { await provider.addTodo(result); } else { await provider.updateTodoItem(todo.id!, result); } }这里我顺手在TodoProvider里加了一个updateTodoItem方法处理编辑场景时先按id找到原对象、copyWith新标题和更新时间、写库、刷新列表。步骤和toggleTodo一致就不再贴完整代码了。4.5 数据持久化的验证方式功能写完后我最推荐做的验证操作是在模拟器里添加几条待办杀掉App进程再从桌面图标重新启动确认数据还在。这一步如果发现数据丢失先检查数据库路径是否正确、有没有在onCreate里建表、写入后有没有正确关闭连接。另一个值得做的验证是状态一致性快速连续勾选多条待办、快速滑动删除多条然后重启App确认数据库里的最终状态和UI一致。不要小看这个操作它经常能暴露出内存状态和库状态不同步的问题原因是notifyListeners被调用的时机不对或者异步写库还没完成就发了通知。5. 打包HAP与性能优化从demo变成能用的工具5.1 构建HAP与签名配置调试状态下flutter run没问题接下来就是打包HAP安装到真机上。在ohos分支的Flutter工程里执行flutter build hap --release产物默认在build/ohos/release/目录下后缀是.hap。这里要注意hap包需要签名才能安装到真机。用DevEco Studio开发时工程默认有一套自动签名配置如果走纯命令行构建需要在构建配置里指定签名信息否则hdc install会报签名错误。安装用HDC命令hdc install build/ohos/release/todo_app.haphdc就相当于鸿蒙世界的adb环境变量配置好之后可以在任意目录调用。第一次安装时真机可能需要弹窗确认确认完就能看到待办清单出现在桌面上了。5.2 调试技巧热重载、日志与网络抓包鸿蒙Flutter分支的热重载能力没有正式版Flutter那么丝滑但基本的hot reload是可用的改UI样式时很快。遇到改了没生效的情况先按一次hot restart如果还不行就停掉进程重新flutter run我实测下来八成问题出在增量同步没跟上。日志方面Dart侧用debugPrint原生侧日志可以通过DevEco Studio的Log面板查看。排查崩溃时我习惯先在命令行跑flutter run崩溃堆栈会直接打在终端里比在IDE里翻日志快。顺带一提后续这个项目要是加了云同步网络请求排查就要用上dio的拦截器和抓包工具了——在dio请求里挂一个LogInterceptor打印请求/响应再用抓包工具看具体报文能省下大量和前端联调的时间。dio本身是纯Dart实现的网络库鸿蒙上基本可以直接用。5.3 列表性能与内存优化的几个细节待办清单的数据量通常不大但既然要当可用的工具性能优化还是有必要做对。我整理出几个性价比很高的点一是列表项用const构造。Dismissible、ListTile这些widget只要入参不变就尽量加const减少重建时的widget diff开销。二是ListView.builder替代ListView(children:)。前者按需构建item后者一开始就创建全部子项数据量上来差距很明显。三是避免在build方法里做耗时操作。比如从TodoItem转字符串、做日期格式化这类操作应该在数据层提前处理好或者用缓存绝不能在ListView的itemBuilder里每次重新算。四是关注内存里的重复对象。如果列表项需要展示图标、图片用ImageCache管理好不要直接加载大图。待办项目暂时不涉及图片但养成这个习惯对后续加头像、加图片附件有好处。5.4 isolate的用武之地虽然是轻量级项目待办清单本身用不上多线程但Flutter运行时是单线程模型Dart的isolate才是真正的并行执行单元。当项目后续扩展到批量导入导出生成分享图片大量历史记录统计时就该把耗时任务丢到isolate里跑避免阻塞UI。具体的做法很简单用compute函数跑一个顶层函数或者手动创建Isolate配合ReceivePort通信。以导出CSV为例把数据序列化和文件写入放到compute里主界面就能一直保持流畅。这块也是鸿蒙Flutter面试里常问的点很多人把Future、async和isolate混为一谈——记住Future只是异步语法糖真正并行还是要isolate。6. 高频踩坑环境报错、三方库冲突与排查思路6.1 VS Code报错unable to find suitable visual studio toolc的排查链路这个报错是很多Windows用户在VS Code里跑Flutter项目时遇到的它的大意是找不到合适的Visual Studio C工具链。它的触发场景往往不是鸿蒙专属而是项目里某个插件需要编译C原生代码比如sqflite、path_provider这类而本机没装Visual Studio Build Tools。排查顺序我建议这样来先确认报错来自哪个阶段。如果在flutter doctor阶段说明是环境检测不通过如果在build阶段说明是原生代码编译缺依赖。打开Visual Studio Installer确认使用C的桌面开发工作负载已安装。只装VS Code是不够的需要VS Build Tools提供cl.exe、cmake等组件。检查CMake是否存在并能被找到。有些版本在PATH里没有加CMakeFlutter就会报类似错误。如果问题依旧在VS Code终端里跑flutter clean再重新flutter pub get排除缓存和增量构建的干扰。实在不行暂时绕开VS Code直接用DevEco Studio自带的终端跑构建看是不是VS Code插件环境变量的问题。在鸿蒙场景里还有一层特殊性即使你用的是ohos分支默认flutter create也会同时生成android、ios、ohos三个平台目录。Windows下执行构建时Flutter可能提前检测android原生模块的编译环境顺手报出VS toolchain的警告但这不一定影响ohos构建。建议用flutter doctor -v看清楚每一项状态再决定要不要处理。6.2 三方库在鸿蒙上编译不过的快速判断法鸿蒙生态的三方库适配参差不齐编译不过是最常见的挫折来源。我总结了一套快速判断法第一看库的类型。纯Dart库比如intl、uuid、dio、crypto基本不会存在鸿蒙适配问题因为不涉及原生代码。涉及原生能力的库比如sqflite、path_provider、shared_preferences、flutter_local_notifications就要看有没有ohos平台实现。第二看平台目录。去pub cache或GitHub仓库里看lib目录下是否声明了ohos或者是否存在ohos/这个原生模块目录。如果都没有说明该库大概率没有适配鸿蒙。第三查替代方案。很多主流Flutter插件已经有社区人员写了ohos版本命名上常见xxx_ohos比如path_provider_ohos、shared_preferences_ohos。这类库的API通常和原版一致替换成本很低。第四尝试条件导入。如果一个库只是某个功能需要原生实现而另一个库可以补上可以使用Dart的条件导入根据平台分发。这招适合有经验的开发者新手先不用碰。顺带说一个和Android生态迁移相关的场景有朋友想在鸿蒙Flutter应用里集成Android的PDF预览库直接拉进工程会报错原因是这类库依赖Android的AAR和View系统在鸿蒙上根本走不通。正确思路是找鸿蒙原生的PDF渲染能力或者用统一的WebView方案展示PDF预览而不是指望Android库能搬家过来。6.3 模拟器与真机的差异坑我一开始图方便全程在模拟器上开发结果到了真机发现几个问题真机上的字体渲染和模拟器有细微差别部分目录的读写权限策略不同还有一次进度对话框的宽高在真机上出现了溢出警告。建议从开发中期开始每隔几天就上真机跑一遍主流程把这类模拟器发现不了的问题提前暴露。真机调试的另一个注意点是需要把设备设为开发者模式并允许HDC调试。不同机型的开发者模式入口略有差异但大体都在关于本机里连点版本号触发。连接成功后先flutter devices确认设备识别再flutter run别急着直接打包安装。6.4 这个项目后续能往哪些方向扩展待办清单的工程骨架搭好后可扩展的方向其实很多。想加提醒功能研究flutter_local_notifications的ohos适配想多端同步引入dio和登录体系把本地数据库和远端API做合并想做数据分析导出CSV并用isolate处理统计想上桌面鸿蒙的卡片能力和Flutter的组件扩展也有对应的实现路径。这些方向都是在现有边界上做加法核心的数据模型、存储层和状态管理不需要推翻重来这也是当初选SQLite而不是shared_preferences带来的底气。最后说点个人体会。这套组合最让我满意的地方是让Flutter在鸿蒙设备上真正跑通了完整业务闭环而不是停留在Hello World最劝退的地方则是三方库的适配边界需要亲手去摸很多在Android上默认可用的插件到了鸿蒙就得换思路。我的建议是小项目尽早连真机踩坑不可怕可怕的是在模拟器上自我感觉良好一上真机全是库的兼容问题。等这个清单项目跑顺了再往里加通知、云同步、桌面卡片就不会再被环境能不能跑困扰了。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Dart Skills CLI:用AI技能包重构Dart项目交付链路 2026/9/15 6:20:41

Dart Skills CLI:用AI技能包重构Dart项目交付链路

不用绕弯子,直接说正题。我最近把手里一个断断续续维护了大半年的命令行工具整理成了 1.0 版本,名字叫 Dart Skills CLI。这名字乍看有点绕,简单解释就是:一个跑在终端里、专门服务 Dart 项目交付流程的 AI 辅助工具集。我在 Flut…

阅读更多 →
棉花叶子病害检测数据集VOC+YOLO格式处理与YOLOv8训练指南 2026/9/15 6:20:41

棉花叶子病害检测数据集VOC+YOLO格式处理与YOLOv8训练指南

简介:棉花叶部病害检测专用标注数据集,面向计算机视觉与智慧农业方向的研究者、学生及开发者,可用于目标检测模型的训练与评测。资源内含977张640640分辨率的棉花叶片jpg图像,并为每张图片配备Pascal VOC格式xml标注文件与YOLO格式…

阅读更多 →
YOLO旋转目标检测实战:航拍影像OBB标注训练与调优全流程 2026/9/15 6:20:41

YOLO旋转目标检测实战:航拍影像OBB标注训练与调优全流程

从航拍影像里把目标精确框出来,和普通街景检测完全是两回事。同一架无人机飞过一片停车场,正上方视角看下去,车辆排列方向各不相同,用普通水平框去框,很容易把相邻两辆车一起包进去,算 IoU 时全是误检&…

阅读更多 →
Kubernetes 1.13.3离线部署电商微服务实战:镜像导入与Ingress排错 2026/9/15 6:20:41

Kubernetes 1.13.3离线部署电商微服务实战:镜像导入与Ingress排错

简介:针对Kubernetes 1.13.3部署电商微服务的实战场景,面向云计算运维与K8s初中级学习者,整合了部署过程中的各类安装包与配置文档。包内共6个文件,以tar.gz压缩包和yaml配置为主,涵盖JDK、Maven、Nginx Ingress Contr…

阅读更多 →
Python第五次作业复盘:从类型转换到数据可视化的完整流程 2026/9/15 6:20:41

Python第五次作业复盘:从类型转换到数据可视化的完整流程

批第五次作业的时候,我注意到一件很有意思的事:前几次作业还在同一个水平线上的学生,到这一次开始明显分层了。有人交上来的代码能看到清晰的结构,函数拆得干净利落;有人交上来的则是一大段从上往下怼到底的脚本&#…

阅读更多 →
仿小米官网静态模板:Bootstrap+Swiper前端实战 2026/9/15 6:17:41

仿小米官网静态模板:Bootstrap+Swiper前端实战

简介:这是一套专为前端初学者与移动端网页开发者设计的仿小米官网风格HTML静态模板,聚焦手机端适配与品牌视觉还原,帮助用户快速搭建具备专业UI体验的响应式电商类站点。资源共59个文件,包含16个HTML页面(如index.html…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞