原始碼傳送門【若你不小心點選進入GitHub了捎帶給個star( ^_^ )】
前言
最近更新專案中用的百度定位SDK時遇見了一個奇葩的問題。當升級SDK後百度定位一直返回505,通過百度定位官網檢視該碼錶示AK非法或者不存在。很糾結,於是自己又寫了一個demo來研究一下百度定位以及大家使用百度定位經常出現的問題,特此記錄。這篇文章我先將百度定位的實現也介紹一下,最後再分析遇到的問題及解決方案。
定位分析
目前百度定位提供了WIFI,基站,GPS等多種定位方式,適用於室內、室外多種定位場景,具有出色的定位效能:定位精度高(其實我是想吐槽的)、覆蓋率廣、網路定位請求流量小、定位速度快。
整合定位SDK
現在官網提供的最新的定位SDK版本是v7.0,官網SDK下載地址請戳 定位SDK,可根據自己的需要下載,在這裡我進入全部下載,只下載了全量定位。在新版本V7.0中百度將定位對開發包實現了分離
(1)基礎定位:開發包體積最小,但只包含基礎定位能力(GPS/WiFi/基站)、基礎位置描述能力;
(2)離線定位:在基礎定位能力基礎之上,提供離線定位能力,可在網路環境不佳時,進行精準定位;
(3)室內定位:在基礎定位能力基礎之上,提供室內高精度定位能力,精度可達1-3米;
(4)全量定位:包含離線定位、室內高精度定位能力,同時提供更人性化的位置描述服務;
對於這四種型別定位開發包是互斥的,一個應用中只需整合一種定位開發包即可。下載成功之後,將jar包和.so檔案放到對應的檔案下即可。
申請祕鑰
使用百度定位,我們需要在官網申請一個AK,專案定位時需要使用這個Ak,一個應用對於一個AK,AK申請時需要提供包名及SHA1值。具體方式
可去官網檢視。在這裡我簡單介紹下SHA1獲取方式。在申請Ak時,頁面填寫釋出版SHA1和開發版SHA1。下面我提供兩種方式獲取SHA1值。
AndroidStudio Terminal獲取
1 2 3 4 5 6 7 8 9 10 11 |
-rfc 以 RFC 樣式輸出 -alias <alias> 要處理的條目的別名 -keystore <keystore> 金鑰庫名稱 -storepass <arg> 金鑰庫口令 -storetype <storetype> 金鑰庫型別 -providername <providername> 提供方名稱 -providerclass <providerclass> 提供方類名 -providerarg <arg> 提供方引數 -providerpath <pathlist> 提供方類路徑 -v 詳細輸出 -protected 通過受保護的機制的口令 |
上面是獲取金鑰庫資訊的一些命令,則在此獲取SHA1可以
1 |
keytool -v -list -keystore 【金鑰庫檔案路徑】 -storepass 【金鑰庫檔案密碼】 |
在Terminal執行命令後就出現上面的詳細資訊。SHA1後面的那一串字元就是我們需要的SHA1.
CMD方式
如果要在CMD中獲取,必須先要設定環境變數,具體設定方式可谷歌搜尋。當然獲取的命令和在AndroidStudio中獲取是一樣的。在上面我獲取下開發版SHA1。對於debug版一般存使用者下的.android目錄下,我們開啟CMD後執行 cd .android然後通過dir就可以看到目錄下會有一個debug.keystore檔案,我們找的就是它。
在圖中你會看到沒有寫-storepass引數(當然也可和上面一樣)。在回車後會提示輸入金鑰庫口令,對於我們的debug版本口令預設是android,輸入後回車即可看到詳細資訊了。
環境配置
要想實現定位,我們必須在清單檔案中加入一些必要的許可權以及key等資訊,如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
<!--百度定位許可權相關--> <!-- 這個許可權用於進行網路定位--> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"></uses-permission> <!-- 這個許可權用於訪問GPS定位--> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"></uses-permission> <!-- 用於訪問wifi網路資訊,wifi資訊會用於進行網路定位--> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"></uses-permission> <!-- 獲取運營商資訊,用於支援提供運營商資訊相關的介面--> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"></uses-permission> <!-- 這個許可權用於獲取wifi的獲取許可權,wifi資訊會用來進行網路定位--> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"></uses-permission> <!-- 用於讀取手機當前的狀態--> <uses-permission android:name="android.permission.READ_PHONE_STATE"></uses-permission> <!-- 寫入擴充套件儲存,向擴充套件卡寫入資料,用於寫入離線定位資料--> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission> <!-- 訪問網路,網路定位需要上網--> <uses-permission android:name="android.permission.INTERNET" /> <!-- SD卡讀取許可權,使用者寫入離線定位資料--> <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"> </uses-permission> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <service android:name="com.baidu.location.f" android:enabled="true" android:process=":remote" > </service> <meta-data android:name="com.baidu.lbsapi.API_KEY" android:value="w7NQOKL8SpxHrs6lixBNoe90" /> </application> |
定位實現
對於定位的實現我們可以分為三步,第一步:初始化LocationClient;第二步:通過LocationClientOption設定定位引數;第三步:實現BDLocationListener介面。看著是不是很簡單,你沒看錯,確實很簡單。
初始化LocationClient
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
/** * 獲取LocationService例項 * * @param context * @return */ public static LocationService getInstance(Context context) { if (locationClient == null) { synchronized (LocationService.class) { locationService= new LocationService(context); } } return locationService; } private LocationService(Context context) { if (locationClient == null) { locationClient = new LocationClient(context); locationClient.setLocOption(getDefaultLocationClientOption()); } } |
設定定位引數
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
/*** * 配置引數 * * @return DefaultLocationClientOption */ public LocationClientOption getDefaultLocationClientOption() { if (locationClientOption == null) { locationClientOption = new LocationClientOption(); locationClientOption.setLocationMode(LocationClientOption.LocationMode.Hight_Accuracy);//可選,預設高精度,設定定位模式,高精度,低功耗,僅裝置 locationClientOption.setCoorType("bd09ll");//可選,預設gcj02,設定返回的定位結果座標系,如果配合百度地圖使用,建議設定為bd09ll; locationClientOption.setScanSpan(3000);//可選,預設0,即僅定位一次,設定發起定位請求的間隔需要大於等於1000ms才是有效的 locationClientOption.setIsNeedAddress(true);//可選,設定是否需要地址資訊,預設不需要 locationClientOption.setIsNeedLocationDescribe(true);//可選,設定是否需要地址描述 locationClientOption.setNeedDeviceDirect(true);//可選,設定是否需要裝置方向結果 locationClientOption.setLocationNotify(true);//可選,預設false,設定是否當gps有效時按照1S1次頻率輸出GPS結果 locationClientOption.setIgnoreKillProcess(true);//可選,預設true,定位SDK內部是一個SERVICE,並放到了獨立程式,設定是否在stop的時候殺死這個程式,預設不殺死 locationClientOption.setIsNeedLocationDescribe(true);//可選,預設false,設定是否需要位置語義化結果,可以在BDLocation.getLocationDescribe裡得到,結果類似於“在北京天安門附近” locationClientOption.setIsNeedLocationPoiList(true);//可選,預設false,設定是否需要POI結果,可以在BDLocation.getPoiList裡得到 locationClientOption.SetIgnoreCacheException(false);//可選,預設false,設定是否收集CRASH資訊,預設收集 locationClientOption.setIsNeedAltitude(false);//可選,預設false,設定定位時是否需要海拔資訊,預設不需要,除基礎定位版本都可用 } return locationClientOption; } |
實現BDLocationListener介面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
/***** * 定位結果回撥,重寫onReceiveLocation方法 * */ private BDLocationListener mListener = new BDLocationListener() { @Override public void onReceiveLocation(BDLocation location) { // TODO Auto-generated method stub if (null != location ) { StringBuffer sb = new StringBuffer(256); sb.append("time : "); /** * 時間也可以使用systemClock.elapsedRealtime()方法 獲取的是自從開機以來,每次回撥的時間; * location.getTime() 是指服務端出本次結果的時間,如果位置不發生變化,則時間不變 */ sb.append(location.getTime()); sb.append("\nlocType : ");// 定位型別 sb.append(location.getLocType()); sb.append("\nlocType description : ");// *****對應的定位型別說明***** //sb.append(location.getLocTypeDescription()); sb.append("\nlatitude : ");// 緯度 sb.append(location.getLatitude()); sb.append("\nlontitude : ");// 經度 sb.append(location.getLongitude()); sb.append("\nradius : ");// 半徑 sb.append(location.getRadius()); sb.append("\nCountryCode : ");// 國家碼 sb.append(location.getCountryCode()); sb.append("\nCountry : ");// 國家名稱 sb.append(location.getCountry()); sb.append("\ncitycode : ");// 城市編碼 sb.append(location.getCityCode()); sb.append("\ncity : ");// 城市 sb.append(location.getCity()); sb.append("\nDistrict : ");// 區 sb.append(location.getDistrict()); sb.append("\nStreet : ");// 街道 sb.append(location.getStreet()); sb.append("\naddr : ");// 地址資訊 sb.append(location.getAddrStr()); sb.append("\nUserIndoorState: ");// *****返回使用者室內外判斷結果***** //sb.append(location.getUserIndoorState()); sb.append("\nDirection(not all devices have value): "); sb.append(location.getDirection());// 方向 sb.append("\nlocationdescribe: "); sb.append(location.getLocationDescribe());// 位置語義化資訊 sb.append("\nPoi: ");// POI資訊 if (location.getPoiList() != null && !location.getPoiList().isEmpty()) { for (int i = 0; i append(poi.getName() + ";"); } } if (location.getLocType() == BDLocation.TypeGpsLocation) {// GPS定位結果 sb.append("\nspeed : "); sb.append(location.getSpeed());// 速度 單位:km/h sb.append("\nsatellite : "); sb.append(location.getSatelliteNumber());// 衛星數目 sb.append("\nheight : "); sb.append(location.getAltitude());// 海拔高度 單位:米 sb.append("\ngps status : "); //sb.append(location.getGpsAccuracyStatus());// *****gps質量判斷***** sb.append("\ndescribe : "); sb.append("gps定位成功"); } else if (location.getLocType() == BDLocation.TypeNetWorkLocation) {// 網路定位結果 // 運營商資訊 if (location.hasAltitude()) {// *****如果有海拔高度***** sb.append("\nheight : "); sb.append(location.getAltitude());// 單位:米 } sb.append("\noperationers : ");// 運營商資訊 sb.append(location.getOperators()); sb.append("\ndescribe : "); sb.append("網路定位成功"); } else if (location.getLocType() == BDLocation.TypeOffLineLocation) {// 離線定位結果 sb.append("\ndescribe : "); sb.append("離線定位成功,離線定位結果也是有效的"); } else if (location.getLocType() == BDLocation.TypeServerError) { sb.append("\ndescribe : "); sb.append("服務端網路定位失敗,可以反饋IMEI號和大體定位時間到loc-bugs@baidu.com,會有人追查原因"); } else if (location.getLocType() == BDLocation.TypeNetWorkException) { sb.append("\ndescribe : "); sb.append("網路不同導致定位失敗,請檢查網路是否通暢"); } else if (location.getLocType() == BDLocation.TypeCriteriaException) { sb.append("\ndescribe : "); sb.append("無法獲取有效定位依據導致定位失敗,一般是由於手機的原因,處於飛航模式下一般會造成這種結果,可以試著重啟手機"); } tv_location.setText(sb+"\n定位結束"); locationService.stop(); }else{ tv_location.setText("\n定位失敗"); } } }; |
通過上面的實現後,我們在想要定位的地方註冊下回撥,並呼叫start()方法即可以獲取位置了,我對註冊開始暫停做了下簡單封裝,具體程式碼參考LocationService。如果要寫的專案裡也要把回撥介面封裝,自定義一個介面回撥返回定位後的詳細位置資訊。到這裡即可成功定位了,下面就開始介紹下這個過程會出現的問題。
定位問題分析
在分析之前我們先看下百度定位返回的錯誤碼,分析定位的問題也就是分析出現錯誤碼的原因。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
獲取定位返回錯誤碼:: public int getLocType ( ) 返回值: 61 : GPS定位結果,GPS定位成功。 62 : 無法獲取有效定位依據,定位失敗,請檢查運營商網路或者WiFi網路是否正常開啟,嘗試重新請求定位。 63 : 網路異常,沒有成功向伺服器發起請求,請確認當前測試手機網路是否通暢,嘗試重新請求定位。 65 : 定位快取的結果。 66 : 離線定位結果。通過requestOfflineLocaiton呼叫時對應的返回結果。 67 : 離線定位失敗。通過requestOfflineLocaiton呼叫時對應的返回結果。 68 : 網路連線失敗時,查詢本地離線定位時對應的返回結果。 161: 網路定位結果,網路定位成功。 162: 請求串密文解析失敗,一般是由於客戶端SO檔案載入失敗造成,請嚴格參照開發指南或demo開發,放入對應SO檔案。 167: 服務端定位失敗,請您檢查是否禁用獲取位置資訊許可權,嘗試重新請求定位。 502: AK引數錯誤,請按照說明文件重新申請AK。 505:AK不存在或者非法,請按照說明文件重新申請AK。 601: AK服務被開發者自己禁用,請按照說明文件重新申請AK。 602: key mcode不匹配,您的AK配置過程中安全碼設定有問題,請確保:SHA1正確,“;”分號是英文狀態;且包名是您當前執行應用的包名,請按照說明文件重新申請AK。 501~700:AK驗證失敗,請按照說明文件重新申請AK。 |
其實知道上面錯誤碼代表的含義後,我們就很快速的定位問題出現地方。當然有些時候不如此,可能需要走一些彎路。
505錯誤
在我升級定位SDK版本後遇到得到就是這個問題,沒有更改任何程式碼但是就是一直返回錯誤碼是505.通過上面錯誤碼錶我們看到時AK不存在或者非法,但是依然很糾結,因為程式碼時點兒也沒有改,只是替換了jar和.so檔案為最新版就不能用了。定位一直返回505,最後在官網更新日誌看到V7.0版本有一條記錄 是優化、完善AK校驗機制,充分保證開發者合法權益,保證開發者應用的安全性。具體怎麼優化並沒有說明。不過也能猜測應該是SHA1的值問題。我先將demo用的定位SDK用V6.2.2(專案中用的此版本),然後更改SHA1的值,不管怎麼改依然能成功定位。但是更改為了V7.0版本發現SHA1的值並不能隨便改,只能是執行程式用的key檔案的SHA1的值,否則就出現505錯誤。至此問題解決。在V7.0之前版本雖說讓填寫SHA1的值,但是並沒有什麼有效作用,在V7.0版本開始加入了嚴格的校驗。在這裡提供一個軟體可以校驗APK的SHA1值,他提供了SHA1的和AK的校驗功能。
如上圖,這上面顯示的SHA1的值應該和你開發版或者釋出版中至少其中的一個相同。否則V7.0定位就不會成功。校驗工具百度網盤下載連結,提取碼:je4r。
162錯誤
162錯誤一般是.so檔案載入失敗引起的。在AndroidStudio中.so檔案的位置和Eclipse中的是不一樣的。預設情況下,AndroidStudio中.so檔案放在main目錄下,在該資料夾下建立jniLibs,然後將不同核心的.so檔案放到該資料夾下就可以了。當然一些人延續了Eclipse位置,將.so檔案放置在libs目錄下,如果此時沒有其他一些配置.so檔案是不能載入的。此時再gradle檔案加入下面程式碼即可
1 2 3 4 5 |
sourceSets { main { jniLibs.srcDirs = ['libs'] } } |
其實最多出現的問題也就是這兩種情況。正常情況下百度定位成功返回的是161也就是網路定位結果(如上圖),但是有時候無網路會返回66機離線定位結果,離線定位是小區定位,需要手機手機中有SIM卡,否則不會返回66,你可以嘗試下,把手機調到飛航模式,發現離線定位會失敗。百度定位預設GPS定位是關閉的,如果想用GPS定位可以通過下面程式碼開啟,
1 |
locationClientOption.setOpenGps(true); |
BDLocationListener只回撥一次
對於很多剛接觸定位的人可能還會遇到一個問題就是,為何多次呼叫start()方法但是BDLocationListener回撥只執行一次。每次只要程式剛啟動時才能定位成功。之後再定位就沒有反應了。如果你第一次遇到這個問題,確實很棘手,不管怎麼改定位相關的程式碼,並不能解決問題。其實此時只需要在清單檔案加入下面程式碼既可以解決BDLocationListener只會回撥一次的問題
1 2 3 4 5 |
<service android:name="com.baidu.location.f" android:enabled="true" android:process=":remote" > </service> |
好了,到此,本篇文章真的結束了,若文章有不足或者錯誤的地方,歡迎指正,以防止給其他讀者錯誤引導
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!
任選一種支付方式