Android GPS 定位详解:权限、双 API 与坐标系转换

QuibblerQuibbler 2020-05-18 约 24 分钟 188 次阅读

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 设备上正常工作、能否在国内地图上准确落点、能否在不拖垮电量的情况下持续追踪。把这几环做扎实,再复杂的定位场景都能在这个基础上扩展。

相关推荐

精选
ViewPager和PagerAdapter、FragmentPagerAdapter、FragmentStatePager
Android

ViewPager和PagerAdapter、FragmentPagerAdapter、FragmentStatePager

ViewPager1、ViewPagerandroidx.viewpager.widget.ViewPager,Android中使用非常广泛的控件,可以说是APP必备:首次打开引导页、页面Banner广告等。常用方法:setAdapter() 设置适配器setOffscreenPageLimit() 设置缓存的页面个数,默认是 1setCurrentItem() 跳转到特定的页面setOnPage

1.1k
获取Android内置WebView内核版本
Android

获取Android内置WebView内核版本

获取Android内置WebView内核版本竟然能遇到这样奇葩的事情,网页用的技术过于新颖,以至于只支持高版本Chromium内核的,低版本安卓系统中内置的内核版本较低,无法加载前端页面。1、设置查看在系统设置里 > 应用 > 应用管理 > 显示系统应用,查看WebView组件:2、页面查看通过WebView发起的网络请求,都会带上浏览器的UA,通常页面都可以通过UA判断浏览器的内核版本。这里有两

1.1w
Android 14适配总结
Android

Android 14适配总结

Android 14适配总结毕业工作至今已经适配了三个Android大版本,从Android 11到Android 12、再到Android 13。2023年,Google即将推出的Android 14,上半年已经开始第一批适配。现在,第四个Android版本已经适配完,总结记录一下。1、Android 14计划Google一般会在2月份对外发布预告,同时放出开发者预览版。“拉通”各大平台、厂商以

1.1w