Quantcast
Channel: CSDN博客移动开发推荐文章
Viewing all articles
Browse latest Browse all 5930

重拾百度定位之踩坑篇

$
0
0

源码传送门


前言

最近更新项目中用的百度定位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。
501700: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>

好了,到此,本篇文章真的结束了,若文章有不足或者错误的地方,欢迎指正,以防止给其他读者错误引导

作者:xiehuimx 发表于2016/11/27 22:29:04 原文链接
阅读:37 评论:0 查看评论

Viewing all articles
Browse latest Browse all 5930

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>