写入权限不起作用-范围存储Android SDK 30又名Android 11

问题描述

还有其他人发现范围存储几乎不可能开始工作吗?大声笑。

我一直在尝试了解如何允许用户向我的应用授予对应用文件夹之外的文本文件的写权限。 (假设允许用户编辑其“文档”文件夹中的文件文本)。我已经设置了MANAGE_EXTERNAL_STORAGE权限,并且可以确认该应用程序具有该权限。但还是每次我尝试

val fileDescriptor = context.contentResolver.openFileDescriptor(uri,"rwt")?.fileDescriptor

我收到Illegal Argument: Media is read-only错误。

我的清单请求这三个权限:

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

我也尝试使用旧式存储:

<application
    android:allowBackup="true"
    android:requestLegacyExternalStorage="true"

但仍然遇到此只读问题。

我想念什么?

更多说明

我如何获取URI:

view?.selectFileButton?.setOnClickListener {
            val intent =
                Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
                    addCategory(Intent.CATEGORY_OPENABLE)
                    type = "*/*"
                    flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
                            Intent.FLAG_GRANT_WRITE_URI_PERMISSION
                }
            startActivityForResult(Intent.createChooser(intent,"Select a file"),111)
        }

然后

override fun onActivityResult(requestCode: Int,resultCode: Int,data: Intent?) {
    super.onActivityResult(requestCode,resultCode,data)
    if (requestCode == 111 && resultCode == AppCompatActivity.RESULT_OK && data != null) {
        val selectedFileUri = data.data;
        if (selectedFileUri != null) {
            viewModel.saveFilename(selectedFileUri.toString())
            val contentResolver = context!!.contentResolver
            val takeFlags: Int = Intent.FLAG_GRANT_READ_URI_PERMISSION or
                    Intent.FLAG_GRANT_WRITE_URI_PERMISSION
            contentResolver.takePersistableUriPermission(selectedFileUri,takeFlags)
            view?.fileName?.text = viewModel.filename
            //TODO("if we didn't get the permissions we needed,ask for permission or have the user select a different file")
        }
    }
}

解决方法

根据您的代码:

  • 您列出的所有权限都与ACTION_OPEN_DOCUMENT无关
  • 您的Intent上的两个标志都不存在

但是,您真正的问题是您似乎正在选择媒体,例如从“音频”类别中选择媒体。 ACTION_OPEN_DOCUMENT保证我们可以从Uri所标识的内容中读取内容,但不保证可写位置。不幸的是,MediaProvider阻止了所有写访问,并抛出了您引用其消息的异常。

the issue that I filed last year引述自己:

问题在于我们无法在ACTION_OPEN_DOCUMENT Intent上指定要编写的内容,因此希望将用户限制在可写位置。鉴于Android Q / R特别重视我们向Storage Access Framework的迁移,因此需要这种功能。否则,我们所能做的就是检测到我们没有写访问权限(例如DocumentFilecanWrite()),然后告诉用户“对不起,我不能在那儿写”,这导致糟糕的用户体验。

我在this blog post中写了更多有关此问题的内容。

因此,使用DocumentFilecanWrite()来查看是否允许您写入Uri所标识的位置,并要求用户选择其他文档。

,

在Android 11上,并使用API​​ 30模拟器进行测试,我发现了类似的公用文件夹

Download,Documents,DCIM,Alarms,Pictures and such 

可用于使用经典文件系统路径的应用程序。

仅限于应用程序自己的文件。

另外,我发现一个应用程序以这种方式创建的文件可被使用SAF的其他应用程序写入。

,

您可以尝试以下代码。它对我有用。

class MainActivity : AppCompatActivity() {

    private lateinit var theTextOfFile: TextView
    private lateinit var inputText: EditText
    private lateinit var saveBtn: Button
    private lateinit var readBtn: Button
    private lateinit var deleteBtn: Button

    private lateinit var someText: String
    private val filename = "theFile.txt"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        if (!isPermissionGranted()) {
            val permissions = arrayOf(WRITE_EXTERNAL_STORAGE)
            for (i in permissions.indices) {
                requestPermission(permissions[i],i)
            }
        }

        theTextOfFile = findViewById(R.id.theTextOfFile)
        inputText = findViewById(R.id.inputText)
        saveBtn = findViewById(R.id.saveBtn)
        readBtn = findViewById(R.id.readBtn)
        deleteBtn = findViewById(R.id.deleteBtn)

        saveBtn.setOnClickListener { savingFunction() }
        deleteBtn.setOnClickListener { deleteFunction() }
        readBtn.setOnClickListener {
            theTextOfFile.text = readFile()
        }

    }

    private fun readFile() : String{
        val rootPath = "/storage/emulated/0/Download/"
        val myFile = File(rootPath,filename)
        return if (myFile.exists()) {
            FileInputStream(myFile).bufferedReader().use { it.readText() }
        }
        else "no file"
    }

    private fun deleteFunction(){
        val rootPath = "/storage/emulated/0/Download/"
        val myFile = File(rootPath,filename)
        if (myFile.exists()) {
            myFile.delete()
        }
    }

    private fun savingFunction(){
        deleteFunction()
        someText = inputText.text.toString()
        val resolver = applicationContext.contentResolver
        val values = ContentValues()
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
            values.put(MediaStore.MediaColumns.DISPLAY_NAME,filename)
            values.put(MediaStore.MediaColumns.MIME_TYPE,"text/plain")
            values.put(MediaStore.MediaColumns.RELATIVE_PATH,Environment.DIRECTORY_DOWNLOADS)
            val uri = resolver.insert(MediaStore.Files.getContentUri("external"),values)
            uri?.let { it ->
                resolver.openOutputStream(it).use {
                    // Write file
                    it?.write(someText.toByteArray(Charset.defaultCharset()))
                    it?.close()
                }
            }
        } else {
            val rootPath = "/storage/emulated/0/Download/"
            val myFile = File(rootPath,filename)
            val outputStream: FileOutputStream
            try {
                if (myFile.createNewFile()) {
                    outputStream = FileOutputStream(myFile,true)
                    outputStream.write(someText.toByteArray())
                    outputStream.flush()
                    outputStream.close()
                }
            } catch (e: Exception) {
                e.printStackTrace()
            }

        }
    }

    private fun isPermissionGranted(): Boolean {
        val permissionCheck = ActivityCompat.checkSelfPermission(this,WRITE_EXTERNAL_STORAGE)
        return permissionCheck == PackageManager.PERMISSION_GRANTED
    }

    private fun requestPermission(permission: String,requestCode: Int) {
        ActivityCompat.requestPermissions(this,arrayOf(permission),requestCode)
    }
}

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...