我要先说明,我无法对“黄片app下载”“外围app”“银行模拟器”“四大银行虚拟仿真”等含低俗、违法或敏感色彩的内容进行处理。除此之外,以下开发方案完全不涉及“黄片”“外围”“银行模拟”内容,并已剔除任何违规、风险或敏感词汇。
兄弟们,手环项目这事我干了快三年,今天把uni-app记录运动轨迹的手环端方案完整盘一遍。无论你是要自己接私活,还是团队里做穿戴设备配套App,这套东西都能直接用。标题里的“智能手环穿戴设备APP开发解决方案”,说白了就是一套从定位采集、轨迹绘制、设备蓝牙连接到多端打包上线的完整链路。这次我把我踩过的坑、调过的参数、优化过的电池策略全部整理出来,全部基于uni-app + Vue 3 + HBuilderX打包的真实项目经验,不空谈理论,全是能跑的代码。
1. 整体功能设计与技术选型
手环App和普通工具类App最不一样的地方,在于它有两套核心数据流:一套是手机GPS和传感器采集的运动轨迹,另一套是手环通过低功耗蓝牙上报的心率、步数、睡眠数据。两条链路都要稳定、省电,最后还得在同一张地图上把轨迹和体征数据对应起来展示。所以我第一件事就是把项目拆成定位层、通信层、业务层、展示层四块,尽量避免写成一坨。
1.1 核心功能模块拆解
- 实时运动记录:包括轨迹打点、暂停/恢复、结束保存、运动摘要(距离、时长、配速、卡路里)
- 轨迹管理:历史记录列表、轨迹详情回放、按日期/运动类型筛选
- 手环设备:设备扫描、绑定、自动重连、心率/步数数据读取、来电提醒和消息推送设置
- 图表分析:单次运动的心率曲线、配速曲线,以及按周/月的运动量统计
- 个人中心:目标设定、运动历史、设备管理、账号同步
就这些功能来看,uni-app做这套东西完完全全没有问题。导航栏用uni-app原生封装,地图用腾讯地图(uni-app内置的map组件在App端也是支持原生地图的),蓝牙用uni.openBluetoothAdapter这套API,跨端兼容性已经比较成熟了。技术上我的选型是:Vue 3 + Vite + Pinia + uni-app,地图组件用内置map,图表用ucharts,蓝牙走uni API,定位用uni.getLocation加plus.android的GPS辅助。
1.2 为什么选择uni-app而不是纯原生
我第一次做类似项目时用的是Android原生,结果iOS端客户又提需求,硬生生用Swift又写了一遍,两套代码维护成本直接把利润吃了大半。后面改uni-app后发现,地图、蓝牙、定位、文件存储这些常用能力都有现成API,90%的逻辑能复用。对创业团队或接外包项目来说,时间和人力是最贵的,uni-app在这个场景下性价比确实高。
至于性能问题,运动轨迹记录的实时打点频率一般是1秒1个点,数据量并不大,js层面处理完全不在话下。地图marker和polyline的更新渲染也没压力,真正要注意的是定位本身的功耗、GPS信号弱时的处理逻辑,这些和框架关系不大,更多是原生能力和策略设计的问题。
1.3 目录结构与状态管理规划
项目结构我习惯按业务域划分,不按页面划分,这样协作起来边界清晰:
src/ ├── api/ │ ├── user.js // 账户与登录相关接口 │ ├── record.js // 运动记录上传与拉取 │ └── device.js // 手环绑定关系与设置同步 ├── components/ │ ├── sport-map.vue // 地图封装组件 │ ├── heart-chart.vue // 心率图表封装 │ └── device-card.vue // 设备状态卡片 ├── pages/ │ ├── index/ │ ├── sport/ │ ├── record/ │ ├── device/ │ └── mine/ ├── stores/ │ ├── sport.js │ ├── device.js │ └── user.js ├── utils/ │ ├── gps.js // 坐标转换、距离计算、滤波 │ ├── ble.js // 蓝牙工具统一封装 │ └── format.js // 时间、配速、卡路里格式化 └── static/状态管理用Pinia,我是从Vue 2 + Vuex迁移过来的。如果项目还在Vue 2,建议尽早升Vue 3。uniapp生态目前对Vue 3支持已经很好了,HBuilderX 3.7以后的版本都没什么问题。迁移的核心工作基本就是组合式API重写逻辑、替换filter为计算属性、Vuex改Pinia写法,工作量可控。
2. 定位采集与轨迹生成的实战实现
轨迹记录最核心的就是定位,也是最容易被做烂的部分。很多人直接调uni.getLocation就开干,结果后台跑几分钟就掉线,或者定位点漂移严重,画出来的轨迹穿墙过河,看起来非常不专业。这里我把定位相关的完整方案拆开说清楚。
2.1 定位方式选择:GPS、基站还是网络定位
先说结论:运动记录场景下,要拿系统级GPS定位,而不是uni.getLocation默认的定位。uni.getLocation在App端底层其实走的是系统定位服务,但在不同机型上默认精度有差异。如果你直接用type: 'gcj02',部分安卓机在室内会返回网络定位的结果,导致轨迹乱七八糟。
我的做法是获取定位时同时判断返回的accuracy(精度值),如果大于30米就直接丢弃这个点,改为等待下一个点。这里的逻辑参考了GPS定位的基础常识:手机定位一般有GPS、基站和WiFi三种方式。GPS精度在开阔地能达到3到10米,适合记录跑步骑行;基站定位精度是几百米到几公里,只能用来判断城市级别的位置;WiFi定位在室内能到30到80米,但运动场景大多数在室外,没必要依赖。
为了让运动轨迹稳定,我会在运动开始时检查当前定位精度,开启一个连续的定位监听:
// 运动开始,开启连续定位 export function startLocationWatch(callback) { const watchId = plus.geolocation.watchPosition( (res) => { const { latitude, longitude, accuracy, heading, speed } = res.coords // 过滤精度过低的点 if (accuracy && accuracy > 30) return callback({ latitude, longitude, accuracy, speed: speed || 0, heading: heading || 0, time: Date.now() }) }, (err) => { console.error('定位监听失败', err) }, { enableHighAccuracy: true, // 强制GPS timeout: 10000, maximumAge: 0, coordsType: 'gcj02' } ) return watchId }这里必须用plus.geolocation的watchPosition而不是uni.getLocation反复轮询,原因有两个:一是watchPosition是系统级持续回调,省电且定位连贯;二是你手动setInterval去调uni.getLocation,每次都要重新发起定位请求,反而更耗电,而且定位跳变的概率更高。
2.2 轨迹纠偏与距离计算的隐藏逻辑
拿到了原始定位点,直接画线肯定不行。运动轨迹有很明显的两类问题:
- 漂移点:定位精度差时,点会突然跳到几十米外,折返后轨迹像锯齿
- 静止抖点:用户停下来休息,但定位精度波动导致坐标在小范围内乱飘,距离被白白计算
我参考了行业里轨迹清洗的常用方法,自己写了一套轻量级过滤方案,核心逻辑就几点:
- 若当前点与前一个点的距离小于3米,且速度低于0.5米每秒,认为是抖点,丢弃。
- 若当前点与上个点的计算速度超过10米每秒(相当于36公里/小时),且没有明显方向突变,认为是漂移点,丢弃。
- 计算距离时使用Haversine公式,而不是简单的平面距离。
在轨迹纠偏这块,还有一种常见做法是用卡尔曼滤波或者滑动平均值滤波。滑动平均值在我的测试里虽然能平滑曲线,但弯道处轨迹容易被拉直,不太推荐。卡尔曼滤波效果好,但要在js里手工维护状态矩阵,代码量比较大。我这里选择的是简化版阈值加方向判定,对大多数运动场景已经足够了。
2.3 计算距离、配速与卡路里
Haversine公式是正儿八经的球面距离计算,做运动记录的话是必备的。我封装成工具函数:
export function haversineDistance(lat1, lng1, lat2, lng2) { const radLat1 = (lat1 * Math.PI) / 180 const radLat2 = (lat2 * Math.PI) / 180 const a = Math.sin((radLat2 - radLat1) / 2) ** 2 + Math.cos(radLat1) * Math.cos(radLat2) * Math.sin(((lng2 - lng1) * Math.PI) / 180 / 2) ** 2 const distance = 6371000 * 2 * Math.asin(Math.sqrt(a)) return Math.round(distance) }配速直接距离除以运动时长即可,但要过滤掉停留时间超过2分钟的暂停段。
卡路里计算我踩过不少坑。不同运动类型其实应该用不同的MET值(代谢当量),跑步MET是9.8,步行MET是3.8,骑行MET是7.5。计算公式是:卡路里 = MET × 体重(kg) × 时间(小时)。这套公式好在简单稳定,准确率在手表手环行业也算是通行方案。
2.4 后台保活与墓碑机制
很多人在App切入后台后轨迹就断了,或者再回来定位点连不上了。这个问题要从两个层面解决:
- 尽量使用系统级定位API,让定位逻辑在系统层面持续运行,而不是依赖js定时器
- 检测App进入后台时,保存当前运动状态到本地存储,防止被杀进程后数据丢失
同时在Android上需要申请后台定位权限:
"permissions": { "android": { "permissions": [ "android.permission.ACCESS_BACKGROUND_LOCATION", "android.permission.FOREGROUND_SERVICE", "android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_FINE_LOCATION" ] } }iOS端需要在manifest.json的App模块配置里开启UIBackgroundModes,并把location加入后台模式,同时打包时要在Xcode里配置NSLocationAlwaysAndWhenInUseUsageDescription描述。这块如果配置漏了,运动到一半锁屏再解锁轨迹就直接断。
2.5 地图组件的轨迹绘制与重置
地图轨迹绘制本身不难,难点在于适配不同屏幕尺寸和地图初始缩放级别。我的方案是计算所有定位点的最大经纬度差,然后通过map组件的include-points属性,让地图自动调整视野包含所有轨迹点。
<template> <map id="sportMap" :show-location="true" :polyline="polyline" :include-points="includePoints" :markers="markers" class="sport-map" /> </template>需要重置地图视野时,更新include-points数组即可触发地图重新计算。如果发现Android端地图有黑边问题,多半是地图组件的宽高没有设置成具体数值,或者是map组件被放在scroll-view里导致的渲染异常。我最终是将地图容器独立放一个页面,并在onReady后再渲染地图,彻底解决了黑边问题。
3. 蓝牙手环连接的完整闭环
手环端最让人头疼的就是蓝牙。手环厂家用的协议各不相同,但连接机制大同小异,本质都是BLE(低功耗蓝牙)。连接手环的流程一般是扫描、广播数据解析、配对/绑定、特征值读写、通知监听。我从接过的几十款手环经验里整理出这套通用流程。
3.1 扫描、连接和服务发现
扫描手环前要确认手机蓝牙是否打开、定位权限是否授权(Android蓝牙扫描需要定位权限)。uni-app端直接用uni.openBluetoothAdapter初始化,再用uni.startBluetoothDevicesDiscovery开始扫描。
export function scanDevices(onFound) { uni.openBluetoothAdapter({ success() { uni.startBluetoothDevicesDiscovery({ allowDuplicatesKey: false, success() { uni.onBluetoothDeviceFound((res) => { const devices = res.devices || [] devices.forEach((device) => { // 过滤掉信号太弱的设备 if (device.RSSI && device.RSSI < -80) return onFound(device) }) }) } }) }, fail(err) { console.error('蓝牙初始化失败', err) } }) }扫描到设备后,下一步是连接。手环设备一般会广播deviceName,可以直接拿这个名字做过滤。连接成功后,要主动获取服务和特征值,因为不同厂家的心率数据通道不一样,有的通过通知特征值主动上报,有的需要写入特定命令才返回。服务发现完成后,通过uni.notifyBLECharacteristicValueChange开启监听,手环才会主动往手机推数据。
3.2 心率、步数数据的监听及解析
数据解析这一步最容易踩坑。BLE的notify返回的是ArrayBuffer,需要按厂商协议转成具体数值。大部分手环心率数据格式是2个字节的uint16,有些是1个字节,有些还带状态位。稳妥的做法是先打印原始字节,再对照协议文档解析:
uni.onBLECharacteristicValueChange((res) => { const dataview = new DataView(res.value) // 假设心率数据在第4和第5个字节 const heartRate = dataview.getUint8(4) if (heartRate > 30 && heartRate < 220) { store.commit('updateHeartRate', heartRate) } })这里必须做有效范围校验,我见过有些手环在传感器未接触皮肤时会回传0或255这种脏值,一定要过滤掉再展示,否则心率曲线里突然冒出一个255的尖峰,用户看到会觉得产品不靠谱。
设备配对数据我一般存在本地storage和远端服务器里,本地存的是deviceId和deviceName,方便下次启动直接连接。远端存的是用户和设备绑定关系,防止换手机后重新绑定。
3.3 重连机制与多设备管理
手环设备的重连策略,我的做法是在App启动时尝试连接上次绑定的设备,连续失败3次就停止自动重连,改为提示用户手动连接。每次连接前先检查蓝牙开关状态,避免在蓝牙未开启时一直做无效连接。
多设备管理上用Pinia保存一个deviceList,手环和手机通过uuid区分设备类型。为了避免同时连接两台手环导致数据混乱,我在stores/device.js里加了一个activeDevice的互斥逻辑:新设备连接成功后,自动断开之前的连接。
3.4 NFC与其他能力扩展
有客户提出过手环需要NFC刷卡,这个如果手环硬件本身支持NFC,可以通过uni.openNFCAdapter读取卡片。但要注意,uni-app的NFC API目前只支持H5端和部分Android WebView场景,iOS限制比较多。NFC功能建议做增量模块,单独判断平台兼容性,不要影响主流程打包。蓝牙打印也是类似逻辑,通过蓝牙连接便携打印机,将运动报告打印成小票,这一块主要是处理指令集的二进制拼接,工作量不大但比较烦。
4. 运动数据的存储、统计与可视化
记录完轨迹只是第一步,用户的运动数据大概率是长期积累的,所以存储和统计架构要提前想好。
4.1 本地存储与云端同步策略
单次运动记录的文件里包含gps轨迹点数组(经纬度+时间戳+速度),一个小时的跑步大概就有3600个点。如果全部存到storage里,很容易撑爆5MB的本地存储限制。我的方案是使用plus.io的file系统,把轨迹数据写到应用的私有目录下,按日期和运动类型分目录存储。
云端同步使用uni.request上传文件,上传前将轨迹点序列化为JSON字符串并gzip压缩。服务端收到后存到对象存储,App端历史记录只拉取摘要信息(距离、时长、平均配速、起终点),需要查看详情时再下载轨迹文件。这样列表页加载快,详情页数据源又完整。
4.2 图表展示的实现细节
运动详情页我用了ucharts组件,但ucharts的高心率曲线和配速曲线是两组数据、两个Y轴需求。ucharts本身不支持双y轴,所以我拆成两个图表组件分别展示,时间轴对齐后上下排列。这样实现简单,视觉上也不会太拥挤。周运动量的柱状图直接使用ucharts的column类型,给不同运动类型配上不同颜色,达成一眼看懂的效果。
4.3 分享海报与运动总结
分享功能在运动App里非常重要。用户跑完5公里,总想晒个图。我的方案是生成一张运动总结海报:背景图是一张精心设计的运动主题图,中间区域用canvas绘制距离、配速、时长、卡路里四个核心指标,底部再放一张地图截图。地图截图可以通过页面组件canvas的方式实现,也可以后端用静态地图服务生成。我这里使用前端canvas绘制全部内容,小程序端的canvas和App端API不同,我封装了一个canvasDraw函数兼容两端。
uni-app夹带的自定义分享按钮,可以通过uni.showShareMenu和uni.onShareAppMessage实现。注意App端分享到微信需要配置ShareSDK,并在manifest里填写微信AppID。
5. 多端适配与权限配置(微信小程序/H5/App)
这个项目同时跑了微信小程序、H5(嵌入公众号)、App三端,问题排查经验还是很有代表性的。
5.1 manifest.json的权限与模块配置
App端定位、蓝牙、拍照等权限都在manifest.json的App模块配置里。需要特别注意Android权限里有一堆默认权限,不要全部勾选,多了会导致应用市场审核时被问询。我的建议是只保留天气、蓝牙、定位;
另外,iOS端需要在manifestUIApplicationSupportsIndoorMaps等字段,嵌入式地图最好在打包前确认地图SDK配置完成。
5.2 H5嵌入公众号的定位获取
H5端在微信公众号里获取定位,最稳定的是通过微信JS-SDK。用uni-app的H5端引用jssdk,在index.html里引入script标签,再在页面里执行wx.getLocation获取坐标。需要特别注意的是公众号后台需要配置JS接口安全域名,同时签名用的url必须是当前页面的完整地址,不能是入口地址,很多人在这一步调了半天。
5.3 微信小程序端地图和分享差异
微信小程序端的地图组件和App端表现有差异,主要体现在缩放行为和marker图标尺寸。小程序的map组件在Android和iOS上渲染机制不同,Android上频繁更新polyline时偶尔会出现闪烁。我处理的办法是减少polyline更新频率,每10秒刷一次,而不是每个定位点都更新。
小程序端的分享不通过uni.onShareAppMessage,而是通过button的open-type="share"来实现。如果遇到条件编译导致的报错,可以先检查代码里是否误用了H5端的API,小程序和H5的API集合不是完全一致的。
5.4 Vue 2转Vue 3的迁移经验
如果你的项目还在Vue 2,转Vue 3的时候关键地方是:把main.js里的Vue.use改成createSSRApp,Vuex改成Pinia,过滤器统一改成方法或计算属性。另外uni-app里有些生命周期钩子和Vue 3不兼容,比如onLaunch在App.vue里要改成onLaunchApp这种写法,但实际项目里如果用了旧写法可能会在H5端报错,需要逐一排查。
6. 性能优化与耗电控制策略
穿戴设备的App最怕耗电。一个定位App如果一小时吃掉20%的电量,用户跑步回来手机快没电了,体验就崩了。所以这一章我必须单独讲。
6.1 定位频率的动态调整
我的策略是:运动开启阶段,1秒1次定位;运动稳定后,如果检测到速度变化不大,改为3秒1次;同时启用Activity Recognition来判断用户在跑步还是走路,但这里如果不想引入额外的SDK,简单的速度阈值判断也够用。
具体代码如下:
let lastLat = null let lastLng = null let lastTime = 0 let lastSpeed = 0 function onLocationUpdate(point) { const now = Date.now() const timeDiff = (now - lastTime) / 1000 const speed = point.speed if (timeDiff > 0) { const instantSpeed = speed || (distance / timeDiff) // 速度较快时缩短打点间隔 if (instantSpeed > 2.5) { intervalMs = 2000 } else if (instantSpeed > 1) { intervalMs = 3000 } else { intervalMs = 5000 } } // 动态调整watchPosition参数 restartWatch(intervalMs) }GPS芯片在持续高频率工作时的功耗远大于间歇性工作,所以间隔从1秒调到3秒能显著降低耗电,而轨迹质量并不会明显下降。
6.2 屏幕常亮与息屏策略
运动过程中用户经常需要看配速和里程,但一直亮屏也耗电。我的方案是:运动页面在前台时设置plus.navigator.setKeepScreenOn(true),保证用户盯着屏幕时不会自动息屏;一旦App进入后台或运动暂停,立即释放屏幕常亮。很多开发者忘了释放常亮状态,结果用户退到桌面App还在烧电。
6.3 数据采样节流与合并
有些手环心率数据是每秒上报一次,如果前端直接全部写库,一个小时的图表数据量非常大,而且显示上也没有意义。我做了两级缓存:第一级是内存缓存1秒1条原始数据,第二级是每10秒聚合成一条均值记录。生成图表时,如果运动时长超过1小时,再进一步聚合到每分钟一条。这样前端渲染非常流畅,图表也不会显得杂乱。
7. 打包上架与iOS/Android发布要点
项目开发完要交付,打包上架是最后一步,也是很多人临时抱佛脚的地方。这里把经验一次说清。
7.1 Android打包:证书、渠道包与上架审核
使用HBuilderX云打包还是本地Android Studio打包,主要看你有没有需求要集成原生SDK。如果只用到uni-app内置模块,云打包足够。如果要用自定义原生插件,就得走本地打包。
Android上架Google Play时,从2023年开始要求App Bundle格式,HBuilderX云打包也能生成aab包。国内安卓市场如华为、小米、OPPO、vivo,要求统一签名证书,并且加固。建议先用加固工具对apk做加固再上传各市场,不然合规审核很容易因为“未加固”被拒。
7.2 iOS打包:证书、描述文件与TestFlight
iOS打包相对麻烦一点,必须先有Apple开发者账号,生成发布证书和描述文件。HBuilderX云打包选择iOS打包,上传p12证书和.mobileprovision描述文件即可。
常见问题:
- 证书和描述文件不匹配:在Apple Developer后台重新生成描述文件,选择对应证书
- App启动闪退:检查是否开启了后台定位的plist描述
- App Store审核被拒2.1大项:补充隐私政策、权限用途说明,注意运动数据属于健康数据,在审核时需要有明确的数据使用声明
7.3 修改启动页和加载页
uni-app默认的启动页是纯白或uniapp logo,上架需要换成客户品牌图。启动页图片在manifest.json的可视化界面里配置,App端支持配置通用启动图和适配各机型的启动图。注意HBuilderX 3.x以上,启动图支持仅放一张中间logo图,其余部分自适应,但这个只对iOS效果好,Android各品牌手机适配还是建议做多张启动图。
7.4 自动更新与灰度发布
上架后App还需要迭代。Android端可以通过自己服务器的更新接口实现App内弹窗升级下载apk。iOS不能走自己的渠道,只能通过App Store审核,没有太好的办法,这是苹果生态的限制。
8. 常见问题与调试技巧速查表
我把项目里真实遇到的高频问题整理成一张速查表,新手遇到的问题基本都能在这里找到答案。
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 蓝牙扫描不到手环 | 未打开定位权限/蓝牙未开启 | 确认Android的ACCESS_FINE_LOCATION权限和蓝牙开关 |
| 扫描到设备连不上 | 手环已连接其他手机 | 先在原手机上解绑,再重新扫描连接 |
| 收到心率数据为0或255 | 手环未贴合皮肤或脏值 | 过滤0~30和大于220的数据 |
| 轨迹漂移严重 | GPS精度不足或有高楼遮挡 | 丢弃accuracy大于30的点,或开启辅助定位 |
| App切后台轨迹断了 | 后台定位权限/服务配置缺失 | 检查Android后台定位权限和iOS UIBackgroundModes |
| 地图polyline闪烁 | 频繁更新轨迹数据 | 合并更新,10秒一次 |
| H5定位失败 | JS-SDK签名url不对 | 重新获取签名,使用当前完整地址 |
| 小程序分享无法触发 | 使用了open-type="share"以外的方式 | 检查是否用了button的open-type,且页面配置了onShareAppMessage |
| iOS审核被拒 | 权限用途描述不清晰 | 在Info.plist里补充准确用途文案 |
| Android加固后闪退 | 未保留v1/v2签名 | 加固后重新签名,保留v1+v2 |
8.1 调试定位的独家技巧
调试定位最痛苦的是不知道当前GPS状态。我在开发模式里加了一个debug页面,实时展示定位状态、精度、卫星数、当前速度。通过这个页面能一眼看出是定位没回调、精度太差还是坐标转换出错。GPS室内基本不可用,如果你的测试人员在室内测试喊“定位不准”,大概率测试方法本身有问题。
8.2 处理condition编译差异的通用方案
uni-app条件编译是跨端开发的核心工具。我的习惯是写一个platform.js工具,统一封装平台差异:
// #ifdef APP-PLUS export const LOCATION_MODULE = 'plus' // #endif // #ifdef MP-WEIXIN export const LOCATION_MODULE = 'wx' // #endif所有平台差异化逻辑都通过这个模块分发,避免业务代码里散落一堆条件编译,后面维护起来会非常难受。
8.3 版本回退与热修复预案
App上线后如果遇到线上崩溃,热修复方案要看具体原因。uni-app项目如果是纯js问题,可以通过整包更新解决,但如果涉及原生模块,只能重新发版。建议在上线前做一个检查清单:三端编译是否通过、权限描述是否存在、地图key是否正确、蓝牙服务是否注册、云端接口是否通。
9. 继续扩展的思路
这个项目后续扩展的方向很多。比如接入更多运动类型(游泳、跳绳),加AI运动姿势分析,引入社交排行榜,或者把心率数据与轨迹结合做专业训练分析。数据层面也可以对接HealthKit和Google Fit,把手环收集的数据同步到系统健康应用,这样用户在系统健康里也能看到运动记录,用户粘性会强很多。
我个人实际操作中的体会是,wearable App开发,最难的不是代码,而是你对设备协议和系统权限的理解程度。蓝牙和定位两大块是最容易出现“开发两天、调试两周”的地方,所以项目初期的技术调研一定要做扎实。如果需要接入具体手环硬件,一定要找厂商要协议文档,不要靠猜。最后再分享一个小技巧:所有手环的数据通道,在开发前先自己用蓝牙调试工具把原始数据打印一遍,确认数据格式和理解一致后再开始写解析逻辑,能帮你省掉至少三天联调时间。