news 2026/10/26 3:22:00

Flutter鸿蒙跨平台开发实战:门店查询App从架构到打包

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Flutter鸿蒙跨平台开发实战:门店查询App从架构到打包

先交代一下背景。我最近接了一个偏工具型的应用项目:全国手账实体店查询。用户打开 App 能看到附近的手账专卖店、文具集合店、文创杂货铺,支持按省市筛选、地图标记、门店详情和收藏。产品提的需求很明确——鸿蒙要跑,iOS 和 Android 也要跑,而且不能接受三套 UI 三套维护。当时团队里对技术栈讨论了两轮,最后定下来用 Flutter 作为跨平台方案,目标平台直接锁定 HarmonyOS NEXT 的 Flutter 适配分支。这篇文章就是把我从环境搭建、数据模型设计、Provider 状态管理到定位地图接入、Release 打包这一整套过程里踩过的坑和最终跑通的方案串起来,给打算做类似跨端工具型应用的朋友做个参考。

1. 为什么是 Flutter + 鸿蒙:"一套代码,多方落地"的取舍

1.1 纯血鸿蒙带来的选择题

先说结论:现在做鸿蒙应用,最大的决策点不是"用 Flutter 还是 ArkTS",而是"你的目标设备到底是哪一批"。

HarmonyOS NEXT 之后的版本不再兼容 APK,这意味着以前"Android APK 直接装进华为手机"的那条路已经断了。而市面上存量还有大量 HarmonyOS 4 及更早的设备,它们在系统层面保留兼容 Android 应用的能力。所以项目真正要面对的是两个世界:旧设备还能跑 APK,新设备只认 HAP 包。如果你不管三七二十一,只打一个 Android 包丢到华为应用市场,新版机型直接安装失败,用户根本进不了首页。

我们当时做过一次设备画像,发现主力用户里 NEXT 设备的比例已经不低,而且还在快速上涨,所以最终目标定为:新版本必须产出 HAP 包,同时保留一个 Android APK 产物给存量用户。在这种双产物需求下,跨平台框架几乎是必然选择——ArkTS 写一套、Android 再写一套,两套代码的维护成本对一个小工具型应用来说太奢侈了。

1.2 Flutter 和 ArkTS 到底怎么选

网上经常有人争论 ArrowTS 和 Flutter 谁更流行,其实这是个伪命题。ArkTS 的流行度只限定在鸿蒙这一个生态里,Flutter 的流行度是整个跨平台开发圈的。对具体项目来说,关起门来看四个维度就够:多端复用、插件生态、UI 一致性、厂商支持。

维度Flutter(OHOS 适配分支)ArkTS 原生
多端复用一套 Dart 代码覆盖 Android/iOS/鸿蒙只能覆盖鸿蒙
UI 一致性自绘引擎保证三端渲染一致鸿蒙原生控件,另两套要重写
插件生态插件需要找 ohos 适配版本,部分要自己包通道官方 SDK 覆盖,但只服务鸿蒙
学习成本团队已有 Dart 经验,上手快要重新学 ArkTS/声明式 UI
构建产物HAP + APK + IPA只有 HAP

我们最终选 Flutter,不是因为 Flutter 比 ArkTS 更“先进”,而是因为团队现有的代码资产、组件库、开发习惯都在 Flutter 上。这里有个心态要摆正:鸿蒙上的 Flutter 不是官方 Flutter 的“一键切换”,它走的是 OpenHarmony 社区的 flutter_flutter 分支,插件层经常要自己补适配。选择 Flutter 意味着你要接受一个现实——你会比纯 ArkTS 项目多花时间在编译环境和插件通道上。但省下来的,是 iOS 和 Android 两边的整段研发周期。

1.3 门店查询这类应用为什么不算最难啃

老实说,手账实体店查询在跨端应用里属于难度中等偏下的类型。核心功能就三块:HTTP 请求列表、地图展示、定位。没有音视频推拉流,没有蓝牙通信,没有大量传感器调用,这对 Flutter 来说是典型的舒适区。

它真正的难点只有两个:一是定位和地图这两个插件的鸿蒙适配是否可用,二是全国门店数据量上来之后,列表和地图怎么高效联动。把这两个问题提前想清楚,后面写代码就会顺很多。如果你做的项目涉及大量原生能力,比如人脸识别、NFC、复杂蓝牙外设,那我不建议用 Flutter 硬碰硬,插件适配成本会把你磨到怀疑人生。

2. 项目冷启动:数据模型与页面架构先想清楚

2.1 门店数据模型怎么设计才够用

这类查询应用的数据模型不复杂,但字段一定要在前期定全。很多团队做一半才想起来缺营业时间、缺门店标签,回头改接口再改客户端,纯粹是给自己找麻烦。

我用的门店模型长这样:

class StoreModel { final String id; final String name; final String province; final String city; final String district; final String address; final double latitude; final double longitude; final String phone; final String businessHours; // 如 "10:00-21:00" final List<String> tags; // 如 ["手账", "文具", "文创"] final String photoUrl; final double rating; bool isFavorite; StoreModel({ required this.id, required this.name, required this.province, required this.city, required this.district, required this.address, required this.latitude, required this.longitude, required this.phone, required this.businessHours, required this.tags, required this.photoUrl, required this.rating, this.isFavorite = false, }); factory StoreModel.fromJson(Map<String, dynamic> json) { return StoreModel( id: json['id'] as String, name: json['name'] as String, province: json['province'] as String, city: json['city'] as String, district: json['district'] as String, address: json['address'] as String, latitude: (json['latitude'] as num).toDouble(), longitude: (json['longitude'] as num).toDouble(), phone: json['phone'] ?? '', businessHours: json['businessHours'] ?? '', tags: List<String>.from(json['tags'] ?? []), photoUrl: json['photoUrl'] ?? '', rating: (json['rating'] as num?)?.toDouble() ?? 0, ); } }

这里有个容易忽略的点:distance 字段不要存数据库,不要信接口下发的缓存距离。用户位置一变,距离就得实时算。我会在展示层根据当前定位经纬度和门店经纬度做一次 Haversine 计算,结果只存在于内存。

2.2 接口协议和缓存策略

门店列表接口我设计成这样就够:

GET /stores?province=浙江&city=杭州&district=西湖区&keyword=手账&page=1&pageSize=20

响应统一封装成:

{ "code": 0, "msg": "ok", "data": { "list": [ ...门店对象... ], "total": 235 } }

为什么不把全国门店一次性拉下来?因为当门店数量到几千甚至上万时,一次性下发会带来两个问题:首屏慢、内存涨。按行政区划分页查询,每次只加载 20 条,是最稳的做法。

缓存方面,我只做两件事:

  • 门店列表的内存缓存:切换 Tab 再切回来时,先展示旧数据,再请求刷新。
  • 收藏门店的本地持久化:用shared_preferences存一个StoreId的字符串集合,每次 toggle 收藏后同步写入。

图片缓存单独交给cached_network_image,这个后面在性能优化部分再展开细说。

2.3 页面划分与路由

这个应用我设计了四个核心页面:

  1. 首页(门店列表):展示当前位置或当前筛选条件下的门店列表。
  2. 地图页:在地图上展示当前列表的门店标记。
  3. 搜索页:按关键词搜索门店名称和标签。
  4. 详情页:展示单家门店的完整信息,包含收藏按钮、拨号按钮、地图导航入口。

首页底部放两个 Tab,一个列表、一个地图,这两个页面共享同一个数据源。搜索页从首页右上角进入,详情页从列表项或地图标记点进入。

路由我直接用的 Flutter 自带命名路由,没有引入 go_router。原因很简单:这个项目没有深层链接需求,没有 Web 端复用需求,命名路由足够清晰。别为了“架构感”给 4 个页面引入一个复杂度明显超纲的路由框架,后面维护起来只有你自己哭。

3. Provider 状态管理落地:门店列表、定位与筛选的状态流

3.1 为什么选 Provider 而不是 Bloc / GetX

现在 Flutter 社区状态管理方案一大把,但手账查询这类中小型工具,我的选择标准就三条:上手快、可追踪、不黑魔法。Provider 在这三点上表现最均衡。

Bloc 的问题是样板代码太多。一个列表加载状态,Bloc 要写 Event、State、Bloc 三个类,门店查询这种页面反复刷新的场景,代码量会翻倍。GetX 确实写起来爽,但它的“隐式依赖”太多了,项目一大,你不知道某个 controller 是被谁、在哪里注入的,排查问题全靠猜。Provider 的思路本质还是 InheritedWidget + ChangeNotifier,没有隐藏魔法,出了问题顺着依赖树一层层找就行。

3.2 三个 Provider 的职责边界

这个项目我拆了三个 Provider,每个职责非常单一:

class LocationProvider extends ChangeNotifier { double? latitude; double? longitude; String? addressText; bool isLocating = false; LocationDeniedType? deniedType; // none/permission/disabled Future<void> locate() async { isLocating = true; notifyListeners(); try { final loc = await _getNativeLocation(); latitude = loc.latitude; longitude = loc.longitude; deniedType = null; } catch (e) { deniedType = LocationDeniedType.permission; } finally { isLocating = false; notifyListeners(); } } } class StoreListProvider extends ChangeNotifier { List<StoreModel> stores = []; String province = ''; String city = ''; String district = ''; String keyword = ''; int page = 1; bool loading = false; bool hasMore = true; String? selectedStoreId; Future<void> load({bool refresh = false}) async { ... } Future<void> loadMore() async { ... } void setRegion(String p, String c, String d) { ... } void selectStore(String? id) { selectedStoreId = id; notifyListeners(); } } class FavoriteProvider extends ChangeNotifier { final Set<String> _ids = {}; bool isFavorite(String id) => _ids.contains(id); Future<void> toggle(String id) async { ... 持久化 ... } }

清单式的问题排查技巧:如果在notifyListeners()之后 UI 没有更新,先看这个 Provider 是否真的挂在了依赖树上;如果在页面dispose之后还收到状态更新,检查你是否在initState里注册了非 Provider 的监听回调。Provider 本身已经帮你管理了生命周期,不要手动addListener,不然容易踩二次通知的坑。

3.3 组件通信的几种写法

这是很多 Flutter 新手都会问的问题:“子组件怎么通知父组件?”“兄弟组件怎么共享数据?”网上很多答案让你用回调、用事件总线,我建议你直接忘掉这些。在 Provider 体系下,组件通信的本质是:让多个组件依赖同一个状态对象,由状态对象统一通知重建。

具体有哪些写法,可以直接背下来:

// 写法一:依赖并监听,数据变了组件重建 final storeList = context.watch<StoreListProvider>().stores; // 写法二:只触发动作,不关心重建 context.read<LocationProvider>().locate(); // 写法三:只监听某个字段,避免整棵树重建 final isLoading = context.select<StoreListProvider, bool>((p) => p.loading);

这里有个很经典的反模式,看到一定要警惕:

// 错误示范 final provider = context.read<StoreListProvider>(); return Text(provider.stores.length.toString());

因为context.read不会监听变更,页面不会跟着数据刷新。要用context.watch或context.select。我团队里新同学至少踩过两次这个坑,每次都是“为什么不刷新?答案就在这里”。

应用初始化时,在 main.dart 里挂 MultiProvider:

void main() { runApp( MultiProvider( providers: [ ChangeNotifierProvider(create: (_) => LocationProvider()), ChangeNotifierProvider(create: (_) => StoreListProvider()), ChangeNotifierProvider(create: (_) => FavoriteProvider()), ], child: const StoreApp(), ), ); }

3.4 列表、地图、筛选三端怎么联动

我特意把"列表、地图、筛选"称为三端,因为它们是三个不同 UI 形态,却共享同一个数据状态。核心联动点就一个:StoreListProvider.selectedStoreId。

  • 用户点击列表某一项:调用storeListProvider.selectStore(id),地图页通过context.select监听这个 id,把相机移到对应门店,并高亮对应 marker。
  • 用户点击地图 marker:调用storeListProvider.selectStore(id),列表页监听到变化后,把对应项滚到可视区域内。
  • 用户切换省市:调用storeListProvider.setRegion(),列表、地图同时清空旧数据并刷新。

列表滚动到指定项,如果列表项高度固定,可以直接用itemExtent加计算偏移量,性能最好:

final index = stores.indexWhere((item) => item.id == selectedStoreId); if (index >= 0) { scrollController.animateTo( index * itemExtent, duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, ); }

如果卡片高度不固定,就用GlobalKey绑到具体 item 的 Widget 上,再调Scrollable.ensureVisible。我实测下来,手账门店列表的卡片高度基本固定,用itemExtent就够,还顺手解决了列表滑动的性能问题。

4. 定位与地图接入:全国门店查询的核心链路

4.1 定位:插件能直接用就先用,不能用就走通道

鸿蒙生态下定位插件分两种情况。

第一种,你找到的 Flutter 插件已经有 ohos 适配版本。比如社区里有geolocator的 OpenHarmony 维护分支,直接 pub 引用就能用。这种最省事,权限声明、定位请求、结果回调全封装好了。

第二种,插件只有 Android/iOS 版本,鸿蒙没适配。这时候别硬等,直接自己封装一个 MethodChannel。Flutter 侧这样写:

class NativeLocationService { static const _channel = MethodChannel('com.example.storelocator/location'); Future<Map<dynamic, dynamic>> getLocation() async { try { return await _channel.invokeMethod('getLocation'); } on PlatformException catch (e) { throw LocationException(e.message ?? '定位失败'); } } }

鸿蒙原生侧,用 DevEco Studio 在 ohos 工程里写一个 ArkTS 组件,调用系统 Location Kit,把latitude和longitude通过channel.invokeMethod回传。这个通道只有两个方法:getLocation和startLocationUpdate,协议简单到不需要写文档。

权限声明千万别忘,在module.json5的requestPermissions里加上ohos.permission.LOCATION,并写明使用场景。鸿蒙对定位权限的审核很严,如果申请文案写得含糊,审核可能打回。

4.2 地图:能用现成的,自己包通道也不难

地图是这类应用里最容易被低估的部分。鸿蒙地图 SDK 目前各家厂商都有适配,但 Flutter 插件层面的完成度参差不齐。我的落地策略分两层:

优先找支持 ohos 的 Flutter 地图插件,把AMap/ 华为 Map SDK 的鸿蒙版通过平台通道暴露给 Flutter。如果插件缺失,就直接在 ohos 工程里用 ArkTS 写一个地图容器,承载 SDK 的原生地图组件,Flutter 侧用一个 PlatformView 占位,所有 marker 数据、相机移动指令都走 MethodChannel 下发。

地图数据层我统一这样生成 marker:

Set<Marker> buildMarkers(List<StoreModel> stores) { return stores.map((store) { return Marker( markerId: MarkerId(store.id), position: LatLng(store.latitude, store.longitude), infoWindow: InfoWindow( title: store.name, snippet: store.address, ), onTap: () { context.read<StoreListProvider>().selectStore(store.id); Navigator.pushNamed(context, '/store', arguments: store.id); }, ); }).toSet(); }

标记点和列表项的点击行为要保证完全一致,不然用户从地图进详情和从列表进详情会感觉“不像同一个 App”。

4.3 全国尺度下的查询策略

手账实体店全国可能几千上万家,一旦筛选条件为空,绝不能把全量数据一次性拉下来。我做了两种模式:

  • 附近模式:获取定位后,请求/stores/nearby?lat=xx&lng=xx&radius=5000,按距离排序。
  • 浏览模式:用户没有授权定位,或者明确要按行政区划浏览时,走省市区分级筛选。

两种模式的切换要非常顺滑。比如用户正在“附近”列表,然后手动选了“浙江省–杭州市”,页面应立即切换到浏览模式,清空旧列表并请求第一页。切回“附近”时,如果定位权限还在,则重新回到附近模式。

分页加载用滚动到底部触发的方案,在ScrollController的listener里判断:

if (scrollController.position.pixels >= scrollController.position.maxScrollExtent - 300) { storeListProvider.loadMore(); }

注意loadMore要加防重入,用一个loadingMore布尔值挡住并发请求,否则快速滚动时同一页会被请求四五次。

4.4 权限、异常与空态设计

工具型应用最忌讳“功能全但报错裸奔”。我把异常状态统一成三种,每一种都有明确 UI:

  1. 定位权限被拒绝:地图页显示说明卡片,告诉用户为什么需要定位,附带“去开启”按钮,跳转到系统设置。
  2. 网络请求失败:列表页保留旧数据,顶部显示一条轻量提示条“网络开小差,点击重试”,而不是直接把整页清空。
  3. 筛选结果为空:显示自定义空态插图 + 文案“这个区域还没有收录门店,换个关键词试试”,而不是空白页。

这些在开发期都是“边缘场景”,但到了用户手里就是每天的常态。我见过太多跨端应用在弱网环境下体验稀烂,就是因为开发时只在顺畅 WiFi 里测。

5. Flutter 鸿蒙工程配置实战:从报错到跑通

5.1 环境:DevEco Studio 与 Flutter SDK 的组合

如果你用官方 Flutter 稳定版直接跑鸿蒙设备,大概率会卡在flutter doctor完全不识别 ohos 平台。正确做法是使用 OpenHarmony 社区维护的 flutter_flutter 分支,把它单独放到一个目录,而不是覆盖你原来的官方 Flutter SDK。

我的环境组合:

  • DevEco Studio 5.0+(含 HarmonyOS SDK)
  • flutter_flutter 分支(OpenHarmony SIG 维护)
  • VS Code 写 Dart 代码,DevEco Studio 用于构建和跑 ohos 工程

装好后先跑flutter doctor,确认能识别 ohos 工具链。创建项目时用flutter create --platforms ohos,android,ios .,这样会在工程根目录同时生成 android、ios、ohos 三个平台目录。如果你拿到手的项目是从 Android 那边复制过来的,也可以手动在工程根目录执行flutter create --platforms ohos .补生成 ohos 目录。

5.2 "apply flutter's main gradle plugin imperatively" 的根因与修法

这个报错我在社区里看到过无数遍,最近我自己也踩了一次。它的全称大致是you are applying flutter's main gradle plugin imperatively using the apply,根因是工程的build.gradle还在用老的命令式写法:

apply plugin: 'com.android.application' apply plugin: 'kotlin-android'

而新版 Flutter 模板要求改用 plugins DSL 声明式写法:

plugins { id 'com.android.application' id 'kotlin-android' }

这个错误通常在“老工程升级 Flutter 版本”或“从旧模板复制项目”时出现。修法其实很简单:找到根目录build.gradle和模块级build.gradle,把所有apply plugin:改成plugins { id '...' }的形式,然后清理重编:

flutter clean flutter pub get

鸿蒙侧还有类似的坑,ohos/build.gradle里的插件声明同样不要用老式 apply。每次我从示例工程复制配置,都会先检查这里,能省至少一小时查错时间。

5.3 Flutter AAR:别拿 Android 的思路套鸿蒙

有一个关键词经常把跨端开发新手带沟里:Flutter AAR。在 Android 生态里,flutter build aar能把 Flutter 模块打包成 AAR 供原生工程集成,这是“混合栈”的标准做法。

但你得清醒:AAR 是 Android 的产物,HarmonyOS NEXT 不认 AAR。如果你的目标机型是 HarmonyOS NEXT,Flutter 模块必须走 OpenHarmony 的 Flutter 引擎集成到 HAP 里,路径完全不一样。如果你在维护的是兼容 Android 的老鸿蒙机型,AAR 还能用,但这不是“为鸿蒙开发 Flutter”,只是“在华为手机上跑 Android 包”。两边的东西别混,产品问起来你要能讲清楚。

5.4 新建项目跑不起来的排查清单

“Flutter 新建项目后跑不起来”是我见过最多的求助帖,实际排查起来就五个点,按顺序查,命中率极高:

现象排查点
flutter doctor里设备不显示手机有没有开启开发者模式 + USB 调试;数据线是不是只充电
第一次构建卡在下载 Gradle检查 gradle-wrapper.properties,换成国内镜像仓库
构建时报 JDK 版本错误确认 DevEco 绑定的 JDK 和 Flutter Gradle 要求一致
装到手机上提示签名无效DevEco Studio 里配置自动签名,登录华为账号后自动生成证书
adb devices显示 unauthorized手机上点击“允许 USB 调试”,重新拔插数据线

非华为电脑连鸿蒙手机经常卡在 adb 设备识别,我的建议是:先确认开发者选项里的“USB 调试”开着,然后在设置里切到“传输文件”模式;如果驱动死活装不上,直接用无线调试。同局域网下手机开启无线调试,用adb connect 手机IP:端口就能连上,省掉一堆驱动折腾。

6. 渲染与性能优化:Impeller、图片缓存与包体瘦身

6.1 Impeller 开关别照抄别人

Impeller 是 Flutter 新的渲染引擎,目标是替代 Skia,主要解决掉帧和卡顿。社区里对它的评价两极分化得很严重,有人说“开了一路丝滑”,有人说“开了花屏闪烁”。

以我在鸿蒙设备上的实测来看,Impeller 在不同芯片平台的表现差异很大,跟 Android 一样需要实机验证。如果在某些华为机型上出现渲染异常、文字模糊、地图边界闪烁,可以在flutter run时临时加个参数对比:

flutter run --no-enable-impeller

如果确认是 Impeller 的问题,优先升级 flutter_flutter 分支版本而不是直接关闭,因为后续版本对鸿蒙设备的适配会越来越好。这个开关不要照抄别人的项目,一定要在你目标真机上测一轮。

6.2 门店图片的缓存与降采样

门店封面图是列表性能和流量的双重杀手。一张原图 2MB,用户在列表里快速滑动,一屏 8 张图,就是 16MB,流量耗不起,内存更扛不住。

我的标准做法是用cached_network_image,并且强制设置降采样宽度。以下这行减少的内存开销非常明显:

CachedNetworkImage( imageUrl: store.photoUrl, width: 120, height: 120, fit: BoxFit.cover, memCacheWidth: 240, // 按 2 倍分辨率降采样 placeholder: (context, url) => const ShimmerBox(), errorWidget: (context, url, error) => const FallbackStoreImage(), )

memCacheWidth等于显示宽度乘设备像素比,一般 2 倍就够。不要传原图宽度,否则大图会在内存里占一块看不到的空间。

另外建议在CachedNetworkImage的cacheManager里配一个统一的缓存过期策略,比如 7 天。门店封面图变更频率不高,过期时间设长一点能明显减少重复下载。

6.3 Release 构建的包体控制

Flutter 的包体在鸿蒙上一样需要刻意控制。我构建 HAP 时只保留 arm64 架构,不要打包 x86_64,能省下不少体积。如果你们还需要模拟器用,单独出一个 debug 包就行,Release 给真机用户只用 arm64。

还有两个土办法实测有效:一是把用不到的国际化语言包删掉,很多依赖库默认带了一堆 locale,只留zh和en;二是检查 pubspec 里有没有“想着以后可能用到”的依赖,这类依赖是包体膨胀的头号来源。每增加一个依赖,就在最后交付前跑一次flutter build hap --release,看体积变化,超过合理预期的一定要查。

7. 真机测试与发布前检查

7.1 让鸿蒙手机进入可调试状态

HarmonyOS 的开发者模式跟多数安卓类系统类似:设置里连续点击“关于本机”中的版本号 7 次,解锁开发者选项,再进开发者选项里打开“USB 调试”。很多人在这一步就卡住了,因为鸿蒙的菜单层级和安卓略有差异,找不到就是找不到。耐心找一下“系统和更新 - 开发人员选项”这个入口。

对于非华为电脑,我遇到的情况是 USB 驱动装不上,连上只显示充电。后来直接用无线调试解决了:手机和电脑连同一个局域网,手机开启无线调试后拿到 IP 和端口,adb connect IP:端口就通了。这个方法不挑电脑品牌,比折腾 USB 驱动高效得多。

真机调试时建议准备两台不同芯片的华为设备。Flutter 渲染引擎在麒麟和骁龙的鸿蒙设备上表现不完全一样,地图 WebView 的兼容性也有差异。只在一台机器上测过就提测,很容易被用户打回。

7.2 发布前的回归清单

我会在提测前过一遍这个表,每个功能都要在真机上实际点一遍:

测试项关注点
首次冷启动权限弹窗顺序是否合理,定位未授权时地图页是否正常
门店列表分页快速滚动时是否重复请求,弱网下 loadMore 是否卡死
筛选切换省市切换时旧数据是否清掉,地图 marker 是否同步
地图标记点击点击 marker 是否能联动列表滚动到对应项
收藏持久化杀掉进程重启后收藏是否还在
弱网/断网错误提示和重试机制是否可用
大字体模式门店名称 2 行时卡片是否溢出
后台恢复App 切后台 10 分钟后回来,地图是否白屏或定位丢失

7.3 我最后盯的几个点

项目上线前,我还会额外盯三个“不起眼但致命”的细节。

第一,定位请求的时序。首页一进来就并行发定位请求和列表请求,很可能定位还没回来,列表已经以“全国模式”加载了一遍,用户看到列表闪动两次。正确做法是先检查是否有缓存或上次定位,有就直接进附近模式,没有就先走浏览模式,定位回来了再提示“已为你切换到附近门店”。

第二,权限被拒之后的二次触发。用户第一次点了“拒绝”,第二次进地图页不能无动于衷,要给出去系统设置的入口。拒绝场景做得好,用户流失能挽回不少。

第三,flutter analyze的告警清零。每次 release 前我都会跑一遍,零警告是底线。跨端项目的坑大多出在“看起来能跑”的代码上,静态检查能拦下一批低级问题。

做这类查询工具,我的体会是:功能做完只是起点,真正决定口碑的是弱网、首次启动、权限被拒这三个场景不崩。把这三点守住,加上 Flutter 三端一套代码的效率,这个项目才算真正落地。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/12 4:18:24

毕业论文AI工具实测推荐:8个网站搞定文献综述与降重

每年3月到5月&#xff0c;我的私信里会被同一种问题塞满&#xff1a;“老师&#xff0c;毕业论文写不出来了&#xff0c;有没有靠谱的AI论文网站推荐&#xff1f;”说实话&#xff0c;我理解这种焦虑&#xff1a;开题报告赶完了&#xff0c;文献综述像一座山&#xff0c;查重红…

作者头像 李华
网站建设 2026/10/25 19:27:03

DRAM存储原理与刷新机制:从1T1C结构到内存暂存技术

2. 从存储层级看DRAM的位置&#xff1a;为什么它叫“内存” 聊DRAM之前&#xff0c;得先把存储层级这件事捋清楚。很多人在学习计算机体系结构时&#xff0c;第一次接触存储金字塔&#xff08;Storage Hierarchy&#xff09;往往会有个困惑&#xff1a;寄存器、Cache、内存、硬…

作者头像 李华
网站建设 2026/10/25 20:37:19

SpringBoot+Vue办公用品直售推荐系统:从架构到部署全解析

你如果正在愁毕设选题&#xff0c;或者Java课设不知道做什么方向&#xff0c;那SpringBootVue这套日常办公用品直售推荐系统管理平台&#xff0c;真的很适合拿来认真啃一遍。这类项目最大的特点就是“技术栈经典、业务链路完整、工作量适中、好答辩”&#xff0c;前有用户端的商…

作者头像 李华
网站建设 2026/10/12 14:06:23

Open-Shell完全指南:在Win11上找回经典开始菜单与高效工作流

Win11发布之后&#xff0c;身边好几个同事都在吐槽新开始菜单。图标挤在一起、磁贴没了、文件夹功能残缺&#xff0c;最要命的是连"关机"这种操作都比以前多了一步。有人当场拍桌子说"微软是不是觉得大家都用平板"&#xff0c;转头就装了Open-Shell。Open-…

作者头像 李华
网站建设 2026/10/14 4:45:47

HAProxy实战:负载均衡配置、健康检查与高可用架构指南

做后端服务的人&#xff0c;迟早会撞上 HAProxy 这个名字。无论是 Nginx、Kubernetes 还是各种网关方案&#xff0c;流量入口处总是能看到它的身影。我自己最开始接触 HAProxy 是因为线上服务扛不住高峰期的请求量&#xff0c;需要一套能在 TCP 和 HTTP 层做流量分发的方案&…

作者头像 李华
网站建设 2026/10/14 2:31:06

2026AI论文工具终极排行榜|8款主流工具实测排名

现在网上AI论文工具五花八门&#xff0c;很多同学盲目跟风乱用&#xff1a;通用大模型写论文AI超标、文献工具不支持中文、润色篡改数据、降重逻辑粗暴翻车。市面上没有一款通用AI&#xff0c;能完全适配国内高校毕设规则、查重标准、盲审要求、答辩流程。今天结合2026最新高校…

作者头像 李华