Jetpack Compose需用rememberLauncherForActivityResult配合Intent.ACTION_OPEN_DOCUMENT触发系统文件选择器,通过ContentResolver安全读取Uri内容并持久化权限,上传逻辑应在viewModelScope中协程执行。
Compose里没有现成的Intent.ACTION_OPEN_DOCUMENT封装怎么办
Jetpack Compose本身不提供文件选择UI组件,也不封装ActivityResultLauncher逻辑。你得靠rememberLauncherForActivityResult配合系统Intent来触发原生文件选择器。直接在可组合函数里调用startActivityForResult会崩溃,因为Composable没Activity上下文——必须通过ActivityResultRegistry桥接。
- 用
rememberLauncherForActivityResult声明一个ActivityResultLauncher,类型为Intent,回调接收Uri - 触发时传入
Intent(Intent.ACTION_OPEN_DOCUMENT).apply { type = "image/*" }(支持*/pdf、application/json等MIME) - Android 10+需确保
android:requestLegacyExternalStorage="true"(仅调试期),但更推荐适配Scoped Storage,用ContentResolver读取Uri而非路径
拿到Uri后怎么安全读取文件内容
不能用uri.path转File——在Android 10+上大概率返回null或SecurityException。必须走ContentResolver.openInputStream()流式读取,尤其上传前要校验大小和类型。
- 用
context.contentResolver.openInputStream(uri)获取InputStream,再转成ByteArray或分块上传 - 校验MIME:调用
context.contentResolver.getType(uri),和预期类型比对,防止用户篡改文件扩展名 - 获取文件名:用
DocumentsContract.getDocumentId(uri)解析ID,再查MediaStore或用OpenableColumns查询显示名(DISPLAY_NAME)
上传时如何避免主线程阻塞和生命周期泄漏
上传逻辑写在LaunchedEffect或ViewModel里都行,但关键点是:别在Composable里直接runBlocking或Thread.sleep;上传任务必须可取消,且响应Activity重建。