心率實(shí)時(shí)傳輸?shù)経WP應(yīng)用:手機(jī)中轉(zhuǎn)+局域網(wǎng)推送方案)
前陣子接手一個(gè)項(xiàng)目要在UWP應(yīng)用里實(shí)時(shí)顯示華為手環(huán)的心率數(shù)據(jù)。網(wǎng)上搜了一圈中文資料要么停在“用手機(jī)App看就行”的層面要么就是問了一句“能不能連PC”然后就沒了下文。真正動(dòng)手做才發(fā)現(xiàn)華為手環(huán)不像專業(yè)心率帶那樣公開標(biāo)準(zhǔn)藍(lán)牙協(xié)議直接連PC這條路基本走不通。折騰了幾天最后我用了一套“手機(jī)采集局域網(wǎng)推送UWP接收”的架構(gòu)把整條鏈路打通了。這篇文章就把整個(gè)方案從架構(gòu)設(shè)計(jì)、代碼實(shí)現(xiàn)到遇到的坑都梳理一遍給你一套能照著復(fù)現(xiàn)的參考實(shí)現(xiàn)。如果你最近也在做類似的項(xiàng)目或者正打算把手環(huán)的實(shí)時(shí)心率數(shù)據(jù)接到Windows桌面端那這篇文章很適合你。我會(huì)先說清楚數(shù)據(jù)到底該怎么拿再講Android端怎么采集和推送最后說UWP端怎么接收和展示中間穿插我實(shí)測(cè)中遇到的問題和解決辦法。1. 先聊清楚華為手環(huán)的“實(shí)時(shí)心率”到底怎么拿1.1 手環(huán)數(shù)據(jù)的三條獲取路徑要拿到華為手環(huán)的心率數(shù)據(jù)從技術(shù)上看主要有三條路。第一條是走華為官方的Health Kit接口。華為運(yùn)動(dòng)健康A(chǔ)pp把用戶授權(quán)過的心率、睡眠、運(yùn)動(dòng)等數(shù)據(jù)傳到華為云端第三方應(yīng)用通過Health Kit獲取。這條路最穩(wěn)官方維護(hù)不需要逆向任何協(xié)議但需要申請(qǐng)開發(fā)者權(quán)限而且數(shù)據(jù)要經(jīng)過手機(jī)App中轉(zhuǎn)。第二條是直接用藍(lán)牙BLE連接手環(huán)讀取GATT服務(wù)里的心率特征。這條路上很多做智能硬件的人第一反應(yīng)就是它但對(duì)華為手環(huán)來說坑很深。華為手環(huán)對(duì)外基本不暴露標(biāo)準(zhǔn)心率服務(wù)數(shù)據(jù)走的是私有協(xié)議官方也沒有公開文檔。即使通過抓包或者社區(qū)逆向拿到了一部分協(xié)議手環(huán)固件一升級(jí)可能就廢了。第三條是走系統(tǒng)級(jí)共享通道。部分華為手機(jī)和手環(huán)之間有私有數(shù)據(jù)通道華為運(yùn)動(dòng)健康A(chǔ)pp會(huì)將一些數(shù)據(jù)寫入系統(tǒng)傳感器其他應(yīng)用通過系統(tǒng)API讀取。這個(gè)方案限制很大只適用于部分華為設(shè)備對(duì)PC端UWP項(xiàng)目來說完全不現(xiàn)實(shí)。1.2 為什么直接拿BLE連PC最容易被卡住我一開始也抱著僥幸心理想著“藍(lán)牙心率服務(wù)都是標(biāo)準(zhǔn)化的0x180D這個(gè)服務(wù)ID總該有吧”。實(shí)測(cè)結(jié)果很打臉華為手環(huán)在PC上能被掃描到但廣播包里根本沒有標(biāo)準(zhǔn)Heart Rate Service。強(qiáng)行用BluetoothLEDevice.FromIdAsync去連接也拿不到0x180D的服務(wù)句柄。就算有些型號(hào)能連接上手環(huán)也不會(huì)默認(rèn)向外持續(xù)發(fā)送心率數(shù)據(jù)。手環(huán)的心率上報(bào)機(jī)制是“按需觸發(fā)”的只有在華為運(yùn)動(dòng)健康A(chǔ)pp建立連接并且開啟運(yùn)動(dòng)模式之后才會(huì)以較高頻率持續(xù)上報(bào)。PC上的UWP應(yīng)用沒有華為的私有配對(duì)認(rèn)證流程很難觸發(fā)這個(gè)狀態(tài)。另外Windows的BLE API本身就面向標(biāo)準(zhǔn)GATT協(xié)議設(shè)計(jì)對(duì)私有服務(wù)和加密特征的支持很有限。所以直接BLE連PC這個(gè)方案從穩(wěn)定性和工作量角度來看都不劃算。1.3 我的推薦架構(gòu)手機(jī)做中轉(zhuǎn)UWP做接收端既然直接連PC走不通那就換一條務(wù)實(shí)路線。最終我采用的是這樣一條數(shù)據(jù)鏈路華為手環(huán) → 華為運(yùn)動(dòng)健康A(chǔ)pp藍(lán)牙連接→ 手機(jī)端Health Kit SDK讀取 → 局域網(wǎng)Socket推送 → PC端UWP接收解析并展示這個(gè)架構(gòu)的好處有幾個(gè)。數(shù)據(jù)源頭是華為官方通道心率數(shù)值的可信度有保障。不需要逆向藍(lán)牙協(xié)議只要把Health Kit的權(quán)限申請(qǐng)下來就能用。手機(jī)端負(fù)責(zé)采集和轉(zhuǎn)發(fā)PC端只做接收和業(yè)務(wù)展示職責(zé)分離清晰后續(xù)換設(shè)備或者加功能都比較方便。唯一的代價(jià)就是手機(jī)必須常駐后臺(tái)而且手機(jī)和PC需要處在同一個(gè)局域網(wǎng)內(nèi)。對(duì)于個(gè)人項(xiàng)目或者實(shí)驗(yàn)室Demo來說這個(gè)代價(jià)完全能接受。2. 數(shù)據(jù)源頭手機(jī)端用華為Health Kit采集心率2.1 申請(qǐng)Health Kit權(quán)限與工程配置華為Health Kit不是開通就能用的需要在華為開發(fā)者聯(lián)盟逐步申請(qǐng)。第一步注冊(cè)開發(fā)者賬號(hào)并且創(chuàng)建應(yīng)用包名、簽名證書的SHA256指紋必須和你最后打包用的證書一致。這一點(diǎn)很多人會(huì)忽略先用debug簽名申請(qǐng)后面換成release證書又會(huì)遇到鑒權(quán)失敗還得重新走審核流程。創(chuàng)建完應(yīng)用之后在AppGallery Connect后臺(tái)開通Health Kit服務(wù)。注意心率屬于敏感健康數(shù)據(jù)申請(qǐng)權(quán)限時(shí)最好把使用場(chǎng)景寫清楚比如“用于PC端實(shí)時(shí)心率監(jiān)控demo”審核周期一般是1到3個(gè)工作日。我那次等了兩天半所以項(xiàng)目排期一定要把這個(gè)時(shí)間算進(jìn)去。工程配置方面把后臺(tái)下載的agconnect-services.json放到Android工程的app目錄下然后在根目錄的build.gradle里配置華為Maven倉(cāng)庫(kù)在app/build.gradle里添加Health Kit依賴。不同版本SDK的依賴坐標(biāo)略有差異大致是這樣的寫法dependencies { implementation com.huawei.hms:health:6.11.0.301 }2.2 授權(quán)登錄與心率讀取流程Health Kit的使用有個(gè)前置條件用戶必須先用華為賬號(hào)登錄并且明確授權(quán)你的應(yīng)用讀取心率數(shù)據(jù)。這個(gè)授權(quán)流程在代碼里分三步。第一步集成華為賬號(hào)登錄也就是Account Kit。用戶點(diǎn)擊登錄按鈕之后會(huì)跳轉(zhuǎn)到華為賬號(hào)授權(quán)頁(yè)同意后返回Authorization Code。第二步用這個(gè)Code去換取Health Kit的訪問權(quán)限也就是申請(qǐng)scope心率對(duì)應(yīng)的scope類似https://www.huawei.com/health/kit/heartrate。第三步檢查用戶是否已經(jīng)在華為運(yùn)動(dòng)健康A(chǔ)pp里綁定了手環(huán)并且開啟了數(shù)據(jù)同步。這一步經(jīng)常被漏掉如果運(yùn)動(dòng)健康A(chǔ)pp里沒有設(shè)備Health Kit拿到的永遠(yuǎn)是空數(shù)據(jù)。授權(quán)通過之后就可以創(chuàng)建HealthDataCollector來讀取心率了。我當(dāng)時(shí)的偽代碼大致是這個(gè)樣子class HeartRateService : Service() { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { val healthDataCollector HealthDataCollector(this) healthDataCollector.setDataCollectorListener( object : DataCollectorListener { override fun onReceiveData( dataType: HiHealthDataType, samplePoints: ListSamplePoint ) { val heartRate samplePoints .firstOrNull() ?.getFieldValue(HiHealthFields.FIELD_HEART_RATE) heartRate?.let { sendToUwp(it) } } }, HiHealthDataType.CONTINUOUS_HEART_RATE ) return START_STICKY } }這里要提醒一句華為SDK的類名在不同版本里有過調(diào)整DataCollectorListener可能在新的SDK里改成了別的回調(diào)接口。寫代碼的時(shí)候以官方文檔為準(zhǔn)但整體思路不變注冊(cè)一個(gè)針對(duì)連續(xù)心率數(shù)據(jù)類型的監(jiān)聽拿到SamplePoint之后提取心率字段。2.3 把心率數(shù)據(jù)推到PC端UWP手機(jī)端拿到心率之后接下來的任務(wù)就是把數(shù)據(jù)送到PC。我在這個(gè)環(huán)節(jié)選擇了TCP長(zhǎng)連接而不是HTTP輪詢。原因是心率數(shù)據(jù)是持續(xù)產(chǎn)生的TCP長(zhǎng)連接一次握手之后就能一直傳數(shù)據(jù)延遲低很多UWP端的StreamSocketListener實(shí)現(xiàn)起來也不復(fù)雜。為了保證UWP端能正確切割數(shù)據(jù)幀我約定了以換行符\n作為每條JSON數(shù)據(jù)的結(jié)束標(biāo)志。Android端發(fā)送心率的代碼簡(jiǎn)化之后是這樣的fun sendHeartRate(heartRate: Int, timestamp: Long) { Thread { try { val socket Socket(192.168.1.100, 9000) val json {\heartRate\:$heartRate,\timestamp\:$timestamp}\n socket.getOutputStream().write(json.toByteArray(Charsets.UTF_8)) socket.close() } catch (e: Exception) { Log.e(HeartRateSender, send failed, e) } }.start() }在實(shí)際工程里需要處理幾個(gè)細(xì)節(jié)。一是連接斷開后的自動(dòng)重連我建議在手機(jī)端維護(hù)一個(gè)全局的Socket連接斷線后每秒重試一次。二是發(fā)送頻率不要過高心率數(shù)據(jù)每秒一次到兩次就夠用了否則手機(jī)耗電和PC端UI刷新壓力都會(huì)變大。三是建議用前臺(tái)服務(wù)來跑這個(gè)發(fā)送邏輯否則Android系統(tǒng)會(huì)在App退到后臺(tái)幾分鐘后殺掉進(jìn)程。2.4 提高實(shí)時(shí)性的幾個(gè)小技巧Health Kit雖然能拿到數(shù)據(jù)但實(shí)時(shí)性取決于手環(huán)的上報(bào)策略。我實(shí)測(cè)發(fā)現(xiàn)手環(huán)在普通待機(jī)狀態(tài)下心率上報(bào)頻率很低可能在幾十秒到幾分鐘才更新一次。要讓數(shù)據(jù)更實(shí)時(shí)需要做幾件事。第一讓手環(huán)進(jìn)入運(yùn)動(dòng)模式。華為手環(huán)在跑步、走路這類運(yùn)動(dòng)模式下心率的采樣頻率會(huì)明顯提高基本能做到每秒甚至更密的上報(bào)。第二把華為運(yùn)動(dòng)健康A(chǔ)pp在手機(jī)后臺(tái)鎖定同時(shí)關(guān)閉系統(tǒng)省電策略對(duì)它的限制否則App一被殺數(shù)據(jù)鏈路就斷了。第三如果SDK支持配置讀取周期把周期設(shè)為盡可能短比如1秒讀取一次。實(shí)測(cè)下來這樣調(diào)整之后UWP端收到的數(shù)據(jù)延遲基本在1到3秒以內(nèi)。對(duì)于“實(shí)時(shí)心率展示”這個(gè)需求來說這個(gè)延遲是完全可以接受的。3. UWP端工程搭建從收數(shù)據(jù)到圖表展示3.1 UWP項(xiàng)目的基礎(chǔ)配置UWP端我用的是標(biāo)準(zhǔn)空白應(yīng)用模板目標(biāo)版本設(shè)置成Windows 10 1809以上就可以。創(chuàng)建完項(xiàng)目之后有兩件事必須第一時(shí)間做掉。第一件事是修改Package.appxmanifest聲明網(wǎng)絡(luò)和藍(lán)牙能力。如果只走局域網(wǎng)Socket方案聲明privateNetworkClientServer就夠了如果后面還想嘗試BLE直連還需要加上bluetooth設(shè)備能力。聲明之后看起來像這樣Capabilities Capability NameprivateNetworkClientServer / DeviceCapability Namebluetooth / /Capabilities第二件事是添加JSON解析庫(kù)。微軟官方的System.Text.Json在UWP上可以直接用但我個(gè)人更喜歡用Newtonsoft.JsonAPI熟悉UWP兼容性也穩(wěn)定。這里有個(gè)容易踩的坑UWP默認(rèn)的網(wǎng)絡(luò)訪問權(quán)限很嚴(yán)格如果忘了聲明privateNetworkClientServer程序運(yùn)行時(shí)會(huì)默默拋異常不會(huì)彈明顯的錯(cuò)誤提示。所以遇到收不到數(shù)據(jù)的問題時(shí)優(yōu)先檢查這個(gè)聲明有沒有寫上。3.2 用StreamSocketListener實(shí)現(xiàn)TCP接收端UWP里搭建TCP接收端比較簡(jiǎn)單核心就兩個(gè)類StreamSocketListener負(fù)責(zé)監(jiān)聽連接DataReader負(fù)責(zé)讀數(shù)據(jù)。啟動(dòng)監(jiān)聽的代碼大致是這樣private StreamSocketListener _listener; private async void StartServer() { _listener new StreamSocketListener(); _listener.ConnectionReceived OnConnectionReceived; await _listener.BindServiceNameAsync(9000); }連接建立之后每次收到數(shù)據(jù)時(shí)觸發(fā)OnConnectionReceived回調(diào)。這里需要注意TCP是流式協(xié)議Android端發(fā)送的多個(gè)JSON包可能粘在一起也可能發(fā)生半包。所以我寫了一個(gè)簡(jiǎn)單的按行讀取方法以\n作為分隔符切分完整幀private async Taskstring ReadLineAsync(StreamSocket socket) { var reader new DataReader(socket.InputStream); reader.InputStreamOptions InputStreamOptions.Partial; var builder new StringBuilder(); while (true) { var size await reader.LoadAsync(512); if (size 0) break; for (uint i 0; i size; i) { var ch (char)reader.ReadByte(); if (ch \n) return builder.ToString(); builder.Append(ch); } } return null; }這個(gè)方法的核心思路是用Partial模式每次先讀一批字節(jié)然后逐個(gè)字符找換行符找到就返回一幀數(shù)據(jù)沒找到就把當(dāng)前內(nèi)容緩存到StringBuilder里繼續(xù)讀。實(shí)測(cè)下來對(duì)心率這種小數(shù)據(jù)量的場(chǎng)景完全夠用。3.3 JSON解析與界面實(shí)時(shí)刷新收到完整的一幀JSON字符串后就可以解析成對(duì)象了。我定義的數(shù)據(jù)結(jié)構(gòu)很簡(jiǎn)單public class HeartRateData { public int HeartRate { get; set; } public long Timestamp { get; set; } }解析代碼用Newtonsoft.Json一行就能搞定var data JsonConvert.DeserializeObjectHeartRateData(line);拿到數(shù)據(jù)之后剩下的事情就是更新界面。UWP中UI更新必須回到UI線程我用的是DispatcherQueue來實(shí)現(xiàn)。核心邏輯是這樣private async void OnHeartRateReceived(HeartRateData data) { await DispatcherQueue.EnqueueAsync(() { CurrentHeartRateText.Text data.HeartRate.ToString(); LastUpdateTimeText.Text DateTimeOffset.FromUnixTimeMilliseconds(data.Timestamp).ToString(HH:mm:ss); }); }如果需要畫實(shí)時(shí)曲線推薦用LiveCharts.Uwp把最近N秒的數(shù)據(jù)點(diǎn)放到ObservableCollection里曲線會(huì)自動(dòng)刷新。我自己項(xiàng)目里還接了一個(gè)歷史記錄列表每來一條數(shù)據(jù)就往ListView里塞一條方便事后回看。3.4 順帶跑通的BLE直連備用方案雖然華為手環(huán)直接BLE連PC不現(xiàn)實(shí)但這套UWP代碼我后來也沒白寫因?yàn)槲野阉迷诹藢I(yè)心率帶上。如果你以后遇到支持標(biāo)準(zhǔn)心率服務(wù)的設(shè)備可以參考下面這段掃描和讀取的流程。先用DeviceInformation.FindAllAsync掃描附近的BLE設(shè)備找到之后用BluetoothLEDevice.FromIdAsync連接再通過GetGattServiceAsync拿到心率服務(wù)訂閱心率特征的通知事件var device await BluetoothLEDevice.FromIdAsync(deviceId); var service await device.GetGattServiceAsync(GattServiceUuids.HeartRate); var characteristic service.GetCharacteristics(GattCharacteristicUuids.HeartRateMeasurement)[0]; characteristic.ValueChanged OnHeartRateChanged; await characteristic.WriteClientCharacteristicDescriptorAsync( GattClientCharacteristicConfigurationDescriptorValue.Notify);OnHeartRateChanged事件里會(huì)收到字節(jié)數(shù)組心率值就在其中。這個(gè)流程對(duì)標(biāo)準(zhǔn)BLE心率設(shè)備非??煽垦舆t幾乎是毫秒級(jí)。所以如果你的項(xiàng)目對(duì)實(shí)時(shí)性要求極高與其死磕華為手環(huán)不如直接用專業(yè)心率帶UWP端代碼幾乎不用改只是數(shù)據(jù)源換了一下。4. 常見問題與排查技巧實(shí)錄4.1 Health Kit授權(quán)與數(shù)據(jù)為空這個(gè)問題我遇到過好幾次而且表現(xiàn)很詭異應(yīng)用能正常啟動(dòng)也沒有報(bào)錯(cuò)但onReceiveData就是不回調(diào)或者回調(diào)里samplePoints始終是空的。排查時(shí)我建議按這個(gè)順序來。先確認(rèn)華為運(yùn)動(dòng)健康A(chǔ)pp里已經(jīng)綁定了手環(huán)并且首頁(yè)能看到當(dāng)前心率。再做一遍Health Kit授權(quán)確認(rèn)授權(quán)頁(yè)面里心率權(quán)限是勾選狀態(tài)。最后核對(duì)AGC后臺(tái)配置包名、證書指紋、Health Kit服務(wù)狀態(tài)是否全部一致。我那次就是證書指紋對(duì)不上重新配置之后數(shù)據(jù)就正常了。還有一個(gè)比較隱蔽的點(diǎn)如果手環(huán)和手機(jī)連接的是不同華為賬號(hào)Health Kit里也讀不到數(shù)據(jù)。手環(huán)綁定的賬號(hào)必須和App登錄的賬號(hào)一致。4.2 手機(jī)端網(wǎng)絡(luò)推送失敗手機(jī)端Socket推不出去優(yōu)先檢查三件事。第一手機(jī)和PC是否在同一個(gè)局域網(wǎng)有些辦公網(wǎng)絡(luò)會(huì)做AP隔離設(shè)備之間互相ping不通。第二PC防火墻是否放行了UWP應(yīng)用的入站連接Windows Defender防火墻默認(rèn)會(huì)攔截UWP應(yīng)用的入站請(qǐng)求需要手動(dòng)添加規(guī)則。第三Android端Socket連接PC時(shí)PC的IP地址要寫對(duì)而且建議先在PC上用其他工具驗(yàn)證一下9000端口確實(shí)在監(jiān)聽。另外提醒一點(diǎn)Android 9之后默認(rèn)禁止明文HTTP和Socket連接如果出現(xiàn)Cleartext communication not permitted的報(bào)錯(cuò)需要給應(yīng)用顯式允許明文流量或者使用加密方案。4.3 UWP端收不到數(shù)據(jù)或粘包解析錯(cuò)誤UWP端收不到數(shù)據(jù)我就干過一件蠢事代碼邏輯完全正確但忘記在Package.appxmanifest里聲明privateNetworkClientServer能力折騰了一下午。所以這個(gè)問題必須排在第一位排查。粘包問題在心率場(chǎng)景下不太明顯但如果發(fā)送頻率調(diào)高比如每秒多次還是會(huì)出現(xiàn)兩個(gè)JSON擠在一個(gè)TCP包里的情況。解決辦法就是我3.2節(jié)里提到的按行讀取和\n分隔符這個(gè)方法實(shí)測(cè)能穩(wěn)定解決粘包。還有一個(gè)編碼坑Android端發(fā)送時(shí)如果用了getBytes()默認(rèn)編碼中文注釋還好但JSON里的字段值如果包含特殊字符PC端解析就可能亂碼。最好兩端統(tǒng)一用UTF-8。4.4 實(shí)時(shí)性不達(dá)預(yù)期的處理思路如果你按上面的方案跑通之后發(fā)現(xiàn)數(shù)據(jù)更新頻率還是太低大概率是手環(huán)沒有處于運(yùn)動(dòng)模式。華為手環(huán)在普通待機(jī)狀態(tài)下心率上報(bào)頻率會(huì)被大幅降低Health Kit自然拿不到連續(xù)數(shù)據(jù)。解決辦法是讓用戶在華為運(yùn)動(dòng)健康A(chǔ)pp里開啟一個(gè)運(yùn)動(dòng)項(xiàng)目比如健走或者跑步心率上報(bào)頻率馬上就會(huì)上來。如果這樣還是不能滿足實(shí)時(shí)性要求那就說明華為手環(huán)本身的能力上限就到這了。消費(fèi)級(jí)手環(huán)的心率傳感器和算法本來就是為健康管理設(shè)計(jì)的不是為科研或者醫(yī)療場(chǎng)景準(zhǔn)備的。真要做秒級(jí)甚至毫秒級(jí)的實(shí)時(shí)心率應(yīng)用建議換支持標(biāo)準(zhǔn)BLE心率服務(wù)的專業(yè)心率帶代碼復(fù)用我3.4節(jié)的部分就行。5. 最后分享幾點(diǎn)經(jīng)驗(yàn)項(xiàng)目收尾之后我自己復(fù)盤了一下有幾個(gè)經(jīng)驗(yàn)值得單獨(dú)說。第一個(gè)經(jīng)驗(yàn)是先用假數(shù)據(jù)聯(lián)調(diào)UWP端再做Android端到PC端的真實(shí)鏈路。我一開始就直接上了真設(shè)備結(jié)果Android端連不上PC、UWP端解析報(bào)錯(cuò)、PC防火墻攔截三個(gè)問題同時(shí)冒出來排錯(cuò)排到懷疑人生。后來改成先用一個(gè)簡(jiǎn)單的Android模擬器或者命令行工具往9000端口發(fā)假數(shù)據(jù)UWP端調(diào)通之后再去解決手機(jī)端的問題效率高了很多。第二個(gè)經(jīng)驗(yàn)是華為Health Kit的權(quán)限審核周期比想象中長(zhǎng)而且審核通過之后SDK配置還有各種細(xì)節(jié)。建議項(xiàng)目一啟動(dòng)就先把開發(fā)者賬號(hào)、應(yīng)用創(chuàng)建、權(quán)限申請(qǐng)這些前置流程走起來不要等到代碼寫完了再申請(qǐng)不然只能在等待審核中干瞪眼。第三個(gè)經(jīng)驗(yàn)是架構(gòu)的價(jià)值大于單個(gè)設(shè)備。這套手機(jī)采集加局域網(wǎng)轉(zhuǎn)發(fā)的方案數(shù)據(jù)源只要換成蘋果的HealthKit、小米手環(huán)的開放平臺(tái)或者專業(yè)BLE心率帶UWP端幾乎不用動(dòng)。所以如果你后續(xù)有接入其他穿戴設(shè)備的需求這套架構(gòu)完全可以復(fù)用只需要新增一個(gè)適配器。最后再提一句華為手環(huán)的實(shí)時(shí)心率本質(zhì)上是“消費(fèi)級(jí)指標(biāo)”它更適合做趨勢(shì)展示、運(yùn)動(dòng)記錄這類場(chǎng)景。如果甲方的需求里寫著“實(shí)時(shí)”而且精度要求很高建議第一時(shí)間把預(yù)期拉回到合理范圍或者直接建議換硬件。技術(shù)方案能解決的問題很多但硬件能力的天花板還是要盡早說清楚。