前言
在國內(nèi)地圖使用最多的應(yīng)該是高德和百度,對(duì)于Flutter來說高德地圖在兩年前就有了比較成熟方便使用的三方庫。反觀百度地圖,去年上半年的時(shí)候開始推出Flutter版本,但是是手動(dòng)集成的,今年再來看已經(jīng)有了定位、地圖、鷹眼的Flutter版本,而且是通過pub管理的。今年有個(gè)項(xiàng)目只需要百度地圖的導(dǎo)航功能,但是官方恰恰就沒有提供,搜了一下網(wǎng)上好像也沒有相關(guān)的庫。所以就動(dòng)手自己封了一個(gè)簡單的百度導(dǎo)航插件,由于自身以前是iOS開發(fā),所以暫時(shí)只提供了iOS端的插件,Android方面待后續(xù)完善,也希望有此需求的Android開發(fā)能夠幫忙完善。
百度地圖導(dǎo)航iOS端開發(fā)
Swift嘗試
由于平時(shí)開發(fā)中主要是使用Swift,所以一開始創(chuàng)建Flutter插件的時(shí)候就選擇的Swift進(jìn)行開發(fā)。但是在這個(gè)過程中遇到一些問題,主要是庫引入相關(guān)。
- 使用pod方式引入BaiduNaviKit,即在podspec中添加導(dǎo)航pod包
s.dependency 'BaiduNaviKit'
問題:百度導(dǎo)航SDK在swift文件中無法通過import BaiduNaviKit的形式引入。在普通的Swift和OC混編工程中,可以通過橋接文件xxx-Bridging-Header.h引入,但是在pod開發(fā)中是不支持橋接文件的。
解決:嘗試通過 https://lingjye.com/2018/06/18/Component-problem/ 中提到的通過設(shè)置private_header_files的方式來充當(dāng)橋接文件
# podspec配置
s.private_header_files = 'Classes/BridgeHeader.h'
// 自定義的頭文件,嘗試充當(dāng)橋接文件,引入導(dǎo)航相關(guān)頭文件
#ifndef BridgeHeader_h
#define BridgeHeader_h
#import "BNaviService.h"
#import <BaiduMapAPI_Base/BMKBaseComponent.h>
#endif /* BridgeHeader_h */
但是pod install之后在umbrella.h中并沒有引入自定義的BridgeHeader.h文件,自然也就沒法使用。不知道哪里沒有設(shè)置正確,如果有pod配置方面比較熟悉的朋友,歡迎提出正確的解決方法。
- 使用手動(dòng)引入Framework的方式,如以下配置:
# BaiduNaviSDK/NaviSDK/inc/*.h 引入導(dǎo)航頭文件
s.source_files = 'Classes/**/*', 'BaiduNaviSDK/NaviSDK/inc/*.h'
# 引入導(dǎo)航相關(guān)的.a和.framework包
s.vendored_libraries = 'BaiduNaviSDK/**/*.a'
s.vendored_frameworks = 'BaiduNaviSDK/**/*.framework'
# 不使用pod依賴,注釋掉
# s.dependency 'BaiduNaviKit'
問題:報(bào)錯(cuò)Include of non-modular header inside framework module 'baidu_map.BridgeHeader,因?yàn)閷?dǎo)航依賴了百度地圖基礎(chǔ)庫,即在NaviSDK中的頭文件中引用了百度地圖的頭文件,如在BNaviService.h中引入了#import <BaiduMapAPI_Map/BMKMapView.h>就會(huì)報(bào)錯(cuò)。
解決:暫時(shí)不知道怎么解決,我猜只有百度官方更新更好的適配Swift才行。
期望:
- 官方的導(dǎo)航SDK支持import module的形式,可以讓Swift直接調(diào)用
- 官方更改目前.h與.a .framework混合的方式,導(dǎo)航SDK統(tǒng)一采用和地圖、定位一樣的純framework方式,滿足手動(dòng)配置的需求
轉(zhuǎn)用OC
因?yàn)樯鲜銮闆r,Swift并不能很方便的接入百度導(dǎo)航。想到OC可以直接引入其頭文件,省去了橋接、庫引用帶來的各種問題,固重新建了以O(shè)C為開發(fā)語言的Flutter插件。
接入方式選擇,pod和Framework手動(dòng)
因?yàn)镺C可以直接引用頭文件,所以直接選擇了pod引入的方式,目前指定了最新的6.2.0版本
s.dependency 'BaiduNaviKit', '6.2.0'
靜態(tài)庫配置問題
加入pod依賴install后,.podspec不做任何額外配置的情況下,還是遇到了一些報(bào)錯(cuò)(警告):
The ‘Pods-XXX‘ target has transitive dependencies that include statically linked binaries
這個(gè)錯(cuò)誤就是百度導(dǎo)航SDK里面用到的.a .framework是靜態(tài)庫的原因
Undefined symbols for architecture arm64
以上兩個(gè)問題可通過配置static_framework = true解決,即
s.static_framework = true
百度導(dǎo)航語音問題
解決庫的引入依賴問題后,參考百度官方的文檔編寫導(dǎo)航相關(guān)的代碼,使用正確的地圖應(yīng)用AppKey授權(quán)導(dǎo)航,即可以正常的在應(yīng)用內(nèi)使用導(dǎo)航功能,授權(quán)代碼調(diào)用authorizeNaviAppKey。
但是會(huì)發(fā)現(xiàn)導(dǎo)航?jīng)]有聲音,及時(shí)我在百度AI開放平臺(tái)注冊(cè)了和地圖BundleID一樣的應(yīng)用,同時(shí)也參考官方文檔申請(qǐng)了免費(fèi)額度的語音合成服務(wù)。在導(dǎo)航初始化時(shí)調(diào)用TTS授權(quán)代碼,其回調(diào)的結(jié)果永遠(yuǎn)是NO不成功。
[[BNaviService getInstance] authorizeTTSAppId:ttsAppId apiKey:ttsApiKey secretKey:ttsSecretKey completion:^(BOOL ttsResult) {
// ttsResult結(jié)果為NO,使用的AI開放平臺(tái)正確的Key
NSLog(@"authorizeTTS ret = %d",ttsResult);
}];
百度官方的文檔比較老了,語音播報(bào)模塊TTS授權(quán)管理還是http://yuyin.baidu.com/app, 跳轉(zhuǎn)后是到AI開放平臺(tái),其使用說明和文檔的出入還是比較大?,F(xiàn)在的AI開放平臺(tái)功能更復(fù)雜,很多也需要付費(fèi)。我申請(qǐng)了語音模塊,沒有付費(fèi),申請(qǐng)了免費(fèi)額度,不知道是不是這個(gè)原因造成的沒有聲音。為了這個(gè)問題,專門提了工單,建議更新下文檔,提供下解決方案。官方答復(fù)檢查Product Name是否為英文,我檢查了工程是沒問題的。
解決:使用內(nèi)置的TTS語音播報(bào)功能
下載了官方的導(dǎo)航demo,使用自己的BundleID和相關(guān)的Key包括TTS相關(guān)Key,運(yùn)行起來是有導(dǎo)航聲音的,但是斷點(diǎn)查看TTS授權(quán)代碼回調(diào)結(jié)果依舊是NO。
對(duì)比了下兩者的區(qū)別,官方demo是采用手動(dòng)配置SDK的方式,對(duì)比發(fā)現(xiàn)其使用了帶TTS相關(guān)的靜態(tài)庫和資源文件,所以基本可以猜測(cè)是使用的內(nèi)置TTS語音播報(bào)。再去仔細(xì)看了下官方的文檔,第一句話就說明了:使用SDK內(nèi)置百度TTS語音播報(bào)功能需要導(dǎo)入libBNTTSComponentSDK.a靜態(tài)庫,并且需要對(duì)應(yīng)用進(jìn)行授權(quán)驗(yàn)證才能夠使用,因此需要主動(dòng)注冊(cè)應(yīng)用相關(guān)信息。
注:使用內(nèi)置的TTS播報(bào)功能,必須導(dǎo)入libBNTTSComponentSDK.a靜態(tài)庫和baiduTTSSDK.bundle資源文件。而且必須要注冊(cè)TTS相關(guān)的Key,一個(gè)很奇怪的現(xiàn)象是:使用了Key調(diào)用TTS授權(quán)代碼,回調(diào)仍然是NO不成功,但是會(huì)有導(dǎo)航聲音,如果不傳任何的Key,就會(huì)沒有導(dǎo)航聲音。而且我iOS申請(qǐng)的TTS Key拿給Android用也能生效,導(dǎo)航有聲音(包名不同)。所以對(duì)TTS授權(quán)這些Key的實(shí)際使用和相關(guān)準(zhǔn)確配置現(xiàn)在都還有一個(gè)問號(hào)?,F(xiàn)在能保證的是傳入申請(qǐng)的Key,使用內(nèi)置語音奏效!
插件中配置聲音
更新:直接使用pod配置TTS即可,在podspect中添加:
s.dependency 'BaiduNaviKit/TTS'
再次看了下百度導(dǎo)航官方的文檔,發(fā)現(xiàn)TTS是支持pod依賴的,因?yàn)槭亲⑨尩袅怂圆黄鹧郛?dāng)時(shí)沒有看到??,還是自己不夠仔細(xì)啊,走了那么多的彎路
插件中是使用pod的方式接入的導(dǎo)航SDK,里面是沒有內(nèi)置TTS相關(guān)靜態(tài)庫和資源文件的,于是就想到了直接將這兩個(gè)聲音依賴文件引入。將官方下載的libBNTTSComponentSDK.a和baiduTTSSDK.bundle拷貝到了插件開發(fā)目錄中(新建了Libs和Resources文件夾存放),并配置了.podspec文件如下:
# pod配置了TTS,手動(dòng)添加的靜態(tài)庫和資源文件就不需要了,刪除即可
# 配置靜態(tài)庫,此處只有l(wèi)ibBNTTSComponentSDK.a
# s.vendored_libraries = 'Libs/*.a'
# 配置資源文件,此處只有baiduTTSSDK.bundle
# s.resource = ['Resources/*.bundle']
至此,導(dǎo)航有聲音了,整個(gè)插件開發(fā)流程也就圓滿了,再次附上關(guān)鍵的.podspec配置:
# 更新為pod依賴
s.dependency 'BaiduNaviKit', '6.2.0'
s.dependency 'BaiduNaviKit/TTS', '6.2.0'
s.static_framework = true
# s.vendored_libraries = 'Libs/*.a' #刪除
# s.resource = ['Resources/*.bundle'] #刪除
開啟Background Modes -> Location updates
注意:根據(jù)官方文檔,在接入自己的應(yīng)用時(shí)不要忘了在Signing&Capabilities中添加Background Mode,選中Location updates,導(dǎo)航是需要后臺(tái)播放更新的,使用過導(dǎo)航的都應(yīng)該知道這個(gè)常識(shí)。如果不設(shè)置的話開始導(dǎo)航時(shí)會(huì)崩潰!
使用
添加依賴
因?yàn)锳ndroid端的功能尚未開發(fā),固還未發(fā)布到pub,目前可使用git source依賴的方式,在pubspec.yaml中配置:
flutter_baidu_navi:
git: https://github.com/Smiacter/flutter_baidu_navi.git
或下載源碼到本地,使用本地依賴插件的方式,在pubspec.yaml中配置插件目錄路徑
flutter_baidu_navi:
path: ../plugin/flutter_baidu_navi # 路勁根據(jù)自己的存放目錄而定
初始化百度地圖定位
/// 在適當(dāng)位置調(diào)用初始化,由于使用內(nèi)置TTS,返回mapSuccess即可
/// 參數(shù)依次為百度地圖AppKey,TTS的AppId、ApiKey、SecretKey
/// TTS相關(guān)的Key為可選參數(shù),如果不傳默認(rèn)為空,導(dǎo)航?jīng)]聲音
FlutterBaiduNavi.init(
"2GF3O2BTHxYHlMnoEuSFvyLo6c0xBn1h",
ttsAppId: "24147217",
ttsApiKey: "vewI9TV5VQRoOGPypStObVL3",
ttsSecretKey: "QaFknadW5sPlNEicIcrdFV4VeGG0KZvo",
);
初始化返回結(jié)果為BaiduNaviInitResult, 具體可查看源碼
/// 百度導(dǎo)航初始化結(jié)果
enum BaiduNaviInitResult {
/// 導(dǎo)航Appkey和TTS授權(quán)均成功
success,
/// 導(dǎo)航Appkey和TTS授權(quán)均失敗【導(dǎo)航AppKey失敗導(dǎo)航一定會(huì)失敗,且會(huì)彈出未授權(quán)提示框,所以請(qǐng)確保地圖應(yīng)用的AppKey準(zhǔn)確無誤】
fail,
/// 導(dǎo)航AppKey成功,TTS失敗
/// 注:實(shí)踐證明,使用內(nèi)置的TTS語音,有TTS相關(guān)Key,及時(shí)使用這些KeyTTS授權(quán)失敗也會(huì)有導(dǎo)航聲音,而且給包名完全不一樣的Android使用也行!
/// 如果TTS相關(guān)的Key為空或者不是AI平臺(tái)申請(qǐng)的Key,導(dǎo)航就很有可能沒有聲音
mapSuccessTtsFail,
/// 導(dǎo)航AppKey失敗,TTS成功【導(dǎo)航AppKey失敗導(dǎo)航一定會(huì)失敗,且會(huì)彈出未授權(quán)提示框,所以請(qǐng)確保地圖應(yīng)用的AppKey準(zhǔn)確無誤】
mapFailTtsSuccess,
}
開始導(dǎo)航
/// 開始導(dǎo)航,參數(shù)依次為起點(diǎn)經(jīng)度、起點(diǎn)緯度、終點(diǎn)經(jīng)度、終點(diǎn)緯度
FlutterBaiduNavi.startNavi(
104.078063,
30.66664,
104.122785,
30.727933,
);
發(fā)起導(dǎo)航返回結(jié)果為BaiduNaviResult, 具體可查看源碼
/// 百度導(dǎo)航結(jié)果
/// 注:選取了一些常見的錯(cuò)誤,如需其他錯(cuò)誤類型,需更改插件代碼返回相應(yīng)的值
enum BaiduNaviResult {
/// 成功開啟導(dǎo)航
success,
/// 定位權(quán)限未開啟或受限
locationUnauthorized,
/// 導(dǎo)航服務(wù)未初始化完成
naviServiceNotInited,
/// 網(wǎng)絡(luò)不可用
networkError,
/// 起點(diǎn)與終點(diǎn)距離太近
tooNear,
/// 其他原因-導(dǎo)航失敗
otherError,
}
TODO:
- Android端功能開發(fā)
- 最終完善,發(fā)布到pub
結(jié)尾
以上純粹是個(gè)人的一些嘗試與感悟,并沒有對(duì)百度地圖,pod開發(fā)與配置有太深入的研究,如有什么不對(duì)的地方,還請(qǐng)指出,我下來再專研專研。感謝!附上項(xiàng)目GitHub地址,也歡迎提issue和需求。