最近在写一个类似微信的相册功能,需要读取照片和视频,支持多文件夹切换,且速度要比微信快。调研后发现基于 MediaStore 的方案最为合适。以前用得不多,特此记录。
ContentResolver 对 GROUP BY 的特殊处理
ContentResolver.query() 没有提供 groupBy 参数(与 SQLiteQueryBuilder.query() 不同),但可以通过在 selection 参数中嵌入 GROUP BY 来实现类似效果。
原理是 ContentResolver 会在编译 SQL 时给 selection 自动加上括号包裹,形成 WHERE ( ... )。利用这一点,可以在 selection 中提前闭合括号,然后追加 GROUP BY 子句。
| |
生成的 SQL 变为:
| |
注意: 这种方式在 Android 10(API 29)之后可能失效。系统
MediaProvider会额外注入is_pending=0、is_trashed=0、volume_name IN (...)等条件,可能导致GROUP BY被错误地放入 WHERE 子句内部。在 Android 14+ 上该 hack 已确定不可用。
现代方案:Android 11+ Bundle 参数
从 Android 11(API 30)开始,ContentResolver.query() 支持通过 Bundle 传递结构化查询参数,无需再使用上述 hack。
| |
推荐使用 QUERY_ARG_GROUP_COLUMNS(结构化参数)而非 QUERY_ARG_SQL_GROUP_BY(原始 SQL),以保证向前兼容。Provider 会通过 Cursor 的 EXTRA_HONORED_ARGS 告知哪些参数已生效。
补充:Android 10 的临时方案
如果仍需支持 API 29,可以考虑以下方案:
- 使用
ContentResolver.query(uri, projection, selection, selectionArgs, sortOrder)配合原始 SQL — 在 selection 中内联子查询 - 客户端分组 — 不使用 GROUP BY,全部查询后在内存中手动分组
- 使用自定义 ContentProvider — 自行控制 SQL 查询逻辑
注意事项
- 传统的 selection hack 在不同 OEM 和 Android 版本上行为可能不一致,务必充分测试。
- 如果目标 API 30+,应优先使用 Bundle 方案。
- 自定义
ContentProvider时,可直接使用SQLiteQueryBuilder.query(),它原生支持groupBy和having参数。