首先告訴大家:我的項目功能包含實時定位,實時軌跡繪制,運動距離展示,縮放自動調整等,基本遇到的bug都被我解決了。大家有問題下面直接問,互相學習(2017.08.08add)!
首先進入百度地圖開發者中心,找到鷹眼軌跡。按照開發指南首先申請密鑰,即獲取百度要求的sha1值,在Android studio控制台G:\android2016\LBSdemo>下輸入keytool -list -v -keystore D:\key\lbsdemo.jks提示'keytool' 不是內部或外部命令,也不是可運行的程序。可能是我打開方式不對,於是我到命令行窗口在Java的安裝目錄下找到keytool命令,
那么按照提示輸入
其中命令-keystore后的目錄是Android Studio在打包時開發者設置的key的路徑,接着會讓你輸入當時設置的密碼,密碼輸入時不顯示,回車即可。http://lbsyun.baidu.com/index.php?title=android-yingyan/guide/key此處有官方圖文引導。
下一步配置工程,下載庫文件解壓后將庫粘到libs文件夾下,並在app的build gradle 添加
sourceSets { main { jniLibs.srcDir 'libs' } }
接下來配置清單文件:(直接從官網上粘)
1、在Application標簽中聲明SERVICE組件,每個APP擁有自己獨立的鷹眼追蹤service
<service android:name="com.baidu.trace.LBSTraceService" android:enabled="true" android:process=":remote"> </service>
2、聲明使用權限:
<!-- 這個權限用於進行網絡定位--> <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> <!-- 寫入擴展存儲,向擴展卡寫入數據,用於寫入對象存儲BOS數據--> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission> <!-- 訪問網絡,網絡定位需要上網--> <uses-permission android:name="android.permission.INTERNET" /> <!-- SD卡讀取權限,用於寫入對象存儲BOS數據--> <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"></uses-permission> <!-- 用於加快GPS首次定位--> <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"></uses-permission> <!-- 用於Android M及以上系統,申請加入忽略電池優化白名單--> <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"></uses-permission>
在Mainfest.xml正確設置AccessKey(ak),如果設置錯誤將會導致鷹眼服務無法正常使用。需在Application標簽中加入以下代碼,並填入開發者自己的 Android 類型 ak。meta-data與activity同一級。
AK就是剛剛拿SHA1申請的那個字符串。
<meta-data android:name="com.baidu.lbsapi.API_KEY" android:value="AK" /> //key:開發者申請的Key
import相關類:
import com.baidu.trace.Trace; import com.baidu.trace.LBSTraceClient; import com.baidu.trace.model.OnCustomAttributeListener; import com.baidu.trace.model.OnTraceListener; import com.baidu.trace.api.track.OnTrackListener; import com.baidu.trace.api.fence.OnFenceListener; import com.baidu.trace.api.entity.OnEntityListener; import com.baidu.trace.api.analysis.OnAnalysisListener; import com.baidu.trace.api.bos.OnBosListener;
以上就是官網上配置工程一節的介紹。
結果,震驚的是,官方demo竟然調不起來,看Log
看了論壇的帖子,才意識到我上面獲取的是發布版本的SHA1,而調試的時候應該用debug的SHA1,那我想的對不對試試就知道了
deblug的SHA1:B2:31:7E:C6:53:FE:4A:1B:E6:C8:B0:24:F5:0A:F5:E4:07:60:A6:BA
release的SHA1:02:D4:70:1E:FD:31:AA:C0:E9:8B:05:E7:DF:0C:DC:99:5D:84:4A:0A
那拿着這個字符串再去百度申請ak,結果,我沉默了,又走了彎路,還是沒調起來。
我真是醉了,我在命令行拿到的SHA1竟然和論壇下載的工具獲得的SHA1不一樣。日狗了。換了SHA1重新設置一下調起來了。接下來就是分析demo移植了。
第二天咱們來分析demo中是如何獲得 手機位置信息並繪制軌跡曲線的。
在TrackApplication中的OnCreate(),
1 @Override 2 public void onCreate() { 3 super.onCreate(); 4 mContext = getApplicationContext(); 5 entityName = CommonUtil.getImei(this); 6 // 若為創建獨立進程,則不初始化成員變量 7 if ("com.baidu.track:remote".equals(CommonUtil.getCurProcessName(mContext))) { 8 return; 9 } 10 SDKInitializer.initialize(mContext); 11 initView(); 12 initNotification(); 13 mClient = new LBSTraceClient(mContext); 14 mTrace = new Trace(serviceId, entityName); 15 mTrace.setNotification(notification); 16 trackConf = getSharedPreferences("track_conf", MODE_PRIVATE); 17 locRequest = new LocRequest(serviceId); 18 mClient.setOnCustomAttributeListener(new OnCustomAttributeListener() { 19 @Override 20 public Map<String, String> onTrackAttributeCallback() { 21 Map<String, String> map = new HashMap<>(); 22 map.put("key1", "value1"); 23 map.put("key2", "value2"); 24 return map; 25 } 26 }); 27 clearTraceStatus(); 28 }
做了一部分初始化的工作:entityName是設備的IMEI號,在通知欄添加了應用通知Notification告知用戶服務正在運行,實例化軌跡客戶端LBSTraceClient、軌跡服務Trace,Trace有多個構造方法,並且給軌跡服務設置Notification。創建SharedPreference來存儲軌跡服務的配置信息。實例化定位請求LocRequest,mClient.setOnCustomAttributeListener()是為了自定義參數,官方解釋是:
為實現自定義屬性數據上傳,開發者須重寫OnCustomAttributeListener監聽器中的onTrackAttributeCallback()接口,調用 LBSTraceClient.setOnCustomAttributeListener()方法設置自定義屬性監聽器,並按照設置的定位周期更新onTrackAttributeCallback()的返回值。SDK每采集一次軌跡,便會自動回調onTrackAttributeCallback()接口,獲取屬性值並寫入當前軌跡點的屬性字段中。自定義屬性監聽器需通過LBSTraceClient.setOnCustomAttributeListener()進行設置,LBSTraceService只回調最新設置的自定義監聽器。onTrackAttributeCallback()的返回值是Map<String, String>類型,每個對象都是一個<key,value>對,其中key為entity的自定義字段名稱,value為值。
clearTraceStatus():
/** * 清除Trace狀態:初始化app時,判斷上次是正常停止服務還是強制殺死進程,根據trackConf中是否有is_trace_started字段進行判斷。 * <p> * 停止服務成功后,會將該字段清除;若未清除,表明為非正常停止服務。 */ private void clearTraceStatus() { if (trackConf.contains("is_trace_started") || trackConf.contains("is_gather_started")) { SharedPreferences.Editor editor = trackConf.edit(); editor.remove("is_trace_started"); editor.remove("is_gather_started"); editor.apply(); } }
在TracingActivity如圖,這個布局就是com.baidu.mapapi.map.MapView和下方兩個按鈕。在OnCreate()的init()初始化三個監聽器和地圖繪制的一些工具類,主要關注點擊兩個按鈕后事件的邏輯。
1 private void init() { 2 initListener(); 3 trackApp = (TrackApplication) getApplicationContext(); 4 viewUtil = new ViewUtil(); 5 mapUtil = MapUtil.getInstance(); 6 mapUtil.init((MapView) findViewById(R.id.tracing_mapView)); 7 mapUtil.setCenter(trackApp); 8 startRealTimeLoc(Constants.LOC_INTERVAL); 9 powerManager = (PowerManager) trackApp.getSystemService(Context.POWER_SERVICE); 10 11 traceBtn = (Button) findViewById(R.id.btn_trace); 12 gatherBtn = (Button) findViewById(R.id.btn_gather); 13 traceBtn.setOnClickListener(this); 14 gatherBtn.setOnClickListener(this); 15 setTraceBtnStyle(); 16 setGatherBtnStyle(); 17 notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 18 }
第8行startRealTimeLoc()看方法名開啟實時定位,傳入實時定位間隔單位是秒,方法實例化了一個內部類runnable,並把這個runnable對象發送到handler處理。這個runnable的子線程調用了TrackApplication的getCurrentLocation()獲取當前位置。
public void startRealTimeLoc(int interval) { realTimeLocRunnable = new RealTimeLocRunnable(interval); realTimeHandler.post(realTimeLocRunnable); }
getCurrentLocation(OnEntityListener entityListener, OnTrackListener trackListener)需要傳入兩個監聽器,在這個方法中,
1 /** 2 * 獲取當前位置 3 */ 4 public void getCurrentLocation(OnEntityListener entityListener, OnTrackListener trackListener) { 5 // 網絡連接正常,開啟服務及采集,則查詢糾偏后實時位置;否則進行實時定位 6 if (NetUtil.isNetworkAvailable(mContext) 7 && trackConf.contains("is_trace_started") 8 && trackConf.contains("is_gather_started") 9 && trackConf.getBoolean("is_trace_started", false) 10 && trackConf.getBoolean("is_gather_started", false)) { 11 LatestPointRequest request = new LatestPointRequest(getTag(), serviceId, entityName); 12 ProcessOption processOption = new ProcessOption(); 13 processOption.setNeedDenoise(true); 14 processOption.setRadiusThreshold(100); 15 request.setProcessOption(processOption); 16 mClient.queryLatestPoint(request, trackListener); 17 } else { 18 mClient.queryRealTimeLoc(locRequest, entityListener); 19 } 20 }
判斷是否開啟了軌跡服務和軌跡采集和網絡連接確定查詢糾偏后實時位置或者進行實時定位。構造請求參數和選項請求位置。
回到剛才的startRealTimeLoc(int interval),傳入的interval間隔也是handler發送消息的間隔。而這個handler在demo中只是簡單的繼承Handler並沒有自定義handlerMessage()的邏輯。這就是個循環。輪詢設備的位置。
在初始化監聽時,OnTrackListener()重寫了onLatestPointCallback(LatestPointResponse response)方法,其中判斷response的返回碼以及返回的點不是原點后將點轉化為地圖上的點並調用mapUtil.updateStatus(currentLatLng,true)繪制點。OnEntityListener()重寫了onReceiveLocation(TraceLocation location),與上面類似。OnTraceListener()中重寫了開啟軌跡服務和采集服務成功失敗的回調,
其中上圖的開啟采集是建立在開啟服務的基礎之上的。例如在開啟服務的回調中,成功的話將TrackApplication中的服務開啟標志位置為true,並在SharedPreference中持久化,注冊廣播。停止服務的回調中,成功停掉的話把TrackApplication的兩個標記位都置為false,並且移除SP的兩個key,解除廣播。
public void onStartTraceCallback(int errorNo, String message) { if (StatusCodes.SUCCESS == errorNo || StatusCodes.START_TRACE_NETWORK_CONNECT_FAILED <= errorNo) { trackApp.isTraceStarted = true; SharedPreferences.Editor editor = trackApp.trackConf.edit(); editor.putBoolean("is_trace_started", true); editor.apply(); setTraceBtnStyle(); registerReceiver(); } viewUtil.showToast(TracingActivity.this, String.format("onStartTraceCallback, errorNo:%d, message:%s ", errorNo, message)); }
if (StatusCodes.SUCCESS == errorNo || StatusCodes.CACHE_TRACK_NOT_UPLOAD == errorNo) { trackApp.isTraceStarted = false; trackApp.isGatherStarted = false; // 停止成功后,直接移除is_trace_started記錄(便於區分用戶沒有停止服務,直接殺死進程的情況) SharedPreferences.Editor editor = trackApp.trackConf.edit(); editor.remove("is_trace_started"); editor.remove("is_gather_started"); editor.apply(); setTraceBtnStyle(); setGatherBtnStyle(); unregisterPowerReceiver(); }
開啟服務時要同時注冊電源鎖和GPS狀態的廣播,停止時解除廣播。這個廣播意義在於:手機鎖屏后一段時間,cpu可能會進入休眠模式,此時無法嚴格按照采集周期獲取定位依據,導致軌跡點缺失。避免這種情況的方式是APP持有電量鎖。還有doze 模式:Doze模式是Android6.0上新出的一種模式,是一種全新的、低能耗的狀態,在后台只有部分任務允許運行,其他都被強制停止。當用戶一段時間沒有使用手機的時候,Doze模式通過延緩app后台的CPU和網絡活動減少電量的消耗。若手機廠商生產的定制機型中使用到該模式,需要申請將app添加進白名單,可盡量幫助鷹眼服務在后台持續運行。在OnResume()中
// 在Android 6.0及以上系統,若定制手機使用到doze模式,請求將應用添加到白名單。 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { String packageName = trackApp.getPackageName(); boolean isIgnoring = powerManager.isIgnoringBatteryOptimizations(packageName); if (!isIgnoring) { Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); intent.setData(Uri.parse("package:" + packageName)); try { startActivity(intent); } catch (Exception ex) { ex.printStackTrace(); } } }
並在相應的生命周期中調用startRealTimeLoc(packInterval)或者stopRealTimeLoc();
那么把點擊事件放到后面來說。
case R.id.btn_trace: if (trackApp.isTraceStarted) { trackApp.mClient.stopTrace(trackApp.mTrace, traceListener); stopRealTimeLoc(); } else { trackApp.mClient.startTrace(trackApp.mTrace, traceListener); if (Constants.DEFAULT_PACK_INTERVAL != packInterval) { stopRealTimeLoc(); startRealTimeLoc(packInterval); } } break;
按鈕兩種狀態,停止服務時通過LBS客戶端調用stopTrace(),stopRealTimeLoc();開啟服務時調startTrace(),判斷間隔是否默認值,否則重新調用實時位置方法。
收集按鈕邏輯:
case R.id.btn_gather: if (trackApp.isGatherStarted) { trackApp.mClient.stopGather(traceListener); } else { trackApp.mClient.startGather(traceListener); } break;
那么我們的重點是:在自己的demo中定位,並且繪制實時軌跡。而且誤差要在接受范圍內。
其實自己走了一些彎路,踩了一些坑。希望這篇文章能幫到那些也要集成類似功能的開發者朋友。
第一步,將鷹眼的庫文件復制到libs下,鷹眼只負責收集並上傳軌跡,如果需要用到其他地圖功能需要自行添加相關的庫!
第二步,百度鷹眼平台申請ak。他有一個獲取應用SHA1碼的工具,如果大家出現230錯誤可以用這個檢測自己的SHA1是否獲取錯誤。
第三步,按照開發指南配置清單文件和gadle。
第四步,在代碼中做一些初始化,包括:SDKInitializer.intialize(mContext);LBSTraceClient和Trace初始化,client設置時間間隔,定位模式如省電啊或者高精度;BitmapUtil、MapUtil的初始化;mapUtil.init((MapView) findViewById(R.id.mapView));mapview控件的實例化;監聽器初始化,請求HistoryTrackRequest初始化等。不一一列舉。
第五步,在onCreate()中定義自己的定位方法,兩點:輪詢請求位置信息;調用client的定位方法。官方舉了兩種例子:在clent調用startTrace后調用queryLatestPoint()獲取糾偏后的實時位置,此時的回調是onTrackListener接口;不調用startTrace()時調用queryRealTimeLoc()獲取實時位置,此時的回調時onEntityListener接口。在自己初始化回調接口的callback中(onTrackListener的onLatestPointCallback,onEntityListener的onReceiveLocation)調用百度的判斷和繪制方法,關鍵是mapUtil.updateStatus(currentLatLng, true)。此外,startTrace()調用后也是有接口回調的,用來告知開發者服務有沒有被開啟,重寫onTraceListener即可。
第六步,繪制實時軌跡。百度給我們提供了歷史軌跡查詢和繪制功能。開發者需要做的就是不斷刷新請求到的歷史軌跡的定位點並將他們用折線連起來。那么具體代碼怎么實現呢?在上面提到的onTrackListener中有一個onHistoryTrackCallback的回調,看到這個名字就是歷史軌跡回調。這個方法返回了一個HistoryTrackResponse對象,百度有自己的邏輯處理這個對象,我們要做的就是循環請求上一個endtime到現在System.currentTimeMillis這個時間段的軌跡信息,繪制工作百度已經替我們做了。自己寫一個循環,調用clent.queryHistoryTrack方法,傳入第四步中初始化的request對象和onTrackListener,並重置request的startTime和endTime即可!
到這里項目的功能已經實現了,截圖如下:
軌跡記錄包括十幾分鍾的步行和半小時的公交車(我上班的路線),可以看出功能已經實現了。不足之處可能大家也發現了,在終點處的線都畫成一片了,為什么呢?我當時把他放大又截了一張:
可以看到終點附近我下了公交步行這一段有明顯錯誤的定位也被繪制到了軌跡中,這就是接下來要說的軌跡糾偏了。
第六步傳入的request對象有一系列糾偏設置:
// 設置需要糾偏 historyTrackRequest.setProcessed(true); // 創建糾偏選項實例 ProcessOption processOption = new ProcessOption(); // 設置需要去噪 processOption.setNeedDenoise(true); // 設置需要抽稀 processOption.setNeedVacuate(true); // 設置需要綁路 processOption.setNeedMapMatch(true); // 設置精度過濾值(定位精度大於100米的過濾掉) processOption.setRadiusThreshold(100); // 設置交通方式為駕車 // processOption.setTransportMode(TransportMode.walking); // 設置糾偏選項 historyTrackRequest.setProcessOption(processOption);
都是官方的方法,根據需要拿來用就行。中午吃飯的時候發動同事都測一下看效果。結果又遇到問題了,打包后的apk文件大家打開之后只有方格沒有數據,可我的測試可用。應該就是百度ak的問題了,把調試和發布的SHA1都設置之后就可以了。
最后分享我在集成百度鷹眼時遇到的一些問題:
1 添加了鷹眼的sdk,發現不全,鷹眼sdk只負責軌跡采集和上傳,如果需要地圖功能,還得再集成地圖sdk。
2 mapView無法實例化Error inflating class com.baidu.mapapi.map.MapView.....
3 java.lang.IllegalArgumentException: marker's icon can not be null,MapUtil的addOverlay()中的icon為空,經過仔細排查發現是BitmapUtil沒有實例化
4 項目運行后先出現北京地圖,過了幾秒出現定位地圖,設置選項我的位置
5 在onTrackListener的回調方法中只有實時位置,無法繪制軌跡。仔細觀察代碼,request需要綁定serviceID
6 查詢軌跡的回調中沒有數據,經過排查,request構建。
7 繪制實時軌跡需要調用startTrace后得到的數據,否則會空指針。
8 一開始導包后項目直接崩掉,仔細排查發現官方demo的清單文件中的application標簽下加上android:name=".LBSapp",其中name是你項目中繼承了Application的子類。
從接手這個問題到昨天夜里繪制實時軌跡,花了兩天多的時間,百度的demo確實讓我學到了東西。感謝大佬。原創,歡迎大家提問。
人往高處走,水往低處流。
==============================================八月三號分割線========================================================================
在項目中做運動軌跡和里程計算時,發現了鷹眼使用的一些新問題,由於使用場景的限制,需要在點擊事件中開啟服務和采集並且要獲取到軌跡和距離信息,當然這些都是要在循環中不斷更新的。出現的問題包括但不限於:沒有定位點;有定位點但開啟軌跡繪制和距離請求后定位點消失且繪制和測距代碼都未執行;未開啟gps時沒有定位信息隨后開啟gps仍無定位和軌跡距離。等等各種異常吧。
經過連夜排查問題發現,在未開啟兩個服務的情況下調用queryDistance、queryHistoryTrack方法返回的list為空,軌跡點為null。即使隨后服務開啟了但是在我的代碼中由於是根據點的起始時間來更新請求參數的導致我在判斷軌跡點是否為null之前就做了繪制操作,而百度繪制方法中有一個判斷,當傳入的軌跡點為零時會移除覆蓋物並將其置為null這樣就會導致我的地圖上什么覆蓋物都沒有了而且我是判斷了返回的點的EndPoint不為null時迭代請求參數,上述情況下EndPoint是null我沒考慮到,導致位null時仍然在循環相同的請求;此外如果不顯示調用前面的兩個query方法,那么onTrackListener中相應的回調就不會執行。
所以我的問題實際上是對鷹眼服務的使用不夠了解和自己代碼的邏輯不夠嚴謹造成的。
完善辦法:查出這個問題的原因花了很久,但解決起來就輕松多了。第一,確保startService和startGather開啟后再做請求,其實就是在onTraceListener的開啟收集成功回調中或者更改標志位再開啟請求數據的循環。此處要注意的是,如果在項目中開啟服務和查詢的邏輯寫在一起,再通過標志位判斷開啟循環請求的話就會出錯,原因在於開啟服務的代碼回調大概需要兩三秒才有回調結果,而此時判斷標志位代碼早已經執行過了,所以請求數據的代碼就永遠得不到執行了。關鍵是第二點,第二點處理好了第一點就沒那么重要了,在調用請求方法后,onTrackListener的相關回調中做好返回數據為空和不為空的邏輯。當返回的點為空或者不為空時,應該合理設置請求參數,這樣只要查詢請求的循環跑起來了數據總會及時更新的。第三,在離開軌跡頁面停掉服務時要在onTraceListener的回調中停止輪詢操作。
=============================================================八月八號問題更新==================================================================================
在實時軌跡繪制中會出現縮放頻繁的問題,剛開始十分頭疼。因為認為這是百度控制的問題,后來經過我的仔細思考,嘿嘿,認為是因為項目中同時輪詢了定位信息和軌跡繪制,而這兩個在地圖上控制縮放時是有沖突的。在onLatestPointCallback中調用的mapUtil.updateStaus()里將回調的定位點放置在屏幕的中心區域,只要定位點不在中心就會調用animateMapStaus()。在這個方法中,利用MapStausUpdateFactory的工廠方法構造mapStaus對象再調用baidumap.animateMapStaus()調整縮放。而繪制軌跡調用mapUtil的drawHistoryTrack()方法,此方法中會調用一個重載的animateMapStaus(),會構造一個包含所有歷史軌跡點的mapStaus對象並調用baidumap.animateMapStaus()調整縮放。而在我同時請求定位點和軌跡時這兩個方法就會輪番調用,導致現象就是,一會這個方法設置了一個比例尺,下一秒另一個同名的重載方法又設置了一個比例尺,地圖 就會不停的縮放。定位了問題之后,就好解決了。項目要求的效果自然是軌跡和定位點必須在屏幕,但定位點不需要一定在屏幕中心,把updateStaus()方法參數稍微改一下即可,使定位點只要在屏幕上就不會調整地圖比例尺就行了。到此運動這一塊跟百度地圖相關的功能都已經解決了。
以上就是最新的鷹眼應用使用分享!如果有覺得描述不清楚的可以留言。