目录
1、前言
2、Android GNSS 介绍
3、Android GNSS 各层级流程分析
3.1 API 接口层
3.2 Framework 服务层
3.3 JNI 层级调用
3.4 Native 层 / Hal 层
4、GNSS NMEA 数据概述
正文
1 前言
大家好,本章节是介绍 Android GNSS 整体框架服务。此篇为学习记录随笔,文中若有错误,还请大家不吝告知,指正修改,小弟先行告谢!!!
文章代码基于Android Q 版本代码分析。文章按照 Android 的层次架构来系统整理 Android GNSS 模块的流程及功能。
2 Android GNSS 介绍
GNSS为Global Navigation Satellite System的缩写,即全球导航卫星系统。当前应用较广泛的主要有美国的GPS、俄罗斯的GLONASS、欧盟的GALILEO和中国北斗卫星导航系统等。
Android GNSS 框架图
(Android GNSS 架构图)
首先先来介绍一下 Android GNSS 模块的整体架构。从图中可以看到,GNSS 架构是从 应用层 ---> 通过原生 jar 包 ---> 调用 AIDL 接口 ---> 连接到 Android Framework 层 ---> 通过 JNI ---> 调用 HIDL 接口 ---> 连接到 Hal 层。AIDL 和 HIDL 跨进程通信用橙色标注,表明调用进程切换。
Android GNSS 应用接口 -> Framework 类图
(引用图,版本应该比较老了)
上图基本包含接口包(android.location 包)中比较重要的类以及 Framework 层 GNSS 服务端比较重要的类。
3 Android GNSS 各层级流程分析
3.1 API 接口层
3.1.1 Location 权限
Location 权限类型:类别(前台信息/后台信息)、精确度(确切位置/大致位置)。
类别
前台位置信息:如果应用仅需要接收一次位置信息或者只在特定一段时间内接收位置信息,则申请前台位置权限。android:foregroundServiceType="location"
后台位置信息:如果应用需要不断获取位置信息,那么需要申请后台位置权限。在 Q 版本及以后,必须在 Manifest 中声明 ACCESS_BACKGROUND_LOCATION 权限。在较低的 Android 版本中,当应用获得前台位置信息访问权限时,也会自动获得后台位置信息访问权限。
精确度
大致位置:提供设备位置的估算值。如果位置值来自 LocationManagerService 或 FusedLocationProvider,该估算值的精确度大约在 3 平方公里内。对应权限为 ACCESS_COARSE_LOCATION 权限。
精确位置:提供尽可能准确的设备位置估算值。如果位置值来自 LocationManagerService 或 FusedLocationProvider,则此估算值的精确度在 50 米内,有时甚至可以精确到几米内。对应权限为 ACCESS_FINE_LOCATION 权限。
3.1.2 代码路径
Android GNSS接口位置代码目录在 frameworks/base/location 下,有三个目录:java、lib、tests。
java目录下的代码被封装到 framework.jar 中为应用提供接口服务;lib目录下的代码被封装为 com.android.location.provider.jar 包;tests目录中是测试相关代码。
3.1.3 重要接口/方法
在原生接口中,比较重要的有 LocationManager、LocationProvider、LocationListener、Location等,具体可以参考 Google官方文档(地址:android.location | Android Developers)。
这些都是 Google 官方提供的 location 包的功能类。其中最重要的类是 LocationManager,这是整个定位服务的入口类。LocationListener 接口是接收 GNSS 信息的回调接口。
下面介绍以下 LocationManager 中一些比较重要的方法
* 获取 LocationManager 对象 —— Context.getSystemService(Context.LOCATION_SERVICE)
* 获取 LocationProvider —— getBestProvider(Criteria criteria, boolean enabledOnly)
* 获取位置信息 —— requestLocationUpdates(String provider, long minTimeMs, float minDistanceM, LocationListener listener)
requestLocationUpdates() 方法的功能是通过给定的参数注册给 Location 服务端,请求位置信息,并且能够持续收到信息回调。
系统通过这一个函数封装整个注册的过程,下面我们以上述接口参数为例,跟踪接口是如何进行封装的。
应用调用 requestLocationUpdates() 请求的语句一般如下
requestLocationUpdates(@NonNull String provider, long minTime, float minDistance,
@NonNull LocationListener listener)
参数解析:
provider:位置提供者的名称
minTime:按配置的时间间隔提供位置数据,单位是 毫秒
minDistance:按移动的距离提供位置数据,单位是 米
listener:回调接口,位置更新是被调用
requestLocationUpdates(@NonNull String provider, long minTime, float minDistance,
@NonNull PendingIntent intent)
参数解析:
provider:位置提供者的名称
minTime:按配置的时间间隔提供位置数据,单位是 毫秒
minDistance:按移动的距离提供位置数据,单位是 米
intent:Framework通过此PendingIntent返回携带location的位置数据,通过send()触发
(frameworks/base/location/java/android/location/LocationManager.java)
总体来说有这两种,区别在于监听位置的回调对象一个是 LocationListener,另一个是 PendingIntent。当然, Framework 在 reportLocation 通知应用的时候,也会在这里判断,是通过哪种方式通知上层。
在 LocationManager 中有很多 requestLocationUpdates() 请求方法的重载,重载之间有嵌套调用,那么在最后还是有一个重载去调用到 Framework 服务中去。如下:
private void requestLocationUpdates(LocationRequest request, LocationListener listener,
Looper looper, PendingIntent intent) {
String packageName = mContext.getPackageName();
// wrap the listener class
// ListenerTransport 是内部类,继承自 ILocationListener.Stub,是 ILocationListener.aidl 的实现,内部封装了对外提供的接口 LocationListener;Framework 层的回调会先调用到 ListenerTransport,再通过 ListenerTransport 内部保存的应用实现的 LocationListener 对象通知到应用端。
ListenerTransport transport = wrapListener(listener, looper);
try {
mService.requestLocationUpdates(request, transport, intent, packageName);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
(frameworks/base/services/core/java/com/android/server/LocationManagerService.java)
我们主要来看下调用到服务端的参数构成,有哪些比较重要的结构组成。
参数一 LocationRequest
LocationRequest 是一个数据对象,表明你所要请求位置服务的要求,例如精确度、按时间回调、按距离回调等。基本是通过其 静态方法 createFromDeprecatedProvider() 去创建对象,传入对象包括:provider、minTime、minDistance、singleShot(在 requestLocationUpdates 的调用中传入的均是 false)。
参数二 ListenerTransport
ListenerTransport 是 LocationManager 的内部类,继承自 ILocationListener.Stub,是 ILocationListener.aidl 的实现,内部封装了对外提供的接口 LocationListener;Framework 层的回调会先调用到 ListenerTransport,再通过 ListenerTransport 内部保存的应用实现的 LocationListener 对象通知到应用端。
参数三 PendingIntent
PendingIntent 是应用端调用 requestLocationUpdates 函数传过来的,如果有 PendingIntent 的话,代表应用通过 PendingIntent 接收回调
参数四 PackageName
PackageName 表示函数调用方的包名
3.1.4 API 使用简单示例
* 在应用的 AndroidManifest.xml 中添加权限
* 设置位置监听
private LocationListener locationListener = new LocationListener() {
// 位置变化时触发
public void onLocationChanged(Location location) {
mLocation = location;
Log.i(TAG, "时间" + location.getTime());
Log.i(TAG, "经度" + location.getLongitude());
Log.i(TAG, "纬度" + location.getLatitude());
Log.i(TAG, "海拔" + location.getAltitude());
}
// GPS 状态变化时触发
public void onStatusChanged(String provider, int status, Bundle extras) {
switch (status) {
case LocationProvider.AVAILABLE: {
Log.i(TAG, "当前 GPS 状态为 可见");
break;
}
case LocationProvider.OUT_OF_SERVICE: {
Log.i(TAG, "当前 GPS 状态为 超出服务区");
break;
}
case LocationProvider.TEMPORARILY_UNAVAILABLE: {
Log.i(TAG, "当前 GPS 状态为 暂停服务");
break;
}
}
}
// GPS 开启时触发
public void onProviderEnable(String provider) {
if (ActivityCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
mContext.requestPermissions(new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, 1000);
}
if (ActivityCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
mContext.requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 1001);
}
Location location = mLocationManager.getLastKnownLocation(provider);
mLocation = location;
}
// GPS 禁用时触发
public void onPorviderDisabled(String provider) {
mLocation = null;
}
};
* 配置定位请求 getCriteria()
private static Criteria getCriteria() {
Criteria criteria = new Criteria();
criteria.setAccuracy(Criteria.ACCURACY_FINE); // 设置定位精确度,ACCURACY_COARSE比较粗略;ACCURACY_FINE比较精确
criteria.setSpeedAccuracy(0); // 设置是否要求速度
criteria.setCostAllowed(false); // 设置是否允许运行商收费
criteria.setBearingAccuracy(0); // 设置是否需要方位信息
criteria.setAltitudeRequired(false); // 设置是否需要海拔信息
criteria.setPowerRequirement(Criteria.POWER_LOW); // 设置对电源的需求
return criteria;
}
* 获取 LocationManager 位置信息
LocationManager mLocationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
// 为获取地理位置信息时设置查询条件
String bestProvider = mLocationManager.getBestProvider(getCriteria(), true);
Log.e(TAG,"bestProvider = " + bestProvider);
// 获取当前位置
Location location = mLocationManager.getLastKnownLocation(bestProvider);
Log.e(TAG,"getLastKnownLocation = " + location);
// 调用位置请求函数,持续请求位置信息
mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,1000,1,locationListener);
以上步骤是应用触发位置请求流程的基本步骤。
下一篇:Android GNSS 模块分析(二)Framework 层
如果文中有错,烦请告知作者,必当有则改之,无则加勉。在此,小弟先行告谢!
联系作者: