参考资料:
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数据库
这个方法接收五个参数,
看起来有点多,后面用的时候再说。
如果不加过滤等条件,查询全部数据只需要把后四个参数置为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 |
5 | date_added | 添加日期 | int |
6 | date_modified | 修改日期 | int |
7 | mime_type | 文件类型(可能为空!) | String |
8 | title | 文件名称(不带后缀名) | String |
10 | _display_name | (带后缀名的媒体文件名:XXX.jpg、XXX.wav、XXX.mp3…)(很可能为空!) | String |
18 | bucket_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()这个方法的五个参数入手啦,再看一下参数的含义:
- 第一个参数是文件的路径,有以下四种:
| 常量 |含义 |
|–|--|
|MediaStore.Files.getContentUri(“external”)|全部内容
|MediaStore.Video.Media.EXTERNAL_CONTENT_URI|视频内容
|MediaStore.Audio.Media.EXTERNAL_CONTENT_URI|音频内容
Uri uri1 = MediaStore.Images.Media.EXTERNAL_CONTENT_URI|图片内容 - 第二个参数是列构建一个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
)
-
第三个参数是查询条件,需要指定要限制条件,如:
“(” + 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 = ? "
-
第四个参数指定前一个参数中的“?”,没有就填NULL
-
第五个参数是排序方式,如:
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"
)