Android如何高效地进行文件查询——MediaStore

参考资料:
android手机文件快速扫描,并归类
Android使用MediaStore获取手机上的文件
Android教程之MediaStore
Android之MediaStore使用的点点滴滴//这里有个获取缩略图的,没用过
Android中多媒体文件、文档以及各类文件的获取

一开始想的是遍历所有文件夹,幸好查了度娘,才意识到这个想法有多蠢。
最为节省时间的做法是通过一个ContentProvider:MediaStore进行查询

一、什么是MediaStore

1、概述

MediaStore这个类是android系统提供的一个多媒体数据库,android中多媒体信息都可以从这里提取。
这个数据库中包括 音频、视频、图像、文件等多种类型的文件索引,Android会周期性地检索手机中的文件,添加至该数据库中。(一些应用也可以自发地将文件添加至该数据库中)
所以,我们只需要查询该数据库中的内容即可。

2、MediaStore在哪里?

使用一个root过的手机,或者第三方模拟器,我们可以找到data/data这个路径
在这里插入图片描述
找到/data/data/com.android.providers.media,里面database里存放着两个数据库:external.db和internal.db,这就是MediaStore数据库的本体。

使用db browser等软件可以看的表的真容
在这里插入图片描述
我们需要的文件信息就在这里啦。

二、从MediaStore获取数据

1、添加权限

在Manifest中添加权限

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

这样还不够,由于到Android 10为止,READ_EXTERNAL_STORAGE已经被列为危险权限,所以我们需要在运行时动态获取该权限。
在这里插入图片描述

//ContextCompat.checkSelfPermission()函数,接受两个参数:Context和权限名,返回值等于PackageManager.PERMISSION_GRANTED时说明同意授权
            if(ContextCompat.checkSelfPermission(this, android.Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){
                //请求授权,接受三个参数:Activity实例;请求权限名的String数组;请求码(唯一值即可)
                ActivityCompat.requestPermissions(this, arrayOf(android.Manifest.permission.READ_EXTERNAL_STORAGE), 1)
            }

2、获取每行数据

val contentResolver = context.contentResolver

可以使用contentResolver对象进行查询,通过contentResolver.query()方法获得MediaStore数据库
这个方法接收五个参数,
contentResolver.query()
看起来有点多,后面用的时候再说。

如果不加过滤等条件,查询全部数据只需要把后四个参数置为null即可,第一个参数为一个Uri对象,这里固定输入MediaStore.Files.getContentUri(“external”)即可,表示查询external.db这个库中的全部数据。

val cursor = contentResolver.query(
     MediaStore.Files.getContentUri("external"),
     null,
     null,
     null,
     null
)

contentResolver.query()方法返回一个Cursor对象
Cursor,译为“游标”,对于每个表来说,cursor指代的就是每一行
Cursor的常用方法有:

  • getColumnCount()
    返回所有列的总数

  • getColumnIndex(String columnName)
    返回指定列的名称,如果不存在返回-1

  • getColumnIndexOrThrow(String columnName)
    从零开始返回指定列名称,如果不存在将抛出IllegalArgumentException 异常。

  • getColumnName(int columnIndex)
    从给定的索引返回列名

  • getColumnNames()
    返回一个字符串数组的列名

  • getCount()
    返回Cursor 中的行数

  • moveToFirst()
    移动光标到第一行

  • moveToLast()
    移动光标到最后一行

  • moveToNext()
    移动光标到下一行,它返回的boolean值的意义为,当为真时表明光标移动成功,为false时说明移动失败,即没有移动成功。

  • moveToPosition(int position)
    移动光标到一个绝对的位置

  • moveToPrevious()
    移动光标到上一行

3、确定每列的含义。

我们可以使用cursor.getColumnNames()这个方法来返回所有列的名称,从而查看这个表中每列的索引和它所代表的内容

val name = cursor.columnNames
for (n in name){
    Log.d("myTest", "${cursor.getColumnIndexOrThrow(n)} -> $n")
}

打印结果如下:

2020-10-06 21:58:43.926 6983-7059/com.lrgy.photoquerytestdemo D/myTest: 0 -> _id//主键id
2020-10-06 21:58:43.926 6983-7059/com.lrgy.photoquerytestdemo D/myTest: 1 -> _data//路径
2020-10-06 21:58:43.926 6983-7059/com.lrgy.photoquerytestdemo D/myTest: 2 -> _size//大小
2020-10-06 21:58:43.926 6983-7059/com.lrgy.photoquerytestdemo D/myTest: 3 -> format
2020-10-06 21:58:43.926 6983-7059/com.lrgy.photoquerytestdemo D/myTest: 4 -> parent
2020-10-06 21:58:43.926 6983-7059/com.lrgy.photoquerytestdemo D/myTest: 5 -> date_added//添加日期
2020-10-06 21:58:43.926 6983-7059/com.lrgy.photoquerytestdemo D/myTest: 6 -> date_modified//修改日期
2020-10-06 21:58:43.927 6983-7059/com.lrgy.photoquerytestdemo D/myTest: 7 -> mime_type//文件类型
2020-10-06 21:58:43.927 6983-7059/com.lrgy.photoquerytestdemo D/myTest: 8 -> title
2020-10-06 21:58:43.927 6983-7059/com.lrgy.photoquerytestdemo D/myTest: 9 -> description
2020-10-06 21:58:43.927 6983-7059/com.lrgy.photoquerytestdemo D/myTest: 10 -> _display_name
2020-10-06 21:58:43.927 6983-7059/com.lrgy.photoquerytestdemo D/myTest: 11 -> picasa_id
2020-10-06 21:58:43.927 6983-7059/com.lrgy.photoquerytestdemo D/myTest: 12 -> orientation
2020-10-06 21:58:43.927 6983-7059/com.lrgy.photoquerytestdemo D/myTest: 13 -> latitude
2020-10-06 21:58:43.927 6983-7059/com.lrgy.photoquerytestdemo D/myTest: 14 -> longitude
2020-10-06 21:58:43.927 6983-7059/com.lrgy.photoquerytestdemo D/myTest: 15 -> datetaken
2020-10-06 21:58:43.927 6983-7059/com.lrgy.photoquerytestdemo D/myTest: 16 -> mini_thumb_magic
2020-10-06 21:58:43.927 6983-7059/com.lrgy.photoquerytestdemo D/myTest: 17 -> bucket_id
2020-10-06 21:58:43.927 6983-7059/com.lrgy.photoquerytestdemo D/myTest: 18 -> bucket_display_name
2020-10-06 21:58:43.927 6983-7059/com.lrgy.photoquerytestdemo D/myTest: 19 -> isprivate
2020-10-06 21:58:43.927 6983-7059/com.lrgy.photoquerytestdemo D/myTest: 20 -> title_key
2020-10-06 21:58:43.927 6983-7059/com.lrgy.photoquerytestdemo D/myTest: 21 -> artist_id
2020-10-06 21:58:43.927 6983-7059/com.lrgy.photoquerytestdemo D/myTest: 22 -> album_id
2020-10-06 21:58:43.927 6983-7059/com.lrgy.photoquerytestdemo D/myTest: 23 -> composer
2020-10-06 21:58:43.928 6983-7059/com.lrgy.photoquerytestdemo D/myTest: 24 -> track
2020-10-06 21:58:43.928 6983-7059/com.lrgy.photoquerytestdemo D/myTest: 25 -> year
2020-10-06 21:58:43.928 6983-7059/com.lrgy.photoquerytestdemo D/myTest: 26 -> is_ringtone
2020-10-06 21:58:43.928 6983-7059/com.lrgy.photoquerytestdemo D/myTest: 27 -> is_music
2020-10-06 21:58:43.928 6983-7059/com.lrgy.photoquerytestdemo D/myTest: 28 -> is_alarm
2020-10-06 21:58:43.928 6983-7059/com.lrgy.photoquerytestdemo D/myTest: 29 -> is_notification
2020-10-06 21:58:43.928 6983-7059/com.lrgy.photoquerytestdemo D/myTest: 30 -> is_podcast
2020-10-06 21:58:43.928 6983-7059/com.lrgy.photoquerytestdemo D/myTest: 31 -> album_artist
2020-10-06 21:58:43.928 6983-7059/com.lrgy.photoquerytestdemo D/myTest: 32 -> duration
2020-10-06 21:58:43.928 6983-7059/com.lrgy.photoquerytestdemo D/myTest: 33 -> bookmark
2020-10-06 21:58:43.928 6983-7059/com.lrgy.photoquerytestdemo D/myTest: 34 -> artist
2020-10-06 21:58:43.928 6983-7059/com.lrgy.photoquerytestdemo D/myTest: 35 -> album
2020-10-06 21:58:43.928 6983-7059/com.lrgy.photoquerytestdemo D/myTest: 36 -> resolution
2020-10-06 21:58:43.928 6983-7059/com.lrgy.photoquerytestdemo D/myTest: 37 -> tags
2020-10-06 21:58:43.928 6983-7059/com.lrgy.photoquerytestdemo D/myTest: 38 -> category
2020-10-06 21:58:43.928 6983-7059/com.lrgy.photoquerytestdemo D/myTest: 39 -> language
2020-10-06 21:58:43.929 6983-7059/com.lrgy.photoquerytestdemo D/myTest: 40 -> mini_thumb_data
2020-10-06 21:58:43.929 6983-7059/com.lrgy.photoquerytestdemo D/myTest: 41 -> name
2020-10-06 21:58:43.929 6983-7059/com.lrgy.photoquerytestdemo D/myTest: 42 -> media_type
2020-10-06 21:58:43.929 6983-7059/com.lrgy.photoquerytestdemo D/myTest: 43 -> old_id
2020-10-06 21:58:43.929 6983-7059/com.lrgy.photoquerytestdemo D/myTest: 44 -> is_drm
2020-10-06 21:58:43.929 6983-7059/com.lrgy.photoquerytestdemo D/myTest: 45 -> width
2020-10-06 21:58:43.929 6983-7059/com.lrgy.photoquerytestdemo D/myTest: 46 -> height
2020-10-06 21:58:43.929 6983-7059/com.lrgy.photoquerytestdemo D/myTest: 47 -> title_resource_uri

好家伙,一共48列,不过大部分的意思都可以从名字中看出来
以下标注几个比较常用的:

序号名称含义值类型
0_id数据库中的主键int
1_data文件的绝对路径(/storage/…)String
2_size文件大小int
5date_added添加日期int
6date_modified修改日期int
7mime_type文件类型(可能为空!)String
8title文件名称(不带后缀名)String
10_display_name(带后缀名的媒体文件名:XXX.jpg、XXX.wav、XXX.mp3…)(很可能为空!)String
18bucket_display_name来自哪个包(如WeiXin)(可能为空!)String

4、取出对应的数据

如此一来,我们便可以使用MediaStore.Files.FileColumns._ID等方法获取到每列的名称字符串,随后传入getString()、getInt()等方法获取每行对应的值。

val ID: Int = cursor!!.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID)
val MIME_TYPE: Int = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.MIME_TYPE)
val DATA: Int = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATA)
val SIZE: Int = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.SIZE)
val DATE_MODIFIED: Int = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATE_MODIFIED)
val NAME = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.TITLE)

var id:Int
var type:String = "未知"
var data:Int
var size:Int
var path:String
var name:String
do {
    id = cursor.getInt(ID)
    if(cursor.getString(MIME_TYPE) != null){
        type = cursor.getString(MIME_TYPE)
    }
    size = cursor.getInt(SIZE)
    data = cursor.getInt(DATE_MODIFIED)
    path = cursor.getString(DATA)
    Log.d(TAG,"id -> $id type -> $type size -> $size data -> $data path -> $path ")
}while (!cursor.moveToNext())

三、分类检索数据

想要分类数据,自然是从contentResolver.query()这个方法的五个参数入手啦,再看一下参数的含义:
contentResolver.query()

  1. 第一个参数是文件的路径,有以下四种:
    | 常量 |含义 |
    |–|--|
    |MediaStore.Files.getContentUri(“external”)|全部内容
    |MediaStore.Video.Media.EXTERNAL_CONTENT_URI|视频内容
    |MediaStore.Audio.Media.EXTERNAL_CONTENT_URI|音频内容
    Uri uri1 = MediaStore.Images.Media.EXTERNAL_CONTENT_URI|图片内容
  2. 第二个参数是列构建一个String数组,把列名传递进去即可
    如:
val MYPROJECTION = arrayOf(
    MediaStore.Images.Media._ID,
    MediaStore.Images.Media.DATA,
    MediaStore.Files.FileColumns.MIME_TYPE,
    MediaStore.Files.FileColumns.SIZE,
    MediaStore.Files.FileColumns.TITLE
)
  1. 第三个参数是查询条件,需要指定要限制条件,如:
    “(” + MediaStore.Files.FileColumns.DATA + " LIKE ‘%.xls’" +
    " or " + MediaStore.Files.FileColumns.DATA + " LIKE ‘%.docx’" +
    " or " + MediaStore.Files.FileColumns.DATA + " LIKE ‘%.apk’" +
    " or " + MediaStore.Files.FileColumns.DATA + " LIKE ‘%.xlsx’" +
    " or " + MediaStore.Files.FileColumns.DATA + " LIKE ‘%.rar’" + “)”

    "mime_type = ? or mime_type = ? or mime_type = ? or mime_type = ? "

  2. 第四个参数指定前一个参数中的“?”,没有就填NULL

  3. 第五个参数是排序方式,如:
    MediaStore.Files.FileColumns.DATE_ADDED + " DESC"//按照添加日期降序顺序排序

四、例子

val DOC_PROJECTION = arrayOf(
    MediaStore.Images.Media._ID,
    MediaStore.Images.Media.DATA,
    MediaStore.Files.FileColumns.MIME_TYPE,
    MediaStore.Files.FileColumns.SIZE,
    MediaStore.Files.FileColumns.TITLE
)

val projection = DOC_PROJECTION
String selection = null;
String[] selectionArgs = null;
Uri uri = MediaStore.Files.getContentUri("external");
if (i == 0) {  //一些能通过mime_type查询出来的文档 .doc .pdf .txt .apk
    selection = "mime_type = ? or mime_type = ? or mime_type = ? or mime_type = ? ";
    selectionArgs = new String[]{"text/html", "application/msword", "application/pdf", "text/plain"};

} else if (i == 1) { //一些不能通过mime_type查询出来的文档 .docx .xls .xlsx .rar
    selection = "(" + MediaStore.Files.FileColumns.DATA + " LIKE '%.xls'" +
            " or " + MediaStore.Files.FileColumns.DATA + " LIKE '%.docx'" +
            " or " + MediaStore.Files.FileColumns.DATA + " LIKE '%.apk'" +
            " or " + MediaStore.Files.FileColumns.DATA + " LIKE '%.xlsx'" +
            " or " + MediaStore.Files.FileColumns.DATA + " LIKE '%.rar'" + ")";
    selectionArgs = null;

} else if (i == 2) { //视频文件
    uri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
    selection = "mime_type = ?";
    selectionArgs = new String[]{"video/mp4"};
} else if (i == 3) {  //音频文件

    uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
    selection = "mime_type = ? or mime_type = ?";
    selectionArgs = new String[]{"audio/mpeg", "audio/ogg"};
}
val cursor: Cursor = context.getContentResolver().query(
    uri,
    projection,
    selection,
    selectionArgs,
    MediaStore.Files.FileColumns.DATE_ADDED + " DESC"
)
  • 10
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值