记录接入百度地图鹰眼的详细步骤

  首先告诉大家:我的项目功能包含实时定位,实时轨迹绘制,运动距离展示,缩放自动调整等,基本遇到的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()方法参数稍微改一下即可,使定位点只要在屏幕上就不会调整地图比例尺就行了。到此运动这一块跟百度地图相关的功能都已经解决了。

  以上就是最新的鹰眼应用使用分享!如果有觉得描述不清楚的可以留言。

posted @ 2017-07-11 14:20  老乡别跑  阅读(18180)  评论(13编辑  收藏  举报