【问题标题】:Android - R/W to removable SD cardAndroid - R/W 到可移动 SD 卡
【发布时间】:2019-03-03 19:38:45
【问题描述】:

我正在创建应用程序,它应该将文件从内部存储(我的意思是 /storage/emulated/0/*)移动到外部可移动存储(微型 SD 卡)。我找到了那个 SD 卡的根路径,但我无法在那里创建文件夹或移动文件。我不知道该怎么做才能让它工作。 我找到了一个叫做存储访问框架的东西,但我不明白如何使用它。 对于获得特定 SD 卡根路径的读/写权限的任何帮助,我将不胜感激。

提前谢谢你。

【问题讨论】:

    标签: android kotlin


    【解决方案1】:

    从 api 19 (KitKat) 开始,几乎不可能将 SD 卡内容作为简单的 File 直接写入,因为它们至少对默认用户以只读方式安装(系统仍然可以写入)。

    DocumentsProvider API 相当复杂(即使是文档中的示例也很难阅读),这就是为什么添加了DocumentFile 来模拟File 行为以便于访问。

    假设用户已经授予读/写存储权限,我们需要获取SD卡根的文档URI(在Activity中):

    var sdCardUri : Uri? = null
    
    private fun requestSDCardPermissions(){
        if(Build.VERSION.SDK_INT < 24){
            startActivityForResult(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE), REQ_PICK_DIRECTORY)
            return
        }
        // find removable device using getStorageVolumes
        val sm = getSystemService(Context.STORAGE_SERVICE) as StorageManager
        val sdCard = sm.storageVolumes.find { it.isRemovable }
        if(sdCard != null){
            startActivityForResult(sdCard.createAccessIntent(null), REQ_SD_CARD_ACCESS)
        }
    }
    

    在 API 24 之前,用户需要自己打开文档选择器并手动选择 SD 卡。这并不理想,但 Android 团队忽略了缺少 SD 卡 API 的事实。

    在较新的版本中,getStorageVolumes() 允许我们通过代码找到 SD 卡,并且只向用户显示将被访问的明确警告。这与读/写存储权限不是同一个对话框。

    现在要处理获得的Uri,我们只需要获取结果中的data.data。将其存储在共享首选项中可能是一个好地方,以防止一遍又一遍地询问用户:

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        if(requestCode == REQ_SD_CARD_ACCESS || requestCode == REQ_PICK_DIRECTORY){
            if(resultCode == RESULT_OK) {
                if(data == null){
                    Log.e(TAG, "Error obtaining access")
                }else{
                    sdCardUri = data.data
                    Log.d("StorageAccess", "obtained access to $sdCardUri")
                    // optionally store uri in preferences as well here { ... }
                }
            }else
                Toast.makeText(this, "access denied", Toast.LENGTH_SHORT).show()
            return
        }
        super.onActivityResult(requestCode, resultCode, data)
    }
    

    我将省略大多数错误检查/文件存在验证,但现在您可以像这样使用获得的sdCardUri(将文件“sample.txt”从内部存储根目录复制到 SD 卡根目录):

    private fun copyToSDCard(){
        val sdCardRoot = DocumentFile.fromTreeUri(this, sdCardUri)
        val internalFile = File(Environment.getExternalStorageDirectory(), "sample.txt")
        // get or create file
        val sdCardFile = sdCardRoot.findFile("sample.txt") ?: sdCardRoot.createFile(null, "sample.txt")
        val outStream = contentResolver.openOutputStream(sdCardFile.uri)
        outStream.write(internalFile.readBytes())
        outStream.flush()
        outStream.close()
        Toast.makeText(this, "copied to SDCard", Toast.LENGTH_SHORT).show()
    }
    

    还有其他方式(从 SD 卡到内部存储):

    private fun copyToInternal(){
        val sdCardRoot = DocumentFile.fromTreeUri(this, sdCardUri)
        val internalFile = File(Environment.getExternalStorageDirectory(), "sample.txt")
        val sdCardFile = sdCardRoot.findFile("sample.txt")
        val inStream = contentResolver.openInputStream(sdCardFile.uri)
        internalFile.writeBytes(inStream.readBytes())
        inStream.close()
        Toast.makeText(this, "copied to internal", Toast.LENGTH_SHORT).show()
    }
    

    【讨论】:

    • 感谢您提供清晰明了的解释。我还是不明白,为什么这么难。很高兴你这么快就帮了我。 :)
    • @Fely 如果您不知道为什么 API 中的某些内容被删除和/或混淆,则默认答案是“安全性”。如果您发现我的代码示例有帮助,请将答案标记为已接受。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-02-18
    • 1970-01-01
    • 1970-01-01
    • 2015-05-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多