Android10及以上访问公有目录

本文详细介绍了在Android 10及以上版本中如何使用MediaStore访问公有目录,如Download、DCIM等。在这些版本中,WRITE_EXTERNAL_STORAGE权限已过时,取而代之的是MANAGE_EXTERNAL_STORAGE。但非文件管理器应用在Google Play上架需遵循特定政策。文章提供了复制、查询和删除公有目录文件的代码示例,并强调了权限申请的细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

公有目录指的是系统根目录下的Download、DCIM、Documents、Screenshots、Music等文件夹。

本文说的访问是指:列举出某一公有目录下的所有文件、删除某个文件、保存文件到某个公有目录等意思。

Android10以下按原来的File(path)方式,本文不表。

Android10及以上可以使用MediaStore访问公有目录。如果我们在公有目录下只操作自己应用生成的文件,是不需要申请文件读写权限的。另外,公有目录下的文件在APP卸载后不会被删除,也能被其他应用访问(仅限于媒体类型的文件)。(其他应用只能read,不能write)

下面是关于Android10及以上存储权限的说明:(官方说明:https://developer.android.com/training/data-storage?hl=zh-cn

  • WRITE_EXTERNAL_STORAGE在Android11已过时,不要再去申请这个。

  • Android10及以上是新版的权限MANAGE_EXTERNAL_STORAGE。但是如果你要在Google Play上架,且你的APP不是文件管理器之类的应用,那么建议查看一下Google Play的政策说明:https://support.google.com/googleplay/android-developer/answer/9956427。代码里Android Stuido也提示说Most apps are not allowed to use MANAGE_EXTERNAL_STORAGE

  • READ_EXTERNAL_STORAGE读取权限。如果你需要读取别的应用保存在公有目录下的文件(仅限于媒体文件,无法读取到其他类型的。。),则需要动态申请这个权限;如果读取的是自己应用保存的文件,即便是公有目录,也不需要申请这个权限(ps:有个场景需要读取权限:应用卸载后,之前创建的文件会变成“无主”文件,以后应用重新安装是需要读取权限的。大白话就是:应用卸载重装后,之前的文件不属于你了,现在你去读取,相当于是读别人的文件,所以要读取权限)

下面的代码都是以Download文件夹为例的,不再特别说明。如果需要操作其他文件夹,改一下Uri即可。

私有目录文件复制到公有目录
/**
 * 复制私有目录的文件到公有Download目录
 * @param context 上下文
 * @param oldPath 私有目录的文件路径
 * @param targetDirName 公有目录下的目标文件夹名字。比如传test,则会复制到Download/test目录下。另外如果Download目录下test文件夹不存在,会自动创建。
 * @return 公有目录的uri,为空则代表复制失败
 */
@RequiresApi(Build.VERSION_CODES.Q)
fun copyFileToDownloadDir(context: Context,oldPath: String,targetDirName:String): Uri? {
    try {
        val oldFile = File(oldPath)
        //设置目标文件的信息
        val values = ContentValues()
        values.put(MediaStore.Images.Media.DESCRIPTION, "This is a file.")
        values.put(MediaStore.Files.FileColumns.DISPLAY_NAME, oldFile.name)
        values.put(MediaStore.Files.FileColumns.TITLE, oldFile.name)
        values.put(MediaStore.Files.FileColumns.MIME_TYPE, getMimeType(oldPath))
        val relativePath = Environment.DIRECTORY_DOWNLOADS + File.separator + targetDirName
        values.put(MediaStore.Images.Media.RELATIVE_PATH, relativePath)
        val downloadUri = MediaStore.Downloads.EXTERNAL_CONTENT_URI
        val resolver = context.contentResolver
        val insertUri = resolver.insert(downloadUri, values)
        if (insertUri != null) {
            val fos = resolver.openOutputStream(insertUri)
            if (fos != null) {
                val fis = FileInputStream(oldFile)
                fis.copyTo(fos)
                fis.close()
                fos.close()
                return insertUri
            }
        }
    } catch (e: Exception) {
        e.printStackTrace()
    }
    return null
}

fun getMimeType(path: String?): String {
    var mime = "*/*"
    path ?: return mime
    val mmr = MediaMetadataRetriever()
    try {
        mmr.setDataSource(path)
        mime = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE) ?: mime
    } catch (e: Exception) {
        e.printStackTrace()
    } finally {
        mmr.release()
    }
    return mime
}
查询公有目录下的文件
/**
 * 获取公有Download目录下的文件
 * @param dirName Download目录下的下一级文件夹的名字
 * @return 文件的uri集合
 */
@RequiresApi(Build.VERSION_CODES.Q)
fun listFiles(context: Context, dirName: String): List<Uri> {
    val resultList = ArrayList<Uri>()
    try {
        val resolver = context.contentResolver
        val downloadUri = MediaStore.Downloads.EXTERNAL_CONTENT_URI
        val resultCursor = resolver?.query(
            downloadUri,
            null,
            MediaStore.Files.FileColumns.BUCKET_DISPLAY_NAME + "=?",
            arrayOf(dirName),
            null
        )
        if (resultCursor != null) {
            val fileIdIndex = resultCursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID)
//          val fileNameIndex = resultCursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DISPLAY_NAME)
            while (resultCursor.moveToNext()) {
                val fileId = resultCursor.getLong(fileIdIndex)
                //文件名
//              val fileName = resultCursor.getString(fileNameIndex)
                val pathUri = downloadUri.buildUpon().appendPath("$fileId").build()
                resultList.add(pathUri)
            }
            resultCursor.close()
        }
    } catch (e: Exception) {
        e.printStackTrace()
    }
    return resultList
}
删除公有目录下的文件
/**
 * 删除公有目录的文件。(自己应用创建的文件才有权限删除)
 */
@RequiresApi(Build.VERSION_CODES.Q)
fun deleteFile(context: Context, fileUri: Uri) {
    try {
        context.contentResolver?.delete(fileUri, null, null)
    } catch (e: Exception) {
        e.printStackTrace()
    }
}
公有目录文件复制到私有目录
/**
 * 公有目录文件复制到私有目录
 * @param fileUri 公有目录文件的uri
 * @param privatePath 私有目录的路径
 */
fun copyToPrivateDir(context: Context,fileUri:Uri,privatePath:String){
    try {
        val fis =  FileInputStream(context.contentResolver.openFileDescriptor(fileUri,"r")?.fileDescriptor)
        fis.copyTo(FileOutputStream(privatePath))
        fis.close()
    }catch (e:Exception){
        e.printStackTrace()
    }
}

上面用到的api,导包如下:

import android.content.ContentValues
import android.content.Context
import android.media.MediaMetadataRetriever
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.MediaStore
import androidx.annotation.RequiresApi
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.lang.Exception
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值