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重建。
无错试用版,保留了所以商城的基本功能,商品数量限制80件2005V-C更新:更新所有订单功能及一些相应的错误,在线支付加上邮费功能支持在线支付八家银行等接口和可以选择商品图文排列功能,可以后台自由设置,银行接口列表如下:动感在线支付支付宝 网银在线 NPS支付 西部支付 1st-pay在线支付平台 首信易支付 易付通 中国在线支付 环讯IPS支付 不使用在线支付默认管理员帐号:admin密码:ad
- 用
viewModelScope.launch启动协程,配合withContext(Dispatchers.IO)做I/O操作 - 上传过程用
ProgressFlow(如MutableStateFlow)通知UI进度,避免在LaunchedEffect里反复收集 - 如果用Retrofit,确保
@Multipart接口接收RequestBody,把InputStream包装成okhttp3.MultipartBody.Part,别传File
@Composable
fun FileUploadButton(viewModel: UploadViewModel) {
val launcher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.OpenDocument()
) { uri ->
if (uri != null) {
viewModel.startUpload(uri)
}
}
Button(onClick = { launcher.launch(arrayOf("image/jpeg", "image/png")) }) {
Text("选择图片")
}
// 进度条
if (viewModel.uploadProgress.value > 0) {
LinearProgressIndicator(progress = viewModel.uploadProgress.value / 100f)
}
}
Scoped Storage下Uri权限怎么持久化
用户选完文件后,Uri默认只在本次Activity有效。若上传失败需重试,或App退到后台再回来,Uri可能失效——报SecurityException: Permission Denial。
- 调用
contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)获取持久权限 - 权限需在
onActivityResult等效时机申请,且只对content://开头的Uri有效(file://无效) - 下次使用前先检查
contentResolver.persistedUriPermissions是否包含该Uri,没有就重新选
takePersistableUriPermission调用时机和ContentResolver读取时的异常兜底——空Uri、SecurityException、IOException不处理,上传界面就卡死或闪退。









