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

    科技2023-10-12  84

    文章目录

    一、什么是MediaStore1、概述2、MediaStore在哪里? 二、从MediaStore获取数据1、添加权限2、获取每行数据3、确定每列的含义。4、取出对应的数据 三、分类检索数据四、例子

    参考资料: 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数据库中的主键int1_data文件的绝对路径(/storage/…)String2_size文件大小int5date_added添加日期int6date_modified修改日期int7mime_type文件类型(可能为空!)String8title文件名称(不带后缀名)String10_display_name(带后缀名的媒体文件名:XXX.jpg、XXX.wav、XXX.mp3…)(很可能为空!)String18bucket_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" )
    Processed: 0.013, SQL: 8