Android应用保存用户选择图片至专属文件夹崩溃问题求助
问题描述
应用中通过rememberLauncherForActivityResult()从相册选择图片可正常展示,但保存至应用专属文件夹时崩溃,执行srcFile.copyTo(desFile)或val fis = FileInputStream(srcFile)时程序直接终止。
相关说明:
- 代码可正常将drawable中的文件保存至应用专属内部存储,但对用户选择的图片URI无效
- 未使用
contentResolver,尝试直接通过流复制文件 - 期望无需Android权限即可使用该URI完成保存或上传操作
编辑补充:
改用ContentResolver后应用仍崩溃,以下是使用ContentResolver的代码,是否存在问题?
fun save_image_extrnal(ctxt: Context, uri_mnbw: Uri?) { if (uri_mnbw != null) { GlobalScope.launch(Dispatchers.IO) { val resolver = ctxt.contentResolver try { val values = ContentValues().apply { put(MediaStore.MediaColumns.DISPLAY_NAME, "tfh") put(MediaStore.MediaColumns.MIME_TYPE, "image/png") put(MediaStore.MediaColumns.RELATIVE_PATH, "frk") put(MediaStore.Images.Media.IS_PENDING, 1) } val uri_new = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values) val inpStream = resolver.openInputStream(uri_mnbw) uri_new?.let { resolver.openOutputStream(it)?.use { outStream -> inpStream?.copyTo(outStream) } values.clear() values.put(MediaStore.Images.Media.IS_PENDING, 0) resolver.update(uri_new, values, null, null) } Toast.makeText(ctxt, "Saved", Toast.LENGTH_SHORT) .show() } catch (e: Exception) { Toast.makeText(ctxt, "$e", Toast.LENGTH_SHORT).show() } } } }
问题分析与解决
核心问题根源
直接操作File对象无效:相册返回的图片URI是
content://类型的内容URI,并非本地文件路径,直接用FileInputStream或copyTo会因找不到文件触发崩溃。而drawable文件是本地路径,因此操作正常。补充代码的具体问题:
RELATIVE_PATH参数错误:必须指定系统合法的媒体目录前缀(如"Pictures/frk"),直接写"frk"会导致MediaStore插入失败,uri_new为空,后续触发空指针崩溃- Toast在IO线程调用:
Toast.makeText属于UI操作,必须在主线程执行,IO线程调用会抛出异常 - 流资源泄漏风险:
inpStream未用use包裹,可能导致资源无法及时释放 - 文件名重复:固定文件名
"tfh"可能导致覆盖已有文件,或因重复插入失败
修复方案
方案1:保存至应用专属内部存储(无需权限)
应用专属内部存储路径(context.filesDir)无需任何权限,仅当前应用可访问,适合保存私有文件:
fun saveToInternalStorage(context: Context, sourceUri: Uri) { GlobalScope.launch(Dispatchers.IO) { try { context.contentResolver.openInputStream(sourceUri)?.use { inputStream -> // 生成唯一文件名避免重复 val fileName = "selected_img_${System.currentTimeMillis()}.png" val targetFile = File(context.filesDir, fileName) targetFile.outputStream().use { outputStream -> inputStream.copyTo(outputStream) } // 切换主线程提示结果 withContext(Dispatchers.Main) { Toast.makeText(context, "保存成功", Toast.LENGTH_SHORT).show() } } } catch (e: Exception) { withContext(Dispatchers.Main) { Toast.makeText(context, "保存失败:${e.message}", Toast.LENGTH_SHORT).show() } } } }
方案2:修复MediaStore保存至外部公共目录的代码
若需保存到外部公共目录,需修正路径参数并规范线程操作:
fun saveToExternalPublic(context: Context, sourceUri: Uri?) { sourceUri ?: return GlobalScope.launch(Dispatchers.IO) { val resolver = context.contentResolver try { val displayName = "tfh_${System.currentTimeMillis()}.png" val values = ContentValues().apply { put(MediaStore.MediaColumns.DISPLAY_NAME, displayName) put(MediaStore.MediaColumns.MIME_TYPE, "image/png") // 指定合法相对路径:Pictures目录下的frk子文件夹 put(MediaStore.MediaColumns.RELATIVE_PATH, "${Environment.DIRECTORY_PICTURES}/frk") put(MediaStore.Images.Media.IS_PENDING, 1) } val uriNew = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values) uriNew ?: throw Exception("无法创建目标文件") // 用use包裹所有流,自动释放资源 resolver.openInputStream(sourceUri)?.use { inputStream -> resolver.openOutputStream(uriNew)?.use { outputStream -> inputStream.copyTo(outputStream) } } // 标记文件为已完成状态 values.clear() values.put(MediaStore.Images.Media.IS_PENDING, 0) resolver.update(uriNew, values, null, null) withContext(Dispatchers.Main) { Toast.makeText(context, "保存成功", Toast.LENGTH_SHORT).show() } } catch (e: Exception) { withContext(Dispatchers.Main) { Toast.makeText(context, "保存失败:${e.message}", Toast.LENGTH_SHORT).show() } } } }
关键注意事项
- 永远不要直接将
content://URI转换为File对象,必须通过ContentResolver打开流访问 - 应用专属内部存储路径通过
context.filesDir获取,无需权限,其他应用无法访问 - 使用MediaStore时,
RELATIVE_PATH必须以系统提供的目录(如Environment.DIRECTORY_PICTURES)开头,否则会插入失败 - 所有UI操作(如Toast、更新界面)必须在主线程执行,通过
withContext(Dispatchers.Main)切换线程
内容的提问来源于stack exchange,提问作者kora bertrand
相关产品推荐
相关产品推荐

