qs_ip_location
用于查询当前公网 IP 及其地理信息的 Flutter 插件,支持 Android 和 iOS。通过多个 IP 信息服务自动回退,将不同服务的响应统一为 QsIpLocationModel,并在本地缓存成功结果。
IP 地理信息反映网络出口的大致位置,不代表设备的 GPS 精确位置。
功能
- 支持 IPv4 和 IPv6 地址校验。
- 按顺序尝试 5 个 IP 信息服务,失败后自动切换。
- 统一国家、地区、城市、时区和经纬度字段。
- 每个接口的连接与接收超时时间均为 30 秒。
- 成功结果缓存 24 小时。
- 同一 isolate 内的并发调用共享一次查询。
安装
在 pubspec.yaml 中添加:
dependencies:
qs_ip_location: ^1.0.1
然后执行:
flutter pub get
当前版本要求 Dart ^3.11.5,请使用包含兼容 Dart SDK 的 Flutter 版本。当前插件声明支持 Android、iOS,不支持 Web。
平台配置
Android
确认应用的 android/app/src/main/AndroidManifest.xml 在 <manifest> 下声明网络权限:
<uses-permission android:name="android.permission.INTERNET" />
插件自身的 Manifest 未声明该权限,需要由宿主应用提供。
iOS
查询通过网络请求完成,不调用系统定位 API,无需为本插件添加定位权限描述。
请求列表中的首个接口使用 HTTP。如果宿主平台的网络策略阻止该请求,插件会尝试后续 HTTPS 接口;不必为了使用本插件而全局放开明文网络访问。
快速开始
import 'package:qs_ip_location/qs_ip_location.dart';
Future<void> loadIpLocation() async {
final location = await QsIpLocation.getIpLocation();
if (location == null) {
print('暂时无法获取 IP 信息');
return;
}
print('IP:${location.ip}');
print('国家:${location.countryName ?? "未知"}');
print('地区:${location.regionName ?? "未知"}');
print('城市:${location.cityName ?? "未知"}');
print('经纬度:${location.lon}, ${location.lat}');
}
若在 runApp() 之前调用,请先执行 WidgetsFlutterBinding.ensureInitialized(),以初始化缓存所需的平台通道。
返回结果
static Future<QsIpLocationModel?> getIpLocation()
- 查询的是当前网络出口 IP,不接受指定 IP 参数。
- 存在有效缓存时直接返回缓存;否则依次请求服务。
- 返回模型时,
ip已通过 IPv4 或 IPv6 校验;其他字段可能为null。 - 仅包含合法 IP 的响应也会视为成功,不会继续请求其他服务来补全地理字段。
- 全部接口失败时返回
null。缓存读写失败不丢弃本次网络查询成功的结果。
数据模型
入口文件同时导出 QsIpLocationModel,无需额外导入模型文件。所有构造参数均为可选命名参数,所有属性均为只读、可空字段。
| 字段 | 类型 | 说明 |
|---|---|---|
ip |
String? |
公网 IP 地址 |
countryName |
String? |
国家名称 |
countryCode |
String? |
国家代码 |
regionName |
String? |
地区名称,例如省、州 |
regionCode |
String? |
地区代码 |
cityName |
String? |
城市名称 |
cityCode |
String? |
城市代码 |
timezone |
String? |
时区 |
lat |
double? |
纬度 |
lon |
double? |
经度 |
字段内容取决于实际响应的服务。名称、代码及语言格式未进行额外标准化;部分服务缺少独立代码字段时,会回退使用其国家或地区字段。
JSON 转换
import 'dart:convert';
import 'package:qs_ip_location/qs_ip_location.dart';
void convertLocation() {
final model = QsIpLocationModel.fromJson(
json: {
'query': '8.8.8.8',
'countryCode': 'US',
'city': 'Example City',
'latitude': '37.0',
'longitude': '-122.0',
},
);
final jsonString = jsonEncode(model.toJson());
final restored = QsIpLocationModel.fromJson(
json: jsonDecode(jsonString) as Map<String, dynamic>,
);
print(restored.ip);
}
fromJson(json: ...) 支持不同服务的字段别名、字符串首尾空白处理、数字字符串坐标以及 loc 形式的坐标(例如 "37.0,-122.0")。包含两个分量的 loc 会覆盖独立经纬度字段。
toJson() 输出上述 10 个统一字段,包含值为 null 的字段。手动构造模型或调用 fromJson 不会校验 IP;IP 校验由 getIpLocation() 的查询与缓存读取流程执行。
请求策略
按以下顺序请求,首个有效结果返回后停止:
| 顺序 | 接口 |
|---|---|
| 1 | http://ip-api.com/json/ |
| 2 | https://free.freeipapi.com/api/json/ |
| 3 | https://ipinfo.io/json |
| 4 | https://ipv4-check-perf.radar.cloudflare.com/api/info |
| 5 | https://ipapi.co/json/ |
网络请求通过 qs_net_request 完成,仅接受 HTTP 200 响应。网络异常、超时、无效 JSON、非对象响应或非法 IP 都会触发下一个接口。同一次查询不会循环重试整个接口列表。
接口列表及超时时间在当前版本中固定,没有公开的自定义服务、API Key 或超时配置入口。插件请求第三方服务时会向该服务暴露网络出口 IP;服务可用性、数据完整性和响应速度取决于服务本身及运行网络。
缓存与并发
成功结果通过 SharedPreferences 缓存 24 小时。过期、损坏、缺失时间戳、未来时间戳或包含非法 IP 的缓存都会重新查询。当前版本不提供强制刷新或清除缓存的公开方法,因此切换网络后仍可能在有效期内返回此前的结果。
同一 isolate 的并发调用共享正在执行的 Future,查询完成后释放。不同 isolate 不共享锁,SharedPreferences 不提供跨 isolate 的事务保证。
从旧模型迁移
如果使用过旧版模型,请调整以下字段访问:
| 旧字段 | 当前字段 |
|---|---|
country |
countryName |
region |
regionCode |
city |
cityName |
query |
ip |
status、zip、isp、org、as 已移除,不提供兼容别名。旧位置缓存和独立公网 IP 缓存在查询时清理,不迁移旧值。
项目地址
源代码与问题反馈:GitHub。