最近在做一个 Flutter 项目,需要实现:

  • 获取用户当前位置
  • 展示当前位置名称
  • 支持 iOS 和 Android
  • 用户拒绝授权时能够降级处理

刚开始以为很简单:


Geolocator.getCurrentPosition()

结果真正落地后才发现,定位功能远比想象中复杂:

  • Android 和 iOS 权限配置不同
  • 用户可能拒绝定位授权
  • 国内地图坐标系和 GPS 坐标系不一致
  • API Key 不能直接写进客户端
  • 定位失败时需要有降级方案

本文把完整实现流程记录下来。


一、项目依赖

dependencies:
  flutter:
    sdk: flutter

  geolocator: ^10.1.0
  permission_handler: ^11.0.1
  http: ^1.1.0
flutter pub get

二、配置定位权限

Android

android/app/src/main/AndroidManifest.xml

<manifest>

    <uses-permission
        android:name="android.permission.ACCESS_FINE_LOCATION"/>

    <uses-permission
        android:name="android.permission.ACCESS_COARSE_LOCATION"/>

</manifest>

如果是骑手、轨迹追踪等场景,可以额外增加:

<uses-permission
    android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>

普通 App 不建议申请后台定位。

iOS

ios/Runner/Info.plist

<key>NSLocationWhenInUseUsageDescription</key>
<string>用于获取当前位置信息</string>

<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>用于持续获取位置服务</string>

这里漏配置是最常见的问题之一。

很多人遇到:

定位无响应

实际上是权限描述没有配置。

三、封装定位服务

创建:

lib/utils/location_service.dart
import 'package:flutter/foundation.dart';
import 'package:geolocator/geolocator.dart';

class LocationService {

  static Future<Position?> getCurrentPosition() async {

    final serviceEnabled =
        await Geolocator.isLocationServiceEnabled();

    if (!serviceEnabled) {
      await Geolocator.openLocationSettings();
      return null;
    }

    LocationPermission permission =
        await Geolocator.checkPermission();

    if (permission == LocationPermission.denied) {
      permission =
          await Geolocator.requestPermission();
    }

    if (
      permission == LocationPermission.denied ||
      permission == LocationPermission.deniedForever
    ) {
      await Geolocator.openAppSettings();
      return null;
    }

    try {

      return await Geolocator.getCurrentPosition(
        desiredAccuracy: LocationAccuracy.high,
        timeLimit: const Duration(seconds: 8),
      );

    } catch (e) {

      debugPrint('定位失败: $e');
      return null;
    }
  }
}

四、为什么定位成功了地图还会偏移?

这是国内定位开发最容易踩的坑。

很多开发者拿到经纬度后直接使用

position.latitude
position.longitude

然后发现:

地图标点偏移
位置不准确

原因是:

GPS原始坐标:WGS84

国内地图坐标:
GCJ02

两者并不一致。

在国内地图场景中,建议统一转换为 GCJ02 后再进行展示和计算。

五、为什么坐标转换要放后端?

很多项目喜欢:

客户端直接请求地图服务

这样虽然开发快,但有一个问题:

API Key暴露

只要别人反编译 APK:

Key直接泄露

后续可能产生大量盗刷。

因此推荐:

Flutter
   ↓
业务后端
   ↓
位置服务

统一由后端代理调用。

六、后端实现坐标转换与逆地理编码

Node.js 示例:

require('dotenv').config()

const express = require('express')
const axios = require('axios')

const app = express()

app.use(express.json())

统一请求头:

const LTS_HEADERS = {
  Authorization: `Bearer ${process.env.LTS_API_KEY}`,
  'Content-Type': 'application/json',
}

接口:

app.post('/api/location/resolve', async (req, res) => {

  const { lat, lng } = req.body

  try {

    const convRes = await axios.post(
      'https://lts.maiyun.net/api/service/geoconv',
      {
        from: 0,
        points: [{ lat, lng }]
      },
      {
        headers: LTS_HEADERS
      }
    )

    if (
      convRes.data.result !== 1 ||
      !convRes.data.list?.length
    ) {
      throw new Error('坐标转换失败')
    }

    const gcj02 = convRes.data.list[0]

    const geoRes = await axios.post(
      'https://lts.maiyun.net/api/service/geocode',
      {
        from: 3,
        point: {
          lat: gcj02.lat,
          lng: gcj02.lng
        }
      },
      {
        headers: LTS_HEADERS
      }
    )

    res.json({
      gcj02Lat: gcj02.lat,
      gcj02Lng: gcj02.lng,
      address:
        geoRes.data.address?.name ?? ''
    })

  } catch (e) {

    res.status(500).json({
      error: '位置解析失败'
    })
  }
})

七、定位失败怎么办?

真实用户环境里

关闭GPS
拒绝授权
系统限制
弱网环境

都可能导致定位失败。

因此建议增加降级策略。

例如:

GPS定位失败
      ↓
IP粗略定位
      ↓
用户手动选择位置

不要把定位成功率完全押在 GPS 上。

八、Flutter调用后端

final result =
  await ApiService.resolveLocation(
    position.latitude,
    position.longitude,
  );

获取返回结果:

setState(() {

  _lat =
    (result['gcj02Lat'] as num).toDouble();

  _lng =
    (result['gcj02Lng'] as num).toDouble();

  _address =
    result['address'] ?? '';

});

注意:

as num

不要直接:

as double

否则后端返回整数时容易出现:

type int is not a subtype of double

九、几个真实踩坑记录

问题原因解决方案
iOS定位无响应Info.plist缺权限描述补齐NSLocation字段
Android无法定位Manifest缺权限声明增加ACCESS_FINE_LOCATION
地图标点偏移WGS84直接展示转GCJ02
定位一直等待没设置超时增加timeLimit
API被盗刷Key写在客户端后端代理
定位失败白屏没做降级增加IP定位和手动选择

十、总结

一个看似简单的定位功能,实际涉及:

  • 权限管理
  • 系统兼容
  • 坐标转换
  • 地址解析
  • 接口安全
  • 异常降级

真正上线后,往往不是定位接口本身最耗时间,而是各种边界情况处理。

本文示例中使用的位置服务接口完成坐标转换和逆地理编码,同类能力在地图服务平台中都比较常见。选择时重点关注:

  • 坐标转换支持情况
  • 逆地理编码准确率
  • 接口稳定性
  • 调用成本

把这些基础能力做好之后,附近门店、轨迹记录、顺风车、配送、骑手定位等场景基本都能顺利落地。

更多推荐