本文介绍: 上文中提到了Android提供内容的叫ContentProvider,那么在Android中怎么区分各个Provider?有的是提供联系人的,有的是提供图片的,有的是提供视频的等等。所以就需要一个唯一标识标识这个Provider,Uri(通用资源标识符 Universal Resource Identifier)就是起到了这个标识作用。每一个ContentProvider都会有一个唯一的Uri地址通过这个Uri标识可以获取到ContentProvider和其中的数据然后行数操作

Android系统内容提供者ContentProvider

安卓系统会在每次开机之后扫描所有文件分类整理存入数据库这个数据库保存手机存储的所有文件信息。该数据库文件存放在Android设备/data/data/com.android.providers.media/databases/data/data/com.android.providers.media.module/databases目录当中,该目录下有两个数据库文件分别是internal.db(内部存储数据库文件)和external.db(外部存储数据库文件), 这两个数据库文件中的数据表和表结构都大体相似区别在于internal.db用来存放内部存储中的文件信息的,而external.db用来存储外部存储中的文件信息的。因此可以通过访问两个数据获取例如媒体文件(音频视频图片)等的文件信息, 而不必通过遍历媒体文件方式获取文件信息。但是在android设备中是禁止应用程序直接这个数据进行直接操作的,而是将这个数据库的操作通过ContentProvider(内容提供者)数据操作提供出来, 如要对ContentProvider中的数据进行操作,可以通过ContentResolver(数据调用者) 对象结合Uri进行调用实现ContentResolver(数据调用者)可以实现与ContentProvider进行通信通过ContentResolver调用ContentProvider的添加(insert)、删除(delete)、查询(query)、修改(update)等操作的同名方法,从而让ContentProvider对象接收数据请求执行请求的操作并返回结果,这是一套标准的Android内容提供者数据模型ContentProvider将其存储数据数据表的形式提供给访问,在数据表中每一行一条记录,每一列为具有特定类型和意义的数据。每一条数据记录都包括一个 “_ID” 数值字段,改字段唯一标识一条数据。每一个Content Provider 都对外提供一个能够唯一标识自己数据集(data set)的公开URI, 如果一个Content Provider管理多个数据集,其将会为每个数据集分配一个独立的URI。所有的Content Provider 的URI 都以”content://” 开头,其中”content:”是用来标识数据是由Content Provider管理schema。Android 系统为一些常见数据类型(如音乐视频图像手机通信联系人信息等)内置了一系列的 Content Provider,这些都位于android.provider包下。持有特定的许可,可以在自己开发应用程序访问这些Content Provider。

为什么使用ContentProvider(内容提供者)来实现这一功能呢?

  1. Android设备系统中有些数据很重要,不能让别的应用程序随便访问并操作,对于需要用到这些数据的应用程序,可以通过ContentProvider这个机制,提供安全的数据访问操作方式
  2. ContentProvider还有一个重要的特点就是它是可以使得某些数据可以被跨进程访问,会自动处理安全性和跨进程通信
  3. Android系统通过ContentProvider暴露自己的数据,可以使得开发者开发时无需知道数据是如何存储的,是使用数据库还是使用文件?开发者只需要通过这一套标准统一接口和数据打交道。

ContentResolver如何使用?

获取ContentResolver实例:

val mResolver = context.getContentResolver();

ContentResolver实例获得后,就可以进行各种增删改查的方法 ,ContentResolver类也提供了与ContentProvider类相对应的四个方法可供调用:

返回值 函数声明 说明
final Uri insert(Uri url, ContentValues values) 该方法用于往ContentProvider添加数据。
final int delete(Uri url, String where, String[] selectionArgs) 该方法用于从ContentProvider删除数据。
final Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) 该方法用于从ContentProvider中获取数据
final int update(Uri uri, ContentValues values, String where, String[] selectionArgs) 该方法用于更新ContentProvider中的数据。

ContentResolver是通过Uri来查询ContentProvider中提供的数据的。因此想操作ContentProvider,必须要知道内容提供者的Uri,在正确得到Uri之后,就可以通过ContentResolver对象来操作ContentProvider中的数据了。

什么是Uri?

上文中提到了Android提供内容的叫ContentProvider,那么在Android中怎么区分各个Provider?有的是提供联系人的,有的是提供图片的,有的是提供视频的等等。所以就需要有一个唯一标识来标识这个Provider,Uri(通用资源标识符 Universal Resource Identifier)就是起到了这个标识的作用。每一个ContentProvider都会有一个唯一的Uri地址,通过这个Uri标识可以获取到ContentProvider和其中的数据,然后行数据操作。
ContentProvider使用的Uri语法结构如下

content://media/external/images/media

对于系统已经提供了如通讯录多媒体短信等的URI,可以直接用ContentResolver调用这些URI,对系统数据库进行增删改查等操作,从而保证整个Android设备中数据的统一

如何获取Uri?

content://media/external/images/media为例,其URI有三种写法

Uri uri1 = Uri.parse("content://media/external/images/media");
Uri uri2 = MediaStore.Images.Media.getContentUri("external");
Uri uri3 = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;

Android 提供了 MediaStore 类来对数据库的的多媒体数据进行封装

我们知道Android系统中的每一种媒体文件有两种地址描述方式:

通过上文可以知道Android为多媒体提供的ContentProvider的Uri都记录在MediaStore这个类里。MediaStore获取文件信息,是通过文件的mime-type获取的,也就说文件储存库中有mime-type这个字段

MediaStore内部

MediaStore内部类对外提供的多媒体Uri常量如下

注意:MediaStore.Downloads.EXTERNAL_CONTENT_URI是Android10版本新增API,用于创建访问媒体文件。

Android系统给我们定义好了许多的媒体文件对应的URI路径如下表:

媒体类型 Uri路径 MediaStore内部类常量 默认存储目录 允许存储目录
Image(图片) content://media/external/images/media MediaStore.Images.Media.EXTERNAL_CONTENT_URI Pictures DCIM、Pictures
Audio(音频) content://media/external/audio/media MediaStore.Audio.Media.EXTERNAL_CONTENT_URI Music Alarms、Music、Notifications、Podcasts、Ringtones
Video(视频) content://media/external/video/media MediaStore.Video.Media.EXTERNAL_CONTENT_URI Movies DCIM 、Movies
Download(下载文件) content://media/external/downloads MediaStore.Downloads.EXTERNAL_CONTENT_URI Download Download

有了对应的Uri 地址,我们可以通过ContentResolver调用添加(insert)、删除(delete)、查询(query)、 修改(update)等操作了。

使用ContentResolver查询数据

有了 Uri 地址,我们可以通过ContentResolverquery() 方法来获取到 Cursor,从而获取到数据库资源

public final Cursor query (Uri uri, String[] projection,String selection,String[] selectionArgs, String sortOrder)

ContentResolver的query方法接受几个参数参数意义如下:

示例:

//查找所有图片的信息
Cursor cursor = contentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
        null,
        null,
        null,
        null);

//查找所有图片的信息的DISPLAY_NAME字段
Cursor cursor = contentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
        new String[]{MediaStore.Images.Media.DISPLAY_NAME},
        null,
        null,
        null);

//查找图片名字叫“xx.png”的图片信息,null表示不进行筛选。
Cursor cursor = contentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
        new String[]{MediaStore.Images.Media.DISPLAY_NAME},
        //第三参数设置条件,相当于SQL语句中的where。
        //null表示不进行筛选。
        MediaStore.Images.Media.DISPLAY_NAME + "='xx.png'",
        null,
        null);
        
//查找图片名字叫“xx.png”的图片信息,如果在selection参数里面有?,那么你在selectionArgs写的数据就会替换掉?,
 Cursor cursor = contentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
        new String[]{MediaStore.Images.Media.DISPLAY_NAME},
        MediaStore.Images.Media.DISPLAY_NAME + "=?",
        new String[]{"xx.png"},
        null);
        
//默认排序升序。注意:desc前有空格
Cursor cursor = contentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
        null,
        null,
        null,
        ContactsContract.Contacts._ID + " DESC");

上面就是各个参数的意义,它返回的查询结果一个Cursor结果集。那么如何使用Cursor对象获取数据?
Cursor就是游标,把查询到的结果集封装在一个Cursor对象当中。我们知道只要是数据表都是可以通过行和列定位到具体的位置然后数据将其取出,Cursor也不例外,可以通过cursor.moveToNext()让行向后移动,然后根据getColumnIndex(String columnName)获取到列名索引位置。有了每一行的的列名索引位置就可以就可以取出一行对应的数据了。当然这些数据类型也是多种多样的,Cursor也给我们提供了以下的方法去获取不同类型的值:

示例:使用ContentResolver通过Uri获取图片文件相关的信息集Cursor,然后通过Cursor取出图片文件相关的信息:

// 先拿到图片提供者的Uri
val imageUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
// 需要获取图片数据表中的哪几列信息,注意这里需要和cursor取出列名相对应否则会出现指针
val projection = arrayOf(
    //获取ID列的数据
    MediaStore.Images.Media._ID,
    //获取MIME_TYPE列的值
    MediaStore.Images.Media.MIME_TYPE,
    //获取DISPLAY_NAME列的值
    MediaStore.Images.Media.DISPLAY_NAME
)

// 查询条件:因为是查询全部图片,传null
//String selection = MediaStore.Images.Media.DISPLAY_NAME +"= ?";
// 条件参数:因为是查询全部图片,传null
//String[] args = new String[] {“xxx.png”}
// 排序:可以添加排序
// val order = MediaStore.Files.FileColumns._ID + "DESC"
// 开始查询
val cursor = contentResolver.query(imageUri, projection, null, null, null)

//获取我们需要的数据在数据的第几列,这里需要和projection查询的数据对应起来,因为cursor结果集只包含projection的列信息,否则会出现指针
if (cursor != null) {
    // 获取id字段是第几列
    val idIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID)
    val mimeTypeIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.MIME_TYPE)
    val displayNameIndex = cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME)

    //循环取出cursor每一行的数据
    while (cursor.moveToNext()) {
        //根据列的坐标取出对应行数的数据
        val id = cursor.getLong(idIndex)
        //根据列的坐标,取出对应行数的数据
        val type = cursor.getString(mimeTypeIndex)
        //根据列的坐标,取出对应行数的数据
        val disName = cursor.getString(displayNameIndex)
        //根据ID和图片提供者的Uri可以合成图片的Uri
        val imageUri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id)
        //TODO
    }
    //关闭游标
    cursor.close()
}

通过上面的例子,可以获取到手机外部存储中所有的图片文件信息的ID、MIME_TYPE、DISPLAY_NAME等信息,如果要获取到文件大小等其他的信息也是可以的,只需要在projection集合添加对应的列名然后,在cursor中找到一行对应的列的索引值就可以取出对应的值,这些列名可以在MediaStore.MediaColumns取公共常量字段,也可以根据文件类型不同在MediaStore的内部类中取值:
图片文件比较常见列名有:

视频文件比较常见的列名有:

  • MediaStore.Video.Media.TITLE 名称
  • MediaStore.Video.Media.DURATION 总时长
  • MediaStore.Video.Media.DATA 地址
  • MediaStore.Video.Media.SIZE 大小
  • MediaStore.Video.Media.WIDTH:视频宽度,以像素为单位。
  • MediaStore.Video.Media.HEIGHT:视频的高度,以像素为单位

音频文件比较常见的列名有:

  • MediaStore.Audio.Media.TITLE:歌名
  • MediaStore.Audio.Media.ARTIST:歌手
  • MediaStore.Audio.Media.DURATION:总时长
  • MediaStore.Audio.Media.DATA:地址
  • MediaStore.Audio.Media.SIZE:大小

使用ContentResolver插入数据

如果需要插入数据只需要调用contentResolver.insert(uri, contentValues);构造一个 ContentValues 对象,通过 ContentResolver.insert 插入到对应的目录中,该方法会返回一个 Uri,通过对该 Uri 进行文件流写入即可:

private fun saveImage(bitmap: Bitmap){
    val values = ContentValues()
    val insertUri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,values)
    insertUri?.let {
        contentResolver.openOutputStream(it).use {outputStream->
            bitmap.compress(Bitmap.CompressFormat.PNG,100,outputStream)
        }
    }
}

ContentValues类和Bundle类很类似,都是使用HashMap泛型形式来存储的,并且都是HashMap<String, Object>()。通过ContentValues可以设置列的数据,和上文中提到的一样可以在MediaStore.MediaColumns中
公共常量字段也可以在MediaStore.MediaColumns取公共常量字段:

private fun saveImage(bitmap: Bitmap){
    val values = ContentValues().apply {
      //设置文件的 MimeType
      put(MediaStore.Images.Media.MIME_TYPE,"image/png")
      //指定保存文件名put(MediaStore.Images.Media.DISPLAY_NAME,"${System.currentTimeMillis()}.png")
      //指定保存文件目录,如果不设置这个值,则会被默认保存到对应的媒体类型文件夹下,
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
           put(MediaStore.Images.Media.RELATIVE_PATH,"${Environment.DIRECTORY_PICTURES}/DemoPicture")
      } else {
           put(MediaStore.MediaColumns.DATA,"${Environment.getExternalStorageDirectory().path}${File.separator}${Environment.DIRECTORY_DCIM}${File.separator}${System.currentTimeMillis()}.png")
      }
     
    }
    //插入文件数据库并获取到文件的Uri
    val insertUri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,values)
    
    insertUri?.let {
        //通过outputStream将图片文件内容写入Url
        contentResolver.openOutputStream(it).use {outputStream->
            bitmap.compress(Bitmap.CompressFormat.PNG,100,outputStream)
        }
    }
}

对于 Android 中的媒体类型应该按照不同的MimeType放到对应的公共目录媒体文件夹下:

媒体类型 Uri路径 MediaStore内部类常量 文件类型(MimeType) 默认存储目录 允许存储目录
Image(图片) content://media/external/images/media MediaStore.Images.Media.EXTERNAL_CONTENT_URI image/* Pictures DCIM、Pictures
Audio(音频) content://media/external/audio/media MediaStore.Audio.Media.EXTERNAL_CONTENT_URI audio/* Music Alarms、Music、Notifications、Podcasts、Ringtones
Video(视频) content://media/external/video/media MediaStore.Video.Media.EXTERNAL_CONTENT_URI video/* Movies DCIM 、Movies
Files(下载) content://media/external/downloads MediaStore.Downloads.EXTERNAL_CONTENT_URI file/* Download Download

当通过MediaStore API创建文件时,文件会保存到对应的类型默认目录,例如图片文件(mimeType = image/*)会被保存到Pictures(Environment#DIRECTORY_PICTURES) 目录下;也可以使用MediaStore.xxx.Media.RELATIVE_PATH自己指定要存放的目录或者子目录,如:contentValues.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + "/自定义子目录"),文件就会放在Pictures/自定义子目录/ 中;或者使用contentValues.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_DCIM),将文件放到DCIM/ 中。
需要注意的是,不能将文件放置到允许存储目录之外的其他文件夹比如将一个 mimeType 为 audio/mpeg 放到 Pictures目录下,这样的行为是不被允许的,也就是如果设置 MIME_TYPE = audia/* 并将 RELATIVE_PATH 设置为 Environment#DIRECTORY_PICTURES 这样是会报 **Throw IllegalArgumentException **。当然也可以将所有的文件都通过 MediaStore 放到 Downloads 文件夹下。

是MediaStore.Images.Media.RELATIVE_PATH是在Android10才被添加进来的因此在低版本中可以通过FilePath适配

原文地址:https://blog.csdn.net/unreliable_narrator/article/details/127250114

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_40624.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注