在 Flutter 中“过滤相册里包含经纬度(GPS EXIF)信息的图片”,本质分三步:
1️⃣ 获取相册访问授权 2️⃣ 读取相册图片列表 3️⃣ 读取图片 EXIF → 判断是否包含 GPS 经纬度
下面给你一套可落地方案(iOS / Android 都适用)。
一、所需依赖(核心)
dependencies:
photo_manager: ^3.0.0 # 相册读取 & 权限
exif: ^3.1.4 # 解析 EXIF(GPS)
flutter pub get
二、相册权限获取(非常关键)
1️⃣ Flutter 代码(统一)
Future<bool> requestPhotoPermission() async {
final PermissionState ps = await PhotoManager.requestPermissionExtend(
requestOption: const PermissionRequestOption(
iosAccessLevel: IosAccessLevel.readWrite,
androidPermission: AndroidPermission.storage,
),
);
return ps.isAuth;
}
⚠️ iOS 14+ 必须 readWrite 才能读 EXIF ⚠️ Android 13+ 走
READ_MEDIA_IMAGES
2️⃣ iOS 权限配置(Info.plist)
<key>NSPhotoLibraryUsageDescription</key>
<string>需要访问相册以读取图片位置信息</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>需要访问相册以处理图片</string>
3️⃣ Android 权限配置
AndroidManifest.xml
<!-- Android 13+ -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
<!-- Android 12 及以下 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
三、读取相册图片
Future<List<AssetEntity>> loadAllImages() async {
final List<AssetPathEntity> paths =
await PhotoManager.getAssetPathList(
type: RequestType.image,
onlyAll: true,
);
if (paths.isEmpty) return [];
return await paths.first.getAssetListPaged(
page: 0,
size: 1000, // 按需分页
);
}
四、读取 EXIF 并判断是否包含经纬度(核心逻辑)
1️⃣ 判断函数
Future<bool> hasGpsLocation(AssetEntity asset) async {
final file = await asset.file;
if (file == null) return false;
final bytes = await file.readAsBytes();
final tags = await readExifFromBytes(bytes);
return tags.containsKey('GPS GPSLatitude') &&
tags.containsKey('GPS GPSLongitude');
}
2️⃣ 过滤相册中“有经纬度的图片”
Future<List<AssetEntity>> filterImagesWithLocation() async {
final images = await loadAllImages();
List<AssetEntity> result = [];
for (final asset in images) {
if (await hasGpsLocation(asset)) {
result.add(asset);
}
}
return result;
}
五、获取真实经纬度数值(可选)
EXIF 里经纬度是 分数格式,需转换:
double _convertToDegree(List values) {
double d = values[0].numerator / values[0].denominator;
double m = values[1].numerator / values[1].denominator;
double s = values[2].numerator / values[2].denominator;
return d + (m / 60.0) + (s / 3600.0);
}
Future<Map<String, double>?> getLatLng(AssetEntity asset) async {
final file = await asset.file;
if (file == null) return null;
final tags = await readExifFromBytes(await file.readAsBytes());
if (!tags.containsKey('GPS GPSLatitude')) return null;
final latValues = tags['GPS GPSLatitude']!.values.toList();
final lonValues = tags['GPS GPSLongitude']!.values.toList();
double lat = _convertToDegree(latValues);
double lon = _convertToDegree(lonValues);
if (tags['GPS GPSLatitudeRef']?.printable == 'S') lat = -lat;
if (tags['GPS GPSLongitudeRef']?.printable == 'W') lon = -lon;
return {'lat': lat, 'lng': lon};
}
六、性能 & 实战建议(非常重要)
⚠️ EXIF 解析是重操作
❌ 不要主线程跑 ❌ 不要一次性扫几千张
✅ 推荐方案
| 场景 | 建议 |
|---|---|
| 首次扫描 | 分页 + Isolate |
| 二次打开 | 结果缓存(本地DB) |
| 大相册 | 只扫近 3 个月 |
| 实时筛选 | 先用 asset.latitude != null(粗筛) |
🚀 更快的「粗筛」(photo_manager 自带)
if (asset.latitude != null && asset.longitude != null) {
// 直接有位置信息(无需 EXIF)
}
iOS / Android 部分机型支持,但 不 100% 准
七、整体流程图
申请相册权限
↓
读取图片列表
↓
EXIF解析
↓
判断 GPS 是否存在
↓
输出包含经纬度的图片
