圖片來自百度官網
前言
最近更新項目中用的百度定位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獲取
-rfc 以 RFC 樣式輸出
-alias <alias> 要處理的條目的別名
-keystore <keystore> 密鑰庫名稱
-storepass <arg> 密鑰庫口令
-storetype <storetype> 密鑰庫類型
-providername <providername> 提供方名稱
-providerclass <providerclass> 提供方類名
-providerarg <arg> 提供方參數
-providerpath <pathlist> 提供方類路徑
-v 詳細輸出
-protected 通過受保護的機制的口令
上面是獲取密鑰庫信息的一些命令,則在此獲取SHA1可以
keytool -v -list -keystore 【密鑰庫文件路徑】 -storepass 【密鑰庫文件密碼】
在Terminal執行命令后就出現上面的詳細信息。SHA1后面的那一串字符就是我們需要的SHA1.
CMD方式
如果要在CMD中獲取,必須先要設置環境變量,具體設置方式可谷歌搜索。當然獲取的命令和在AndroidStudio中獲取是一樣的。在上面我獲取下開發版SHA1。對于debug版一般存用戶下的.android目錄下,我們打開CMD后執行 cd .android然后通過dir就可以看到目錄下會有一個debug.keystore文件,我們找的就是它。
在圖中你會看到沒有寫-storepass參數(當然也可和上面一樣)。在回車后會提示輸入密鑰庫口令,對于我們的debug版本口令默認是android,輸入后回車即可看到詳細信息了。
環境配置
要想實現定位,我們必須在清單文件中加入一些必要的權限以及key等信息,如下
<!--百度定位權限相關-->
<!-- 這個權限用于進行網絡定位-->
<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
/**
* 獲取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());
}
}
設置定位參數
/***
* 配置參數
*
* @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接口
/*****
* 定位結果回調,重寫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 < location.getPoiList().size(); i++) {
Poi poi = (Poi) location.getPoiList().get(i);
sb.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。如果要寫的項目里也要把回調接口封裝,自定義一個接口回調返回定位后的詳細位置信息。到這里即可成功定位了,下面就開始介紹下這個過程會出現的問題。
定位問題分析
在分析之前我們先看下百度定位返回的錯誤碼,分析定位的問題也就是分析出現錯誤碼的原因。
獲取定位返回錯誤碼::
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文件加入下面代碼即可
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
其實最多出現的問題也就是這兩種情況。正常情況下百度定位成功返回的是161也就是網絡定位結果(如上圖),但是有時候無網絡會返回66機離線定位結果,離線定位是小區定位,需要手機手機中有SIM卡,否則不會返回66,你可以嘗試下,把手機調到飛行模式,發現離線定位會失敗。百度定位默認GPS定位是關閉的,如果想用GPS定位可以通過下面代碼打開,
locationClientOption.setOpenGps(true);
BDLocationListener只回調一次
對于很多剛接觸定位的人可能還會遇到一個問題就是,為何多次調用start()方法但是BDLocationListener回調只執行一次。每次只要程序剛啟動時才能定位成功。之后再定位就沒有反應了。如果你第一次遇到這個問題,確實很棘手,不管怎么改定位相關的代碼,并不能解決問題。其實此時只需要在清單文件加入下面代碼既可以解決BDLocationListener只會回調一次的問題
<service
android:name="com.baidu.location.f"
android:enabled="true"
android:process=":remote" >
</service>
好了,到此,本篇文章真的結束了,若文章有不足或者錯誤的地方,歡迎指正,以防止給其他讀者錯誤引導