Android中如何使用GPS获取位置信息?一个小Demo如下
GPS简介
Gobal Positioning System,全球定位系统,是美国在20世纪70年代研制的一种以人造地球卫星为基础的高精度无线电导航的定位系统,它在全球任何地方以及近地空间都能够提供准确的地理位置、车行速度及精确的时间信息;它是具有在海、陆、空进行全方位实时三维导航与定位功能的新一代卫星导航与定位系统。
中国用的定位系统是北斗卫星导航系统Beidou Navigation Satellite System,简称BDS,BDS、GPS、俄罗斯的GLONASS、欧盟的GALILEO是联合国卫星导航委员会已经认定的供应商。
手机的定位功能应用广泛,本章来学习GPS的应用开发。
GPS的常用API
GPS系统有三部分组成,卫星空间部分、地面监控部分和用户设备部分。
对于Android机来说,是用户设备部分,是GPS定位系统的接收机,也就是说GPS定位需要有硬件支持GPS功能。对于Android开发人员来说,通过LocationManager、LocationProvider、Location这三个API的一些方法就能开发出GPS应用。
程序的LocationManger对象不能直接创建,而是要通过Context.getSystemService方法获取
LocationManager locationManager= (LocationManager) getSystemService(Context.LOCATION_SERVICE);
List<String> allProviders = locationManager.getAllProviders();
通过询问chatGPT,整理出了这样的一个表格,LocationManger的方法以及作用如下:
方法名 | 返回值 | 作用 |
getLastKnownLocation(String provider) | Location | 获取指定 provider 的最近一次位置信息 |
requestLocationUpdates(String provider, long minTime, float minDistance, LocationListener listener) | void | 注册位置监听器,以获取 provider 的位置更新。参数 minTime 和 minDistance 分别表示位置更新的最小时间间隔和最小距离间隔 |
removeUpdates(LocationListener listener) | void | 取消位置监听器的注册 |
isProviderEnabled(String provider) | boolean | 判断指定 provider 是否可用 |
addProximityAlert(double latitude, double longitude, float radius, long expiration, PendingIntent intent) | void | 注册一个“地理围栏”,当设备进入指定半径的圆形区域时触发 intent |
removeProximityAlert(PendingIntent intent) | void | 取消“地理围栏”的注册 |
使用 LocationManager 时需要申请适当的权限,比如 ACCESS_FINE_LOCATION 或 ACCESS_COARSE_LOCATION 权限。
还有一个API是LocationProvider,他不是Android中的类,而是 Android 系统中的一个抽象类,定义了位置提供者的标准接口,它的实现类用于提供位置信息。LocationProvider 的实现类可以通过系统服务 LocationManager 来获取。当应用程序请求获取位置信息时,LocationManager 会选择合适的位置提供者来获取位置信息,根据不同的需求选择不同的位置提供者。
方法名 | 返回值 | 作用 |
getName() | String | 获取位置提供者(locationProvider)的名称 |
getAccuracy() | int | 获取该位置提供者的精度 |
getPowerRequirement() | int | 获取该位置提供者的电量消耗 |
hasMonetaryCost() | boolean | 判断该位置提供者是否需要支付费用 |
requiresCell() | boolean | 判断该位置提供者是否需要使用移动网络 |
requiresNetwork() | boolean | 判断该位置提供者是否需要使用数据网络 |
requiresSatellite() | boolean | 判断该位置提供者是否需要使用卫星 |
supportsAltitude() | boolean | 判断该位置提供者是否支持海拔信息 |
supportsBearing() | boolean | 判断该位置提供者是否支持方向信息 |
supportsSpeed() | boolean | 判断该位置提供者是否支持速度信息 |
还有一个API是Location,Location 是 Android 中用于表示位置信息的类,它包含了经度、纬度、海拔、速度、方向等位置信息。Location 对象是由 LocationProvider 提供的,LocationManager 负责获取 LocationProvider 提供的位置信息,并将其封装成 Location 对象,然后提供给应用程序使用。方法如下:
方法名 | 返回值 | 作用 |
getLatitude() | double | 获取该位置的纬度 |
getLongitude() | double | 获取该位置的经度 |
getAltitude() | double | 获取该位置的海拔 |
getAccuracy() | float | 获取该位置的精度 |
getBearing() | float | 获取该位置的方向 |
getSpeed() | float | 获取该位置的速度 |
getTime() | long | 获取该位置信息的时间戳 |
hasAccuracy() | boolean | 判断该位置信息是否包含精度信息 |
hasAltitude() | boolean | 判断该位置信息是否包含海拔信息 |
hasBearing() | boolean | 判断该位置信息是否包含方向信息 |
hasSpeed() | boolean | 判断该位置信息是否包含速度信息 |
需要注意的是,获取 Location 对象时可能会出现空值,因此在使用 Location 对象前需要先进行非空判断。另外,获取位置信息需要申请适当的权限,比如 ACCESS_FINE_LOCATION 或 ACCESS_COARSE_LOCATION 权限。
上面三个API就是Android GPS定位支持的三个核心API。使用步骤如下:
1 获取系统的LocationManger对象。
2 使用LocationManger,通过指定的locationProvider来获取定位信息,定位信息由Location表示。
3 从Location对象中获取定位信息。
locationProvider
写一个简单的demo来获取当前系统所有的locationProvider
public class MainActivity extends AppCompatActivity {private static final String TAG = "MainActivity";private ListView listView;private LocationManager locationManager;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);listView = findViewById(R.id.list);locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);//获取所有的locationProvider名称List<String> allProviders = locationManager.getAllProviders();ArrayAdapter adapter=new ArrayAdapter(this, android.R.layout.simple_list_item_1,allProviders);listView.setAdapter(adapter);}
}
在真机上运行可以看到该系统上有如下四个LocationProvider
移动端通常通过WIFI、GPS、基站这三种方式来定位设备。
以下这四种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";
NETWORK_PROVIDER:通过移动网络的基站或者 Wi-Fi 来获取地理位置;优点:只要有网络,就可以快速定位,室内室外都可;缺点:精确度不高;
GPS_PROVIDER:通过 GPS 来获取地理位置的经纬度信息;优点:获取地理位置信息精确度高;缺点:只能在户外使用,获取经纬度信息耗时,耗电;
PASSIVE_PROVIDER:被动接收更新地理位置信息,而不用自己请求地理位置信息。
PASSIVE_PROVIDER 返回的位置是通过其他 providers 产生的,可以查询 getProvider() 方法决定位置更新的由来,需要 ACCESS_FINE_LOCATION 权限,但是如果未启用 GPS,则此 provider 可能只返回粗略位置匹配;
FUSED_PROVIDER:已经被废弃了
通过名称获取指定的locationProvider
LocationProvider provider = locationManager.getProvider(LocationManager.GPS_PROVIDER);
使用GPS获取位置信息
代码如下:
public class MainActivity extends AppCompatActivity {private static final String TAG = "MainActivity";private LocationManager locationManager;private TextView showInfo;@RequiresApi(api = Build.VERSION_CODES.M)@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);showInfo = findViewById(R.id.show_location);requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION},0x123);}private void updateView(Location location) {if (location != null) {Log.d(TAG, "updateView: 222");String res = "实时位置:\n" +"经度:" + location.getLongitude() +"\n纬度:" + location.getLatitude() +"\n高度:" + location.getAltitude() +"\n速度:" + location.getSpeed()+ "\n方向:" + location.getBearing();showInfo.setText(res);} else {Log.d(TAG, "updateView: 111");showInfo.setText("");}}@SuppressLint("MissingPermission")@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);if (requestCode == 0x123 && grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {//创建locationManger对象locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);//获取最新的定位信息Location lastKnownLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);updateView(lastKnownLocation);//每隔三秒获取一次GPS信息locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 3000, 8f, new LocationListener() {@Overridepublic void onLocationChanged(@NonNull Location location) {updateView(location);}});}}
}
这里代码也很简单,我布局文件只写了一个textView来展示经纬度信息,首先在onCreate方法里动态申请权限。在Android6.0以上不仅仅要在声明文件里静态注册,还有动态申请,否则会出现安全异常。然后就是一个更新UI的方法,通过传入的location获取经纬度高度等信息。选择完获取权限后,会执行onRequestPermissionsResult回调函数,然后就是获取定位信息更新UI。
最后需要在声明文件里静态注册权限。
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
我获取到的是北京市内某地区的经纬度,把该程序和地图功能结合,就能反映出该经纬度在地图上的具体位置,即可开发出GPS导航系统。
室内WIFI定位
室内WIFI定位主要根据WIFI路由器所在的位置进行定位,主要应用于室内较小的空间的精准定位。
Android 9 添加了WiFi 室内定义功能(RTT),以下内容来自 developer.android.com
Android 9 添加了对 IEEE 802.11mc Wi-Fi 协议(也称为 Wi-Fi Round-Trip-Time (RTT))的平台支持,从而让您的应用可以利用室内定位功能。
在运行 Android 9 且具有硬件支持的设备上,应用可以使用 RTT API 来测量与附近支持 RTT 的 Wi-Fi 接入点 (AP) 的距离。 设备必须已启用位置服务并开启 Wi-Fi 扫描(在 Settings > Location 下),同时您的应用必须具有 ACCESS_FINE_LOCATION 权限。
设备无需连接到接入点即可使用 RTT。 为了保护隐私,只有手机可以确定与接入点的距离;接入点无此信息。
如果您的设备测量与 3 个或更多接入点的距离,您可以使用一个多点定位算法来预估与这些测量值最相符的设备位置。 结果通常精准至 1 至 2 米。
室内WIFI定位的管理器是WifiRttManager,获取方式如下,由于在Android9后才增加该功能,所以需要先判断。
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {WifiRttManager wifiRttManager= (WifiRttManager) getSystemService(Context.WIFI_RTT_RANGING_SERVICE);
}
获取到wifirttmanger对象后,可以用startRanging开始定位,该方法里有三个参数。
startRanging(@NonNull RangingRequest request, @NonNull Executor executor, @NonNull RangingResultCallback callback)
RangingRequest:该参数代表一个定位请求,该参数管理本次定位是基于哪个WIFI节点进行访问的
Executor:创建一个新的线程来执行WIFI定位,避免阻塞UI线程。
RangingResultCallback:定位成功或失败的时候触发该对象内的特定方法
写一个具体的案例来实现室内WIFI定位,代码如下:
public class MainActivity extends AppCompatActivity {private static final String TAG = "MainActivity";WifiRttManager mWifiRttManager;//定义监听WIFI状态改变的BroadcastReceiverpublic class WIfiChangeReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent) {if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(intent.getAction())) {//开始执行wifi定位startWifiLoc();}}}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//定义一个监听网络状态的改变 WIFI状态改变的intentFilterIntentFilter wifiFilter = new IntentFilter();wifiFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);wifiFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);wifiFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);registerReceiver(new WIfiChangeReceiver(), wifiFilter);}@SuppressLint("MissingPermission")private void startWifiLoc() {//获取wifi管理器WifiManager wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);//判断是否支持室内wifi定位功能boolean hasRtt = getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_RTT);Log.d(TAG, "startWifiLoc: 是否具有室内WIFI定位功能:" + hasRtt);//Android版本大于9才能使用if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {//获取室内WiFi定位管理器mWifiRttManager = (WifiRttManager) getSystemService(Context.WIFI_RTT_RANGING_SERVICE);RangingRequest request = new RangingRequest.Builder().addAccessPoints(wifiManager.getScanResults())//添加WiFi扫描结果.build();//创建RangingRequest对象Log.d(TAG, "startWifiLoc: request:"+request);//开始请求执行室内WIFI定位mWifiRttManager.startRanging(request, Executors.newCachedThreadPool(), new RangingResultCallback() {@Overridepublic void onRangingFailure(int i) {//WIFI定位出错执行的方法Log.d(TAG, "onRangingFailure:错误码是: "+i);}@Overridepublic void onRangingResults(@NonNull List<RangingResult> list) {//室内WiFi定位返回结果时触发for (RangingResult rr : list){Log.d(TAG, "onRangingResults:与 "+rr.getMacAddress()+" WIFI的距离是:"+rr.getDistanceMm());}}});}}}
运行后报错了
查询了一下原因是我设备硬件不支持WIFIRTT功能。
总结WIFI定位的步骤:
1 获取WifiRttManger对象
2 通过RangingRequest.Builder来创建对象,创建之前应该先添加WiFi扫描得到WiFi访问点的信息
3 调用WifiRttManger对象的startRanging方法
近距离警报
之前学习的API方法中有一个函数:
addProximityAlert(double latitude, double longitude, float radius, long expiration, PendingIntent intent)
代码:
public class MainActivity extends AppCompatActivity {private static final String TAG = "MainActivity";private LocationManager locationManager;@RequiresApi(api = Build.VERSION_CODES.M)@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 0x123);}@SuppressLint("MissingPermission")@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);if (requestCode == 0x123 && grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);//定义靠近点的经纬度信息double lon = 116.00;double lat = 46.00;float radius = 500F; //定义半径500MIntent intent = new Intent(this, ProxAlertReciever.class);//将intent封装成pendingIntentPendingIntent pendingIntent = PendingIntent.getBroadcast(this, -1, intent, 0);//添加临近警告locationManager.addProximityAlert(lat, lon, radius, -1, pendingIntent);}}
}
注册一个广播监听变化:
public class ProxAlertReciever extends BroadcastReceiver {private static final String TAG = "ProxAlertReciever";@Overridepublic void onReceive(Context context, Intent intent) {boolean isEnter= intent.getBooleanExtra(LocationManager.KEY_PROXIMITY_ENTERING,false);if (isEnter){//靠近提示Log.d(TAG, "onReceive: 快靠近了!!");}else {Log.d(TAG, "onReceive: 离开了!!");}}
}
至此 Android中的GPS的就讲完了,想开发出具体的导航系统还要结合地图功能。