首先告诉你们:个人项目功能包含实时定位,实时轨迹绘制,运动距离展现,缩放自动调整等,基本遇到的bug都被我解决了。你们有问题下面直接问,互相学习(2017.08.08add)!php
首先进入百度地图开发者中心,找到鹰眼轨迹。按照开发指南首先申请密钥,即获取百度要求的sha1值,在Android studio控制台G:\android2016\LBSdemo>下输入keytool -list -v -keystore D:\key\lbsdemo.jks提示'keytool' 不是内部或外部命令,也不是可运行的程序。多是我打开方式不对,因而我到命令行窗口在Java的安装目录下找到keytool命令,java
那么按照提示输入android
其中命令-keystore后的目录是Android Studio在打包时开发者设置的key的路径,接着会让你输入当时设置的密码,密码输入时不显示,回车便可。http://lbsyun.baidu.com/index.php?title=android-yingyan/guide/key此处有官方图文引导。api
下一步配置工程,下载库文件解压后将库粘到libs文件夹下,并在app的build gradle 添加网络
sourceSets { main { jniLibs.srcDir 'libs' } }
接下来配置清单文件:(直接从官网上粘)app
一、在Application标签中声明SERVICE组件,每一个APP拥有本身独立的鹰眼追踪serviceide
<service android:name="com.baidu.trace.LBSTraceService" android:enabled="true" android:process=":remote"> </service>
二、声明使用权限:工具
<!-- 这个权限用于进行网络定位--> <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申请的那个字符串。post
<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()方法参数稍微改一下便可,使定位点只要在屏幕上就不会调整地图比例尺就好了。到此运动这一块跟百度地图相关的功能都已经解决了。
以上就是最新的鹰眼应用使用分享!若是有以为描述不清楚的能够留言。