Media Store 分区存储

文章讲述了在Android应用中向Download目录及其子目录写入文件时遇到的异常,以及提供了解决方案,包括手动清理、申请权限和使用MediaStoreAPI。同时介绍了MediaStore查询文件的方法和示例。
摘要由CSDN通过智能技术生成

最近测试把文件写入到系统Download目录时发现直接把文件写入到Download中时没有问题。但是如果在Download中添加了一级目录,比如Download/Test时,第一次安装app,而且download目录下没有Test时,可以正常写入文件。但是卸载重装app或者Download中原来就有Test目录,写入文件时就会出现以下异常

文件写入代码如下:

    private fun createFile1(){
        val path = "${Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)}/Test"
        val directory = File(path)
        if(!directory.exists()){
            directory.mkdirs()
        }
        val file = File(directory,"test.txt")
        FileOutputStream(file).use { outputStream->outputStream.write("Hello world!".toByteArray()) }
    }

解决方式用三种,第一种是手动删除掉Download中的Test目录,或者创建子目录时确保子目录在Download中不存在,这种解决方法简单粗暴。

第二种就是申请所有文件管理权限。

第三种就是用MediaStore写入文件,代码如下:

MediaStore 写入文件
    /**
     * 创建文件到download目录
     */
    private fun createFile() {
        val values = ContentValues().apply {
            put(MediaStore.MediaColumns.DISPLAY_NAME, "sdf.txt")// 文件名
            put(MediaStore.MediaColumns.MIME_TYPE, "text/plain")// 文件类型
            put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS + "/Test1")// 文件保存相对路径
        }

        val uri = contentResolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, values)
        uri?.let {
            contentResolver.openOutputStream(uri).use { outputStream ->
                outputStream?.write("hello world".toByteArray())
            } ?: run {
                Log.e("uri", "Failed to create file")
            }
        }
    }

除了Download目录,其他的如Pictures,DCIM也是类似

 /**
     * 创建图片到Picture目录
     */
    private fun createImage() {
        val values = ContentValues().apply {
            put(MediaStore.MediaColumns.DISPLAY_NAME, "test")
            put(MediaStore.MediaColumns.MIME_TYPE, "image/png")
            put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + "/Test1")

            // 图片专用参数
            put(MediaStore.Images.ImageColumns.DESCRIPTION,"描述测试")
//            put(MediaStore.Images.ImageColumns.DATE_TAKEN,Date().timeStamp())

        }
        val uri: Uri? = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
        val bitmap = AppCompatResources.getDrawable(this,R.drawable.img1)?.toBitmap()
        uri?.let {
            contentResolver.openOutputStream(it).use {outputStream->
                bitmap?.compress(Bitmap.CompressFormat.PNG,90,outputStream!!)
            }
        }
    }

    private fun createDCIM(){
        val values = ContentValues().apply {
            put(MediaStore.MediaColumns.DISPLAY_NAME,"照片名")
            put(MediaStore.MediaColumns.MIME_TYPE,"image/png")
            put(MediaStore.MediaColumns.RELATIVE_PATH,Environment.DIRECTORY_DCIM+"/Test")
        }

        val uri:Uri? = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,values)
        val bitmap = AppCompatResources.getDrawable(this,R.drawable.img1)?.toBitmap()
        uri?.let {
            contentResolver.openOutputStream(it).use {outputStream->
                bitmap?.compress(Bitmap.CompressFormat.PNG,90,outputStream!!)
            }
        }

说完了写入,接下来看一下查询文件

MediaStore查询文件
  private fun queryFiles(context: Context): List<Uri> {
        val uriList = mutableListOf<Uri>()
        sb.setLength(0)
        val projection = arrayOf(MediaStore.Downloads._ID, MediaStore.Downloads.DISPLAY_NAME)
        val uri: Uri = MediaStore.Downloads.EXTERNAL_CONTENT_URI
        context.contentResolver.query(uri, projection, null, null, null)?.use { cursor ->
            Log.e("查询结果数量", cursor.count.toString())
            while (cursor.moveToNext()) {
                val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID)
                val nameColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME)
                val contentUri: Uri = ContentUris.withAppendedId(
                    uri,
                    cursor.getLong(idColumn)
                )
                uriList.add(contentUri)
                sb.append(cursor.getString(nameColumn) + "\n")
                // 打印图片名称和URI
                Log.e("名称", cursor.getString(nameColumn))
                Log.e("URI", contentUri.toString())
            }
        }
        binding.text.text = sb.toString()
        return uriList
    }

query方法里面的参数和SQLiteHelper查询的参数基本上是一致的,区别在于这个查询第一个参数不是传的数据库名,而是Uri,其他的参数用法和SQLiteHelper数据库一模一样。

projection:用于指定需要从查询中返回哪些列。如果返回所有列,可以设置为 null

selection:指定查询条件,比如"${MediaStore.Images.Media.DISPLAY_NAME} LIKE ?",查询图片名中包含“?”的图片,其中问号为占位符,通过下一个参数selectionArgs传入。

selectionArgs:selection 参数一起使用,用于为 selection 字符串中的占位符提供具体的值,防止SQL注入。

如果除Uri外的参数都传Null,就表示不进行任何筛选,并且查询结果中会返回所有列。

筛选图片名中包含指定名字的图片:

 private fun queryImageByName(name: String): List<Bitmap> {
        val result = mutableListOf<Bitmap>()
        val uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI

        // 正确使用LIKE关键字的通配符%
        val selection = "${MediaStore.Images.Media.DISPLAY_NAME} LIKE ?"
        val selectionArgs = arrayOf("%$name%")

        // 查询
        contentResolver.query(uri, null, selection, selectionArgs, null)?.use { cursor ->
            val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns._ID)

            // 遍历所有匹配项
            while (cursor.moveToNext()) {
                val id = cursor.getLong(idColumn)
                val imageUri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id)

                val bitmap = contentResolver.openInputStream(imageUri)?.use { inputStream ->
                    BitmapFactory.decodeStream(inputStream)
                }
                bitmap?.let {
                    result.add(it)
                }
            }
        }

        Log.e("图片数量", result.size.toString())
        return result
    }

demo地址:GitHub - JasonChance1/MediaStoreDemo: MediaStore分区存储

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值