想法是这样的:
这个想法是为了阻止任何权限请求,因为 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")
}
}
是否可以这样做或者我错过了什么?谢谢!
将
ActivityResultContracts.GetContent()
切换到 ActivityResultContracts.OpenDocument()
。 GetContent
不属于 SAF;您无法使用 Uri
获得持久的 GetContent
权限。