Android适配分区存储后如何获取外部存储的File对象

自从Android 10推出分区存储(Scoped Storage)后,我们在处理外部存储的文件时面临了新的挑战。本文将探讨在适配分区存储之后,如何有效地获取外部存储的File对象。

一、理解分区存储 🧐

分区存储是为了加强用户数据的安全性和隐私而引入的。这意味着应用现在对存储的访问受到更严格的限制。用户需要适应这一变化,确保应用的兼容性和功能性。
分区存储详细解释

二、申请必要的权限 🔑

即使在分区存储环境中,访问外部存储的File对象仍然需要相应的权限。确保你的应用已经请求了必要的存储权限。这是保证应用顺利运行的基础步骤。
AndroidManifest.xml 中添加必要的权限:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

三、使用新的文件访问方法 🔄

在分区存储模型下,传统的文件访问方法可能不再有效。开发者需要探索新的API,如MediaStoreStorage Access Framework,这些API提供了与分区存储兼容的文件访问机制。

  1. 使用MediaStore访问媒体文件:
import android.content.ContentResolver
import android.content.ContentUris
import android.content.Context
import android.provider.MediaStore

fun queryMediaFiles(context: Context) {
    val contentResolver: ContentResolver = context.contentResolver

    val projection = arrayOf(
        MediaStore.Images.Media._ID,
        MediaStore.Images.Media.DISPLAY_NAME,
        MediaStore.Images.Media.DATE_ADDED
    )

    val selection = "${MediaStore.Images.Media.MIME_TYPE} = ?"
    val selectionArgs = arrayOf("image/jpeg")
    val sortOrder = "${MediaStore.Images.Media.DATE_ADDED} DESC"

    val queryUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI

    val cursor = contentResolver.query(
        queryUri,
        projection,
        selection,
        selectionArgs,
        sortOrder
    )

    cursor?.use {
        val idColumn = it.getColumnIndexOrThrow(MediaStore.Images.Media._ID)
        val nameColumn = it.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME)
        val dateAddedColumn = it.getColumnIndexOrThrow(MediaStore.Images.Media.DATE_ADDED)

        while (it.moveToNext()) {
            val id = it.getLong(idColumn)
            val name = it.getString(nameColumn)
            val dateAdded = it.getLong(dateAddedColumn)

            val contentUri = ContentUris.withAppendedId(queryUri, id)
            // 处理查询到的媒体文件信息
            // ...
        }
    }
}
  1. 使用Storage Access Framework(SAF)选择外部存储中的文件:
import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.provider.OpenableColumns

private const val REQUEST_CODE_PICK_FILE = 1001

fun pickFileFromExternalStorage(activity: Activity) {
    val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
        addCategory(Intent.CATEGORY_OPENABLE)
        type = "*/*" // */
    }
    activity.startActivityForResult(intent, REQUEST_CODE_PICK_FILE)
}

fun handleActivityResult(requestCode: Int, resultCode: Int, data: Intent?, activity: Activity) {
    if (requestCode == REQUEST_CODE_PICK_FILE && resultCode == Activity.RESULT_OK) {
        data?.data?.let { uri ->
            activity.contentResolver.query(uri, null, null, null, null)?.use { cursor ->
                val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
                cursor.moveToFirst()
                val fileName = cursor.getString(nameIndex)

                // 处理选择的文件
                // ...
            }
        }
    }
}
  1. 复制一份临时文件到应用文件存储中(使用Storage Access Framework 获取到的uri)。
	@RequiresApi(Build.VERSION_CODES.Q)
    fun copyFileFromUriToAppStorage(context: Context, uri: Uri): File? {
        val contentResolver: ContentResolver = context.contentResolver
        val displayName = getDisplayName(context, uri)
        val inputStream = contentResolver.openInputStream(uri)
        var file: File? = null

        inputStream?.use { input ->
            // 创建临时文件,将输入流的内容写入应用程序的缓存文件中
            file = File(context.cacheDir, displayName)
            FileOutputStream(file).use { output ->
                val buffer = ByteArray(4 * 1024) // 4KB 缓冲区
                var read: Int
                while (input.read(buffer).also { read = it } != -1) {
                    output.write(buffer, 0, read)
                }
                output.flush()
            }
        }

        return file
    }

    @SuppressLint("Range")
    private fun getDisplayName(context: Context, uri: Uri): String {
        var displayName = ""
        val cursor: Cursor? = context.contentResolver.query(uri, null, null, null, null)
        cursor?.use {
            if (it.moveToFirst()) {
                displayName = it.getString(it.getColumnIndex(OpenableColumns.DISPLAY_NAME))
            }
        }
        return displayName
    }

感谢阅读,Best Regards!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Android 12 中,由于安全性的考虑,应用程序访问本地文件系统的方式发生了变化。从 Android 12 开始,应用程序将无法访问 `file:///android_asset/` 目录。相反,您应该使用 `content://` URI 或将文件复制到应用程序专用目录中的 `files/` 目录。 以下是一些适用于 Android 12 的解决方案: 1. 使用 `AssetManager` 类来获取 `assets` 目录中的文件。例如,您可以使用以下代码从 `assets` 目录中读取文本文件: ``` try { InputStream inputStream = getAssets().open("filename.txt"); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); String line; while ((line = bufferedReader.readLine()) != null) { // process the line } bufferedReader.close(); } catch (IOException e) { e.printStackTrace(); } ``` 2. 将文件复制到应用程序专用目录中的 `files/` 目录,并使用 `FileProvider` 类来生成 `content://` URI。例如,您可以使用以下代码将文件复制到 `files/` 目录: ``` try { InputStream inputStream = getAssets().open("filename.txt"); FileOutputStream outputStream = openFileOutput("filename.txt", Context.MODE_PRIVATE); byte[] buffer = new byte[1024]; int length; while ((length = inputStream.read(buffer)) > 0) { outputStream.write(buffer, 0, length); } outputStream.close(); inputStream.close(); } catch (IOException e) { e.printStackTrace(); } ``` 然后,您可以使用以下代码生成 `content://` URI: ``` File file = new File(getFilesDir(), "filename.txt"); Uri contentUri = FileProvider.getUriForFile(this, "your.package.name.fileprovider", file); ``` 在此示例中,`your.package.name` 应该替换为您的应用程序的包名。 请注意,如果您的应用程序需要访问其他应用程序的文件,则需要使用 `ACTION_OPEN_DOCUMENT` 或 `ACTION_CREATE_DOCUMENT` 操作来请求用户授权。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jiet_h

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值