Android GPS 定位详解:权限、双 API 与坐标系转换
定位是地图、出行、运动、天气类 App 的基础能力。Android 提供了两套定位 API——原生 LocationManager(基于 GPS/网络等硬件 Provider)与 Google 的 FusedLocationProviderClient(融合定位,更省电、更精准),再叠加运行时权限、国内坐标系偏移等坑,初学者很容易踩雷。本文按"原理选型 → 权限 → 两套 API → 数据解析 → 坐标系 → 测试省电"的顺序,详细讲解 Android GPS 定位的完整链路。参考 Android 官方位置服务文档。
1、定位原理与 API 选型
Android 的"定位"不只靠 GPS 卫星,而是融合多种信号源,系统会根据环境自动选择最优方案:
信号源 说明 精度 耗电
------------------------------------------------------------------
GPS_PROVIDER 卫星定位,室外准、室内弱 高(几米) 高
NETWORK_PROVIDER 基站 + WiFi 定位,室内可用 中(几十米) 低
PASSIVE_PROVIDER 被动接收其他 App 触发的定位结果 依来源 极低
FusedProvider Google 融合以上信号,自动择优 高 较低对应两套 API:LocationManager(android.location,系统原生,不依赖 Google 服务,国产无 GMS 设备也能用)和 FusedLocationProviderClient(com.google.android.gms.location,Google 融合定位,需 Play Services,体验最佳)。选型原则——有 GMS 优先用 Fused,无 GMS 或需兼容国产设备用 LocationManager。
核心实现(选型要点):
1. GPS 室外准但耗电高、室内无信号;网络定位室内可用但精度低
2. FusedProvider 自动融合 GPS+WiFi+基站,按需切换,省电且精准,是首选
3. LocationManager 是系统 API,无需第三方依赖,国产无 GMS 设备只能用它
4. 两者返回的都是 Location 对象,数据解析方式一致
2、权限配置与运行时申请
定位属于危险权限,分两步:manifest 声明 + 运行时动态申请。Android 6.0 起必须运行时申请,11 起后台定位需单独权限。
<!-- AndroidManifest.xml:声明定位权限 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <!-- 精确(GPS) -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <!-- 粗略(网络) -->
<!-- Android 11+ 后台定位,需先获得前台定位后单独申请 -->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />// 运行时动态申请(Android 6.0+)
private static final int REQ_LOCATION = 100;
void requestLocationPermission() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION},
REQ_LOCATION);
} else {
startLocation(); // 已授权,直接定位
}
}
// 申请结果回调
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] results) {
super.onRequestPermissionsResult(requestCode, permissions, results);
if (requestCode == REQ_LOCATION && results.length > 0
&& results[0] == PackageManager.PERMISSION_GRANTED) {
startLocation();
}
}核心实现:
1. FINE 是精确(GPS),COARSE 是粗略(网络),一般两个都申请以保证兼容
2. Android 6.0+ 必须运行时申请,否则 requestLocationUpdates 不回调
3. Android 11+ 后台定位要 ACCESS_BACKGROUND_LOCATION,且必须在用户已授予前台定位后单独跳系统设置申请
4. 调用任何定位 API 前都要先 checkSelfPermission,避免 SecurityException
3、LocationManager:原生定位 API
系统原生方案,通过 Provider 获取位置。典型流程:拿实例 → 用 Criteria 选 Provider → 注册监听 → 回调拿 Location → 不用时移除。
// 1. 获取 LocationManager
LocationManager lm = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
// 2. 用 Criteria 选择最佳 Provider
Criteria criteria = new Criteria();
criteria.setAccuracy(Criteria.ACCURACY_FINE); // 高精度
criteria.setPowerRequirement(Criteria.POWER_LOW); // 低功耗
criteria.setAltitudeRequired(false);
criteria.setSpeedRequired(false);
String provider = lm.getBestProvider(criteria, true); // true=只返回已开启的
// 3. 取最近一次已知位置(秒出,可能为 null)
Location last = lm.getLastKnownLocation(provider);
// 4. 注册持续定位(minTime 间隔 ms,minDistance 最小位移 m)
lm.requestLocationUpdates(provider, 1000L, 0f, locationListener);
// 5. 回调
private final LocationListener locationListener = new LocationListener() {
@Override public void onLocationChanged(Location location) {
double lat = location.getLatitude();
double lng = location.getLongitude();
// 处理位置
}
@Override public void onStatusChanged(String p, int status, Bundle extras) { }
@Override public void onProviderEnabled(String p) { }
@Override public void onProviderDisabled(String p) { }
};
// 6. 不用时移除监听(onStop / onDestroy),省电
@Override protected void onDestroy() {
lm.removeUpdates(locationListener);
super.onDestroy();
}核心实现:
1. getSystemService(LOCATION_SERVICE) 拿 LocationManager,再 getBestProvider 按 Criteria 选最优 Provider
2. getLastKnownLocation 返回缓存的上次定位,可秒出但可能为 null,适合快速展示
3. requestLocationUpdates(provider, minTime, minDistance, listener) 注册监听,位置变化触发 onLocationChanged
4. 务必在离开时 removeUpdates,否则 GPS 持续工作严重耗电
- 提示:onStatusChanged 在 Android 10 起已废弃,保留空实现即可兼容
4、FusedLocationProviderClient:融合定位(推荐)
Google 融合定位,自动择优 GPS/网络信号,省电且精准。新版无需 GoogleApiClient,直接用 Client + Task 异步回调。
// app/build.gradle 添加依赖
// implementation 'com.google.android.gms:play-services-location:21.0.1'
// 1. 获取客户端(无需 GoogleApiClient)
FusedLocationProviderClient client =
LocationServices.getFusedLocationProviderClient(this);
// 2. 最近一次位置(Task 异步,可能为 null)
if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) == PERMISSION_GRANTED) {
client.getLastLocation().addOnSuccessListener(this, location -> {
if (location != null) {
double lat = location.getLatitude();
}
});
}
// 3. 构建 LocationRequest 配置定位策略
LocationRequest request = LocationRequest.create();
request.setInterval(10000); // 常规间隔 ms
request.setFastestInterval(5000); // 最快间隔 ms
request.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); // 高精度
// 4. 回调
LocationCallback callback = new LocationCallback() {
@Override public void onLocationResult(LocationResult result) {
for (Location location : result.getLocations()) {
// 处理每一次位置更新
}
}
};
// 5. 发起持续定位,绑定主线程 Looper
client.requestLocationUpdates(request, callback, Looper.getMainLooper());
// 6. 离开时移除
client.removeLocationUpdates(callback);核心实现:
1. 引入 play-services-location 依赖,通过 LocationServices 拿 FusedLocationProviderClient
2. getLastLocation() 返回 Task,addOnSuccessListener 异步拿最近位置,可能为 null
3. 持续定位用 LocationRequest 设间隔与精度(PRIORITY_HIGH_ACCURACY / BALANCED / LOW_POWER)
4. requestLocationUpdates 搭配 LocationCallback,回调在指定 Looper(一般主线程)
- 新版(21.x)推荐用 LocationRequest.Builder 替代 create()+setXxx,旧写法仍兼容
5、Location 数据解析
无论哪套 API,返回的 Location 对象字段一致,包含经纬度及丰富的运动信息:
location.getLatitude(); // 纬度
location.getLongitude(); // 经度
location.getAltitude(); // 海拔(米,仅 GPS 有效)
location.getSpeed(); // 速度(米/秒,需 Provider 支持)
location.getBearing(); // 方位角(度,0=正北,顺时针)
location.getAccuracy(); // 精度半径(米,越小越准)
location.getTime(); // 定位时间戳(UTC 毫秒)
// getExtras() 还可取卫星数:bundle.getInt("satellites")核心实现:
1. 经纬度是定位的核心输出,配合 getAccuracy 判断可信度(如精度 > 50m 可丢弃)
2. 海拔、速度、方位角依赖 GPS Provider,网络定位通常为 0
3. getTime 用于判断定位新旧,过滤过期缓存
4. getExtras 可拿到卫星数量等扩展信息,用于定位质量诊断
6、坐标系转换:WGS84 / GCJ02 / BD09
这是国内地图开发最大的坑:GPS 原始坐标是 WGS84,而国内地图(高德、腾讯用 GCJ02,百度用 BD09)做了加密偏移。直接把 GPS 经纬度标在国内地图上,会偏移几十到几百米,必须转换。
坐标系 来源 使用场景
---------------------------------------------------------
WGS84 GPS 卫星原始坐标(国际标准) 谷歌地球、国际地图、GPS 设备
GCJ02 国测局加密(火星坐标) 高德、腾讯、谷歌中国
BD09 百度在 GCJ02 上二次加密 百度地图
// 关系:WGS84 --(国测局加密)--> GCJ02 --(百度加密)--> BD09// WGS84 转 GCJ02 核心算法(eviltransform,国测局加密公开实现)
private static final double A = 6378245.0;
private static final double EE = 0.00669342162296594323;
static double[] wgs84ToGcj02(double wgsLat, double wgsLng) {
if (outOfChina(wgsLat, wgsLng)) return new double[]{wgsLat, wgsLng}; // 国外不偏移
double dLat = transformLat(wgsLng - 105.0, wgsLat - 35.0);
double dLng = transformLng(wgsLng - 105.0, wgsLat - 35.0);
double radLat = wgsLat / 180.0 * Math.PI;
double magic = Math.sin(radLat);
magic = 1 - EE * magic * magic;
double sqrtMagic = Math.sqrt(magic);
dLat = (dLat * 180.0) / ((A * (1 - EE)) / (magic * sqrtMagic) * Math.PI);
dLng = (dLng * 180.0) / (A / sqrtMagic * Math.cos(radLat) * Math.PI);
return new double[]{wgsLat + dLat, wgsLng + dLng}; // 返回 {gcjLat, gcjLng}
}
// outOfChina / transformLat / transformLng 为辅助公式,开源库可直接复用核心实现:
1. GPS/LocationManager/Fused 返回的都是 WGS84,画到国内地图前必须转 GCJ02/BD09
2. 高德/腾讯用 GCJ02,百度用 BD09(GCJ02 再做一次偏移)
3. 国外坐标不偏移,outOfChina 判断后直接返回原值
4. 实际项目建议直接用地图 SDK 自带的坐标转换接口,或开源库 eviltransform,避免手写出错
7、模拟器测试与省电注意事项
真机测试受环境限制大,开发期推荐用模拟器注入位置;上线则要把省电和体验做扎实。
// 模拟器注入位置:Extended Controls(右侧三点) -> Location
// 可单点发送,也可导入 GPX/KML 路线模拟移动轨迹
// 真机:开发者选项 -> 选择模拟位置信息应用(指定一个 Mock Location App)注意事项与省电:
1. 不定位时一定 removeUpdates / removeLocationUpdates,GPS 持续唤醒是耗电大户
2. 室内/高楼旁 GPS 信号弱,首次定位慢(TTFF 可达几十秒),先用 getLastKnownLocation 兜底展示
3. requestLocationUpdates 的 minTime / minDistance 不要设太小,间隔越短越耗电
4. 后台定位受 Android 10/11 限制,需 ACCESS_BACKGROUND_LOCATION 并引导用户到设置开启
- 国内地图务必做坐标系转换;无 GMS 设备(多数国产机)只能用 LocationManager
8、总结
Android 定位的核心,是在"权限 → API → 数据 → 坐标"四个环节都做对。选对 API、申请好权限、解析 Location、转换坐标系,定位功能就立得住。
关键要点:
- 两套 API:有 GMS 用 FusedLocationProviderClient,无 GMS 用 LocationManager
- 权限:FINE+COARSE 声明,6.0+ 运行时申请,11+ 后台定位单独申请
- 流程:拿实例 → 选 Provider/Criteria → requestLocationUpdates → 回调解析 → removeUpdates
- Location 含经纬度/海拔/速度/方位/精度;getLastLocation 可为 null,注意判空
- 国内地图:WGS84(GPS) 必须转 GCJ02(高德腾讯)/BD09(百度),否则偏移几百米
- 省电:不定位必移除监听;合理设间隔;用模拟器注入位置测试
对于做地图、出行、运动轨迹、LBS 服务的开发者而言,把"权限申请 + 双 API 选型 + 坐标系转换"这条链路打通是必要的——它决定了定位功能能否在国产无 GMS 设备上正常工作、能否在国内地图上准确落点、能否在不拖垮电量的情况下持续追踪。把这几环做扎实,再复杂的定位场景都能在这个基础上扩展。

