tencent_location_flutter

tencent_location_flutter 是面向业务应用的 Flutter 腾讯定位主包。

非官方声明:本包不是腾讯官方包,也不代表腾讯地图或腾讯位置服务官方 SDK。 本包只是面向 Flutter 的第三方封装;腾讯、腾讯地图、Tencent Location SDK 等名称、商标、原生 SDK 与官方接口语义归其权利方所有。

它提供:

  • 初始化、隐私同意、SDK 信息、授权状态、单次定位、连续定位
  • 统一入口:朝向、距离计算、圆形范围判断、坐标转换、取消单次定位、回调频率调整、设备 ID、自定义 SDK 数据、DR 惯导
  • 统一入口:圆形/多边形/行政区划围栏、围栏事件流
  • Android 命名空间:场景化定位、前台服务通知、最近一次定位、GPS 支持判断、OAID、系统缓存开关、原生库加载开关
  • iOS 命名空间:权限申请、临时完整精度授权、原生朝向校准界面控制
  • 强类型模型、事件流,以及统一的跨平台调用入口

当前固定原生版本:

  • Android:7.6.1.8
  • iOS:4.3.1

Android 8.7.5.1 已做过编译验证,但该版本线缺少围栏、场景化定位、DR、系统缓存等当前需要开放的公开 SDK API;默认版本固定在仍覆盖这些能力的 7.x 版本线最新版本。iOS 4.3.1 随包 xcf 已公开 geofence 头文件,插件通过 SwiftPM 开放统一地理围栏能力。

安装

当前仓库地址:

  • 浏览地址:https://cnb.cool/nazo/tencent_location_flutter
  • Git 地址:https://cnb.cool/nazo/tencent_location_flutter.git

业务应用只依赖这一个包:

dependencies:
  tencent_location_flutter:
    git:
      url: https://cnb.cool/nazo/tencent_location_flutter.git
      path: packages/flutter_tencent_location

说明:

  • 宿主不需要额外修改 build.gradle
  • 宿主不需要创建或修改 Podfile;Flutter 3.44+ 按 SwiftPM 解析 iOS 插件
  • 宿主不需要手工下载腾讯 Android/iOS SDK

宿主配置

Android

至少声明这些权限:

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />

如果要做后台定位或前台服务,再补充:

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

iOS

至少声明:

  • NSLocationWhenInUseUsageDescription
  • NSLocationAlwaysAndWhenInUseUsageDescription

如果要临时完整精度定位,再补:

  • NSLocationTemporaryUsageDescriptionDictionary

如果要后台定位,再打开:

  • Background Modes > Location updates

说明:

  • iOS Simulator 不支持腾讯定位 SDK,请直接用真机验证
  • 即使插件已经内置 iOS SDK,权限声明依然必须由宿主自己完成

快速开始

final TencentLocation location = TencentLocation.instance;

await location.setUserAgreePrivacy(true);
await location.initialize(
  const InitializeOptions(
    key: 'YOUR_TENCENT_LOCATION_KEY',
    coordinateType: TencentLocationCoordinateType.gcj02,
    requestLevel: TencentLocationRequestLevel.adminName,
    android: AndroidInitOptions(
      locationMode: TencentAndroidLocationMode.highAccuracy,
      allowGps: true,
    ),
    ios: IosManagerOptions(
      allowsBackgroundLocationUpdates: true,
      pausesLocationUpdatesAutomatically: false,
    ),
  ),
);

final TencentLocationData data = await location.getLocationOnce(
  options: const SingleLocationOptions(),
);

print(data.latitude);
print(data.longitude);
print(data.address);
print(data.addressDescription);

关于地址描述

如果你的业务目标是“拿一个可直接展示给用户的位置描述”,优先使用:

  • TencentLocationRequestLevel.formattedAddress

对应行为:

  • Android:如果原生 SDK 提供 REQUEST_LEVEL_FORMATTED_ADDRESS 就使用该级别;当前实现会在缺少该常量时回退到 REQUEST_LEVEL_ADMIN_AREA
  • iOS:因为原生 SDK 没有完全等价级别,插件会退化到可返回地址字符串的级别

单次定位在 Android 上还会额外做两件事:

  • 请求级别不是 geo 时,要求首个结果就带地址信息
  • 关闭缓存,并把 SingleLocationOptions.timeoutMillis 映射到原生首个结果超时

字段语义:

  • address 原生 SDK 自己的地址字段
  • addressDescription 插件整理后的“描述地址”,更适合直接展示

如果你主要想拿 POI,请用 TencentLocationRequestLevel.poi,但它不保证一定有 address / addressDescription

连续 geo 定位运行中可以再发起一次单次定位,插件不会主动停止当前连续定位监听。 但 Android 腾讯 SDK 的 requestSingleFreshLocation(...) 在周期性定位已启动时, 返回结果会跟随当前连续请求级别;因此连续 geo 会话中单次 poi 请求通常仍只返回坐标, 不能稳定拿到省市区、街道或 POI。需要这些详情时,应让连续请求级别本身使用 adminName/poi, 或停止连续 geo 会话后再执行单次详情定位。

2026-05-24 的 Android 全功能真机测试已覆盖连续定位、单次地址、heading、围栏、DR 等主能力;连续 geo 中单次 poi 的专项测试在本轮真机环境中连续首包超时, 未取得有效结论。该专项仍保留为设备侧复测入口,用于确认具体机型和网络环境下的真实表现。

连续定位示例

final LocationUpdateSession session = await location.startLocationUpdates(
  const LocationUpdateOptions(
    intervalMillis: 15000,
    requestLevel: TencentLocationRequestLevel.adminName,
    backgroundLocation: true,
    androidRequest: AndroidLocationRequestOptions(
      intervalMillis: 15000,
      locationMode: TencentAndroidLocationMode.highAccuracy,
    ),
    androidForegroundNotification: AndroidForegroundNotificationOptions(
      id: 1001,
      channelId: 'location_foreground',
      channelName: 'Tencent Location',
      title: 'Tencent Location',
      text: 'Continuous location is running',
    ),
  ),
);

session.updates.listen(print);
session.errors.listen(print);

设备朝向

设备朝向通过独立系统传感器链路提供;插件不暴露腾讯定位请求里的 allowDirection,定位结果也不再包含设备朝向字段。GPS bearing 只表示移动方向, 不能作为设备机身朝向使用。 Android 原生层使用 SensorManager,优先监听 TYPE_ROTATION_VECTOR, 其次回退到 TYPE_GEOMAGNETIC_ROTATION_VECTOR,设备不支持旋转向量时才使用 TYPE_ACCELEROMETER + TYPE_MAGNETIC_FIELD 合成朝向。 iOS 原生层使用系统 CLLocationManager.startUpdatingHeading() 获取罗盘 heading, 不走 TencentLBSLocationManager.startUpdatingHeading(),也不使用定位点 course。 采集开启后,插件会以约 500ms 间隔持续推送最后已知朝向;即使设备静止、 系统传感器没有新的角度变化,上层也不需要回退到定位 bearing 来维持方向显示。

await location.startHeadingUpdates();

location.headingUpdates.listen((HeadingData data) {
  print(data.magneticHeading);
});

await location.stopHeadingUpdates();

跨平台官方能力

Android / iOS 语义一致的 SDK 能力会通过 TencentLocation 统一入口暴露, 业务代码不需要感知平台命名空间:

final double distance = await location.distanceBetween(
  startLatitude: 22.5,
  startLongitude: 113.9,
  endLatitude: 22.6,
  endLongitude: 114.0,
);

final bool inside = await location.containsCoordinate(
  centerLatitude: 22.5,
  centerLongitude: 113.9,
  radiusMeters: 100,
  latitude: 22.5001,
  longitude: 113.9001,
);

await location.changeCallbackInterval(3000);
await location.cancelLocationRequest();
await location.setDeviceId('user-or-device-id');
await location.setSdkData('ReGeoCodingnKey', 'YOUR_WEB_SERVICE_KEY');

final bool drSupported = await location.isDrSupported();
if (drSupported) {
  final TencentDrStartCode code = await location.startDrEngine(
    const TencentDrOptions(motionType: TencentDrMotionType.walk),
  );
  final DrPosition? position = await location.getDrPosition();
  await location.stopDrEngine();
}

如果你还需要访问平台专属能力,location.android / location.ios 命名空间只保留无法可靠抽象为跨平台语义的能力,例如场景化定位、OAID 和 iOS 权限等。

中国区域离线判断

final bool inChina = isCoordinateInChina(
  latitude: 22.543096,
  longitude: 114.057865,
);

final bool sameResult = location.isCoordinateInChina(
  latitude: 22.543096,
  longitude: 114.057865,
);

isCoordinateInChina(...) 是纯 Dart 离线函数,覆盖中国大陆、香港、澳门、台湾以及数据源多边形中包含的岛屿。实现使用边界盒、经纬度网格索引和多边形精确包含判断;索引只减少候选多边形与边数量,不用粗略矩形、圆形或坐标偏移规则替代边界判断。

坐标转换使用独立的 isCoordinateInGcj02TransformRegion(...) 作为偏移适用区判断。该函数不是领土判断;香港、澳门、台湾仍会被 isCoordinateInChina(...) 判定为中国区域,但默认不会被套用 GCJ-02 近似偏移。

坐标转换

final TencentCoordinate wgs84 = gcj02ToWgs84(
  latitude: 22.587286,
  longitude: 113.948066,
);

final TencentCoordinate gcj02 = location.convertWgs84ToGcj02(
  latitude: wgs84.latitude,
  longitude: wgs84.longitude,
);

gcj02ToWgs84(...)wgs84ToGcj02(...) 以及 TencentLocation.instance 上的同名语义方法都在 Dart 层离线执行;GCJ-02 偏移适用区域外坐标会原样返回。

正向 WGS84 -> GCJ-02 公式与公开开源 coordtransform 等常见实现同族,是移动互联网地图业务里常见的非官方近似公式;逆向 GCJ-02 -> WGS84 由本插件基于同一正向公式做迭代反解。该能力不依赖 Android/iOS 原生 SDK,也不会发起网络请求。它不是腾讯定位 SDK 的官方双向转换接口,也不是国家公开标准算法。腾讯 iOS SDK 公开了 TencentLBSLocationUtils.WGS84TOGCJ02,但没有对等的 GCJ02TOWGS84;Android 定位 SDK 文档主要提供定位结果坐标系配置,未提供双端对等的独立双向转换工具。

gcj02ToWgs84(...) 通过迭代反解本插件的 wgs84ToGcj02(...),误差边界只表示相对同一近似公式的数值反解误差,不代表相对腾讯 SDK 内部实现或测绘级算法的官方精度保证。该能力适合普通移动定位业务中的坐标系归一、地图展示和轨迹存储前处理,不适用于测绘、法律边界、审计、资产确权或其他要求官方转换参数的场景。

平台专属官方能力

Android:

final String? oaid = await location.android.getOaid();
final bool gpsSupported = await location.android.isGpsSupported();

final LocationUpdateSession sceneSession =
    await location.android.startLocationWithScene(
  TencentAndroidLocationScene.sport,
);

AndroidLocationRequestOptions.smallAppKey 会映射到 Android 原生 TencentLocationRequest.setSmallAppKey(...),用于官方文档中的分渠道或业务场景标识。

iOS:

await location.ios.requestWhenInUseAuthorization();
await location.ios.dismissHeadingCalibrationDisplay();

说明:

  • 原生 SDK 文档中标记为测试、调试或内部使用的接口不作为业务 API 暴露。
  • Android SDK 日志监听、setDebuggable 等诊断入口不进入主包公开接口。
  • 地理围栏只通过统一入口暴露:location.addGeofence(...)location.getValidGeofences()location.removeGeofence(...)location.removeAllGeofences()

进一步阅读