以SQL注入的方式高效查询Android设备上所有图片所在的文件
图片选择器相信很多人一定不会陌生,现在只要带点图片功能的app都会做一个选择图片的功能,这就很有可能会遇到这样一个需求,就是先展示出所有图片所在的文件夹,再点击进入文件夹后展示该文件夹下的所有图片,在这里如何获取到Android设备上所有图片所在的文件夹便是一个棘手的问题。
MediaStore
没有提供查询文件夹的方法,只有一个关于文件夹的属性:MediaStore.Files.FileColumns.PARENT
,官方文档的解释是:
The index of the parent directory of the file
Type: INTEGER
虽然并不知道这个index值的实际含义,但至少可以用它来区分不同的文件是不是在同一个文件夹下了。
另外,在使用ContentResolver
查询MediaStore
的数据时,如果参数写错了,异常信息中会暴露出系统编译的SQL语句。
例如执行以下的查询:
context.getContentResolver().query(
MediaStore.Files.getContentUri("external"),
new String[]{"abc", "def"},
MediaStore.Files.FileColumns.MEDIA_TYPE + " = " + MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE,
null,
MediaStore.Files.FileColumns.DATE_MODIFIED + " DESC"
);
会报出这样的崩溃:
android.database.sqlite.SQLiteException: no such column: abc (code 1): , while compiling: SELECT abc, def FROM files WHERE (media_type = 1) ORDER BY date_modified DESC
观察这个SQL语句的结构:
SELECT abc, def FROM files WHERE ( media_type = 1 ) ORDER BY date_modified DESC
我们可以知道调用query()
方法时传入的参数的对应位置,以上加粗的字是系统生成的(包括WHERE
那里的一对小括号),非加粗部分是我们自己传入的参数。
我们不难通过SQL注入的方式,拼凑复杂的查询语句得出我们想要的东西。
现在想要查询出所有图片所在的文件夹,我们只要对MediaStore.Files.FileColumns.PARENT
使用group by
关键字即可,具体的SQL语句如下:
SELECT COUNT(parent), _data
FROM files
WHERE (media_type = 1)
GROUP BY (parent)
其中parent
就是MediaStore.Files.FileColumns.PARENT
的取值;media_type
是MediaStore.Files.FileColumns.MEDIA_TYPE
;_data
是MediaStore.Files.FileColumns.DATA
,表示文件的路径,我们可以通过截取文件的路径获取到文件夹的路径。
转换为Java代码便是如下的查询:
Cursor cursor = context.getContentResolver().query(
MediaStore.Files.getContentUri("external"),
new String[]{
"COUNT(" + MediaStore.Files.FileColumns.PARENT + ")",
MediaStore.Files.FileColumns.DATA,
},
MediaStore.Files.FileColumns.MEDIA_TYPE + " = " + MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE
+ " ) GROUP BY (" + MediaStore.Files.FileColumns.PARENT,
null,
null
);
if (cursor != null) {
while (cursor.moveToNext()) {
int imageFileCountInFolder = cursor.getInt(0);
String imageFilePath = cursor.getString(1);
File folderFile = new File(imageFilePath).getParentFile();
Log.d("test", String.format(Locale.getDefault(), "文件夹路径:%s, 文件夹中的图片个数:%d", folderFile.getAbsolutePath(), imageFileCountInFolder));
}
cursor.close();
}
这里顺便把每个文件夹中的图片个数也查询到了。然而,往往我们还要显示文件夹的封面图片,而且还要是文件夹里最新的一张图片。上面的查询得到的图片是任意的,并没有保证是最新的一张图片。
能否在一次查询中解决这个问题?否则如果多次查询数据库导致多次IO操作,或是在Java代码中处理,都耗时、耗内存。答案是可以的,不过语句要复杂一点。
先写好SQL语句:
SELECT COUNT(parent) AS fileCount, _data
FROM (SELECT * FROM files WHERE (media_type = 1) ORDER BY date_modified)
GROUP BY (parent)
ORDER BY fileCount DESC
这里使用子查询是因为最外层的ORDER BY
针对的是分组后的数据进行排序。组内排序有多种方式,这里我们就使用子查询。转换为Java代码如下:
Cursor cursor = context.getContentResolver().query(
MediaStore.Files.getContentUri("external"),
new String[]{
"COUNT(" + MediaStore.Files.FileColumns.PARENT + ") AS fileCount",
MediaStore.Files.FileColumns.DATA + " FROM (SELECT *",
},
MediaStore.Files.FileColumns.MEDIA_TYPE + " = " + MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE + ")"
+ " ORDER BY " + MediaStore.Files.FileColumns.DATE_MODIFIED + " )"
+ " GROUP BY (" + MediaStore.Files.FileColumns.PARENT,
null,
"fileCount DESC"
);
if (cursor != null) {
while (cursor.moveToNext()) {
int imageFileCountInFolder = cursor.getInt(0);
String latestImageFilePath = cursor.getString(1);
File folderFile = new File(latestImageFilePath).getParentFile();
Log.d("test", "文件夹路径:" + folderFile.getAbsolutePath());
Log.d("test", "文件夹中的图片个数:" + imageFileCountInFolder);
Log.d("test", "文件夹中最新的一张图片的路径:" + latestImageFilePath);
}
cursor.close();
}
查询某个文件夹下的所有图片的代码:
String folderPath = "/storage/emulated/0/Pictures";
Cursor cursor = getContext().getContentResolver().query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
null,
MediaStore.Images.ImageColumns.DATA + " like '%" + folderPath + "%'",
null,
MediaStore.Images.ImageColumns.DATE_MODIFIED + " DESC"
);