When building a photo album feature similar to WeChat, I needed to read photos and videos with multi-folder switching — and it had to be faster than WeChat. After some research, the MediaStore approach proved most suitable. Since I hadn’t used it much before, this post serves as a record.
The GROUP BY Hack in ContentResolver
ContentResolver.query() does not expose a groupBy parameter (unlike SQLiteQueryBuilder.query()), but you can achieve a similar effect by embedding GROUP BY directly into the selection argument.
The trick relies on the fact that ContentResolver wraps the selection in parentheses during SQL compilation, producing WHERE ( ... ). By closing the parenthesis early in the selection string, you can append a GROUP BY clause.
| |
The resulting SQL looks like:
| |
Warning: This approach may break on Android 10 (API 29) and above. The system
MediaProviderinjects additional conditions such asis_pending=0,is_trashed=0, andvolume_name IN (...), which can incorrectly place theGROUP BYinside the WHERE clause. As of Android 14+, this hack is confirmed broken.
Modern Approach: Android 11+ Bundle Parameters
Starting with Android 11 (API 30), ContentResolver.query() supports structured query parameters via Bundle, eliminating the need for the hack above.
| |
Prefer QUERY_ARG_GROUP_COLUMNS (structured) over QUERY_ARG_SQL_GROUP_BY (raw SQL) for forward compatibility. The Provider indicates which arguments were honored via the Cursor’s EXTRA_HONORED_ARGS.
Workaround for Android 10
If you still need to support API 29, consider these alternatives:
- Use
ContentResolver.query(uri, projection, selection, selectionArgs, sortOrder)with a subquery inline in the selection - Client-side grouping — fetch all results and group in memory
- Custom ContentProvider — take full control of the SQL query logic
Caveats
- The traditional selection hack behaves inconsistently across OEMs and Android versions — test thoroughly.
- If your
minSdkVersionis 30+, prioritize the Bundle approach. - When implementing a custom
ContentProvider, useSQLiteQueryBuilder.query()directly — it natively supportsgroupByandhavingparameters.