android中几种定位方式详解
Android进阶之路系列:https://blog.csdn.net/chzphoenix/column/info/16488
前言:
android中我们一般使用LocationManager来获取位置信息,这里面有四中provider:
public static final String NETWORK_PROVIDER = "network";public static final String GPS_PROVIDER = "gps";public static final String PASSIVE_PROVIDER = "passive";public static final String FUSED_PROVIDER = "fused";其中fused已经被废弃了,其它三种区别如下:
(1)GPS_PROVIDER:通过 GPS 来获取地理位置的经纬度信息; 优点:获取地理位置信息精确度高; 缺点:只能在户外使用,获取经纬度信息耗时,耗电;
(2)NETWORK_PROVIDER:通过移动网络的基站或者 Wi-Fi 来获取地理位置; 优点:只要有网络,就可以快速定位,室内室外都可; 缺点:精确度不高;
(3)PASSIVE_PROVIDER:被动接收更新地理位置信息,而不用自己请求地理位置信息。
PASSIVE_PROVIDER 返回的位置是通过其他 providers 产生的,可以查询 getProvider() 方法决定位置更新的由来,需要 ACCESS_FINE_LOCATION 权限,但是如果未启用 GPS,则此 provider 可能只返回粗略位置匹配;
我们通常使用gps和network这两种方式。
但是我们还可以通过其它方式获取位置信息,这篇文章就详细的讲解一下在android中几种获取定位的方式。
1、GPS定位
这个用的最普遍,可以获取上次定位,也可以监听变化,代码如下:
需要权限
//GPS定位
var locManager = getSystemService(Context.LOCATION_SERVICE) as LocationManagervar loc = locManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)if(loc != null){ Log.e("gpslocation", loc.toString()) toast(loc.toString())}locManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0F, object : LocationListener{ override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) { } override fun onProviderEnabled(provider: String?) { } override fun onProviderDisabled(provider: String?) { } override fun onLocationChanged(location: Location?) { Log.e("gpslocation", location.toString()) toast(location.toString()) }})
2、NETWORK定位
与gps定位代码基本一直,只不过将provider改成LocationManager.NETWORK_PROVIDER
3、AGPS定位
实际上是将上面两种定位结合起来,具体原理如下:
AGPS手机首先将本身的基站地址通过网络传输到位置服务器;
位置服务器根据该手机的大概位置传输与该位置相关的GPS辅助信息(包含GPS的星历和方位俯仰角等)到手机;
该手机的AGPS模块根据辅助信息(以提升GPS信号的第一锁定时间TTFF能力)接收GPS原始信号;
手机在接收到GPS原始信号后解调信号,计算手机到卫星的伪距(伪距为受各种GPS误差影响的距离),并将有关信息通过网络传输到位置服务器;
位置服务器根据传来的GPS伪距信息和来自其他定位设备(如差分GPS基准站等)的辅助信息完成对GPS信息的处理,并估算该手机的位置;
位置服务器将该手机的位置通过网络传输到定位网关或应用平台。
我的理解就是通过网络位置和位置服务器判断出最佳的卫星,减少了获取卫星信号的时间。因为网络位置获取很快,所以可以减少整体的定位时间。
AGPS并不是一种定位方式,只是一种优化方案,代码与GPS一样,只不过在设置中将定位模式设成AGPS。
上面是android自带的定位方式,我们还可以获取一些原始信息(比如基站信息、wifi信息),通过公开的接口来获取位置信息。下面几种方式就是使用原始信息通过API来获取位置信息。
4、基站定位
通过TelephonyManager我们可以拿到基站信息,再通过相关的api接口就能得到经纬度,但是基站定位精度很差。
基站信息包含如下:
MCC,Mobile Country Code,移动国家代码(中国的为460);
MNC,Mobile Network Code,移动网络号码(中国移动为00,中国联通为01);
LAC,Location Area Code,位置区域码;
CID,Cell Identity,基站编号,是个16位的数据(范围是0到65535)。
拿到这个信息后,我们可以通过一些公开的api服务拿到经纬度,如下:
http://www.google.com/loc/json google的,post请求,好像停用了
http://www.cellocation.com/interfac/ 目前可用,是免费的
实例:
http://api.cellocation.com:81/cell/?mcc=460&mnc=1&lac=4301&ci=20986&output=json
返回:
{"errcode":0, "lat":"40.00598145", "lon":"116.48539734", "radius":"937", "address":"北京市朝阳区来广营地区东湖渠;溪阳东路与屏翠东路路口东70米"}接口参数:
名称 | 类型 | 必填 | 说明 |
mcc | int | 是 | mcc国家代码:中国代码 460 |
mnc | int | 是 | mnc网络类型:0移动,1联通(电信对应sid),十进制 |
lac | int | 是 | lac(电信对应nid),十进制 |
ci | int | 是 | cellid(电信对应bid),十进制 |
coord | string | 否 | 坐标类型(wgs84/gcj02/bd09),默认wgs84 |
output | string | 否 | 返回格式(csv/json/xml),默认csv |
另外还有很多提供这种接口和数据的平台,自己搜索即可
代码如下:
需要权限
//基站定位,这里只实现了GSM的,CDMA的有些许不同
val telManager = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManagerif(telManager.cellLocation is GsmCellLocation) { val cellLoc = telManager.cellLocation as GsmCellLocation if (cellLoc != null) { val operator = telManager.networkOperator val mcc = operator.substring(0, 3).toInt() val mnc = operator.substring(3).toInt() val cid = cellLoc.cid val lac = cellLoc.lac var sb = StringBuilder("http://api.cellocation.com:81/cell/?") sb.append("mcc=") sb.append(mcc) sb.append("&mnc=") sb.append(mnc) sb.append("&lac=") sb.append(lac) sb.append("&ci=") sb.append(cid) sb.append("&output=json") Log.e("tellocation", sb.toString()) doAsync { val result = URL(sb.toString()).readText() Log.e("tellocation", result) } }}
5、WIFI定位
wifi定位是通过WifiManager拿到wifi的信息,主要是wifi的BSSID(即mac地址)。然后通过一些api查询经纬度,比如http://www.cellocation.com/interfac/
实例:
http://api.cellocation.com:81/wifi/?mac=00:87:36:05:5d:ea&output=json
返回:
{"errcode":0, "lat":"39.950008", "lon":"116.230049", "radius":"222", "address":"北京市海淀区四季青镇益园文创基地c区9号楼;南平庄中路与西平庄路路口西北561米"}
接口参数:
名称 | 类型 | 必填 | 说明 |
mac | string | 是 | WIFI热点的MAC地址(BSSID) |
coord | string | 否 | 坐标类型(wgs84/gcj02/bd09),默认wgs84 |
output | string | 否 | 返回格式(csv/json/xml),默认csv |
代码如下: 需要权限
|
6、混合定位
混合定位就是获取附近的wifi列表信息(包括信号强度)和附近的基站列表信息(包括信号强度),通过一些api获取经纬度。
这种方式相对于单一的基站和wifi定位要更精确一些。
获取附近的wifi列表在WIFI定位已经提到过了,通过WifiManager的getScanResults函数获取扫描到的wifi列表,其中level就是信号强度,可能需要做一下去重。
获取附近的基站列表则有些问题。
官方提供了一个方式,通过TelephonyManager的getNeighboringCellInfo函数获得,其中mRssi就是信号强度。
需要权限
//获取附近基站信息
val telManager = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManagerval neighborCells = telManager.neighboringCellInfofor(cell in neighborCells){ //这里将rssi转化为dBm val level = -131 + 2 * cell.rssi Log.e("neighboringCellInfo", "cid:${cell.cid} lac:${cell.lac} rssi:$level")}
但是实际使用时发现cid和lac都是-1。
检查NeighboringCellInfo的构造方法,如下:
public NeighboringCellInfo(int rssi, String location, int radioType) { // set default value mRssi = rssi; mNetworkType = NETWORK_TYPE_UNKNOWN; mPsc = UNKNOWN_CID; mLac = UNKNOWN_CID; mCid = UNKNOWN_CID; // pad location string with leading "0" int l = location.length(); if (l > 8) return; if (l < 8) { for (int i = 0; i < (8-l); i++) { location = "0" + location; } } // TODO - handle LTE and eHRPD (or find they can't be supported) try {// set LAC/CID or PSC based on radioType switch (radioType) { case NETWORK_TYPE_GPRS: case NETWORK_TYPE_EDGE: mNetworkType = radioType; // check if 0xFFFFFFFF for UNKNOWN_CID if (!location.equalsIgnoreCase("FFFFFFFF")) { mCid = Integer.parseInt(location.substring(4), 16); mLac = Integer.parseInt(location.substring(0, 4), 16); } break; case NETWORK_TYPE_UMTS: case NETWORK_TYPE_HSDPA: case NETWORK_TYPE_HSUPA: case NETWORK_TYPE_HSPA: mNetworkType = radioType; mPsc = Integer.parseInt(location, 16); break; } } catch (NumberFormatException e) { // parsing location error mPsc = UNKNOWN_CID; mLac = UNKNOWN_CID; mCid = UNKNOWN_CID; mNetworkType = NETWORK_TYPE_UNKNOWN; }}
可以看到只对GPRS和EDGE网络进行了处理,而3G、4G网络都是UNKNOWN_CID,即-1。说明这种方法不支持,已经过时。
官方还有另外一个方式,通过TelephonyManager的getAllCellInfo函数获得。这个函数要求minsdkverison必须在17及以上
需要权限
//获取基站信息,这里只处理了LTE网络的
val telManager = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManagerval cells = telManager.allCellInfofor(cell in cells){ if(cell is CellInfoLte) { Log.e("celllist", "cid:${cell.cellIdentity.ci} lac:${cell.cellIdentity.tac} dbm:${cell.cellSignalStrength.dbm} isRegistered:${cell.isRegistered}") }}
得到的信息如下:
E/celllist: cid:3912981 lac:4154 dbm:-87 isRegistered:true
E/celllist: cid:2147483647 lac:2147483647 dbm:-101 isRegistered:false
E/celllist: cid:2147483647 lac:2147483647 dbm:-102 isRegistered:false
E/celllist: cid:2147483647 lac:2147483647 dbm:-108 isRegistered:false
E/celllist: cid:2147483647 lac:2147483647 dbm:-101 isRegistered:false
其中第一个是我们正在使用的基站,可以看到正常的返回了信息,而其余的则返回默认的信息(Integer.MAX_VALUE)
说明这个方法也只能拿到当前使用的基站信息。而且据网上的说法,当使用2G网络,getAllCellInfo得到的是NULL。
这样目前没有更好的方式获取多个基站信息了。
当我们拿到附近的基站信息和wifi信息,可以通过http://www.cellocation.com/interfac/ 提供的混合定位接口查询位置信息,与上面类似,这里不细说了。
Android进阶之路系列:https://blog.csdn.net/chzphoenix/column/info/16488
更多相关文章
- Android开发错误信息与解决方案汇总
- Android判断Wlan信号强弱及wlan管理信息
- Android监控外接USB设备和获取USB等设备的详细信息
- Android P SystemUI下拉时,状态栏和通知栏显示位置不一致。
- Android中保存并设置ListView位置
- Android 中如何获取editText文本信息
- Android获取已安装应用信息(图标,名称,版本号,包)
- Android XML属性在文档中的位置
- Android中音乐文件的信息详解【安卓源码解析二】