如何使用文件流和存储访问框架(SAF)持久访问单个文件而无需重复请求权限

问题描述 投票:0回答:1

想法是这样的:

  • 用户单击选择文件按钮以使用系统文件选择器选择文件
  • 使用文件流,应用程序将 URI 位置保存到其内部存储
  • 应用程序读取文件并对其内容执行某些操作
  • 下次用户打开应用程序时,将从其内部存储中读取 URI 位置,并且无需用户执行任何操作即可读取文件内容

这个想法是为了阻止任何权限请求,因为 SAF 不需要访问单个文件的权限。临时权限也会保留,因此用户下次打开应用程序时,他们不需要执行任何操作,从而实现无缝体验。

但是,我在保留此权限时遇到了一些麻烦。我认为这与 URI 的存储方式有关(必须将其转换为字符串才能写入字节数组),所以权限信息可能会丢失?

现在,用户可以选择一个文件,但是当应用程序重新启动并尝试再次加载该文件时,应用程序因

java.lang.SecurityException: Permission Denial: opening provider com.android.externalstorage.ExternalStorageProvider from ProcessRecord{...} requires that you obtain access using ACTION_OPEN_DOCUMENT or related APIs

而崩溃

我的代码现在看起来像这样:

[package and imports...]


class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            AppTheme {
                FileSelect()   // dialog for selecting a file
            }
        }
    }
}


@Composable
fun FileSelect() {
    // State variables
    val context = LocalContext.current
    var selectedFileUri by remember { mutableStateOf<Uri?>(null) }
    var fileContent by remember { mutableStateOf<String?>(null) }

    // Open file from URI, set fileContent, and write URI to internal storage
    val openFile = { uri: Uri? ->
        selectedFileUri = uri
        uri?.let {
            fileContent = readTextFromUri(context, it)
            try {
                takePersistableUriPermission(context, it)
            } catch (e: SecurityException) {
                e.printStackTrace()
            }
        }
        // Try writing to internal storage for future use
        try {
            val fos: FileOutputStream = context.openFileOutput("URILocation.txt", Context.MODE_PRIVATE)
            fos.write(selectedFileUri.toString().toByteArray()); fos.flush(); fos.close()
            Log.d("MainActivity", "Write URILocation.txt - success")
            
        } catch (e: IOException) {
            // Error writing to URILocation.txt
            Log.d("MainActivity", "Write URILocation.txt - failure")
            e.printStackTrace()
        }
    }

    // Launch file selector
    val launcher = rememberLauncherForActivityResult(
        contract = ActivityResultContracts.GetContent(),
        onResult = { uri: Uri? -> openFile(uri) }
    )

    // Handle select file
    fun buttonOnClick(){
        try {
            // Try reading URILocation.txt
            val fin: FileInputStream = context.openFileInput("URILocation.txt")
            Log.d("MainActivity", "Read URILocation.txt - success")
            var a: Int; val temp = StringBuilder(); while (fin.read().also { a = it } != -1) { temp.append(a.toChar()) }
            val uriS = temp.toString()  // URI String
            fin.close()
            // URI URI Object
            val uri: Uri = Uri.parse(uriS)
            // Read file at URI
            openFile(uri)
        } catch (e: IOException) {
            // If no URILocation.txt, launch file picker
            launcher.launch("*/*")
        }
    }

    Surface {
        Column {
            // Select file button
            Spacer(modifier = Modifier.height(100.dp))
            Button(onClick = {
                buttonOnClick()
            }) {
                Text(text = "Select or Load File")
            }

            // Display file contents
            selectedFileUri?.let { uri ->
                Text(text = "Selected file: $uri")
                fileContent?.let { content ->
                    Text(text = content)
                    [Do something with the content...]
                }
            }
        }
    }
}

// Takes file URI -> returns nullable string of content
fun readTextFromUri(context: Context, uri: Uri): String? {
    return context.contentResolver.openInputStream(uri)?.use { inputStream ->
        BufferedReader(InputStreamReader(inputStream)).use { reader ->
            reader.readText()
        }
    }
}

// Grant persistable permissions to URI
fun takePersistableUriPermission(context: Context, uri: Uri) {
    val contentResolver: ContentResolver = context.contentResolver
    val takeFlags: Int = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION

    // Check if the URI can be granted persistable permissions
    val takeFlagsSupported = DocumentsContract.isDocumentUri(context, uri)
    if (takeFlagsSupported) {
        try {
            contentResolver.takePersistableUriPermission(uri, takeFlags)
        } catch (e: SecurityException) {
            e.printStackTrace()
        }
    } else {
        // Handle the case where the URI does not support persistable permissions
        Log.d("MainActivity","Persistable permissions not supported for this URI: $uri")
    }
}

是否可以这样做或者我错过了什么?谢谢!

android kotlin android-jetpack-compose
1个回答
0
投票

ActivityResultContracts.GetContent()
切换到
ActivityResultContracts.OpenDocument()
GetContent
不属于 SAF;您无法使用
Uri
获得持久的
GetContent
权限。

© www.soinside.com 2019 - 2024. All rights reserved.