在Android Jetpack Compose中使用Retrofit上传图片失败求助
问题描述
我在Android Jetpack Compose中尝试使用Retrofit2上传图片文件到服务器,但未能成功。之前用XML+Kotlin实现过相同功能且正常工作,以下是我的代码:
API 接口
interface ImageService { @Multipart suspend fun uploadImage( @Part image: MultipartBody.Part, ) }
Repository
class ImageRepository @Inject constructor (private val imageService: ImageService) { suspend fun uploadImage(file: File) : Boolean { return try { imageService.uploadImage( image = MultipartBody.Part .createFormData( "image", file.name, file.asRequestBody() ) ) true } catch (e: IOException) { e.printStackTrace() false } catch (e: HttpException) { e.printStackTrace() false } } }
ViewModel
fun uploadImage(file: File) { viewModelScope.launch { imageRepository.uploadImage(file) } }
Compose 界面
@Composable fun PickImage() { val context = LocalContext.current val imageViewModel = viewModel<ImageViewModel>() Column { val imageUri = remember { mutableStateOf<Uri?>(null) } val launcher = rememberLauncherForActivityResult( contract = ActivityResultContracts.GetContent(), onResult = { imageUri.value = it } ) if (imageUri.value != null) { Image( painter = rememberAsyncImagePainter(model = imageUri.value), contentDescription = null, modifier = Modifier.size(200.dp) ) } Spacer(modifier = Modifier.height(10.dp)) Button(onClick = { launcher.launch("image/*") }) { Text( text = "Gallery", fontWeight = FontWeight.Bold, fontSize = 30.sp ) } //Upload Button(onClick = { val inputStream = context.contentResolver.openInputStream(imageUri.value!!) val file = File(context.cacheDir, "mage.png") file.createNewFile() file.outputStream().use { inputStream!!.copyTo(it) } imageViewModel.uploadImage(file) }) { Text(text = "Upload") } } }
解决思路
1. 修复文件复制的潜在问题
代码中使用!!强制解包inputStream和imageUri.value,存在崩溃风险;同时文件名拼写错误(mage.png应为image.png)。优化后的上传点击逻辑:
Button(onClick = { val uri = imageUri.value ?: return@Button runCatching { context.contentResolver.openInputStream(uri)?.use { inputStream -> val file = File(context.cacheDir, "image.png") file.outputStream().use { outputStream -> inputStream.copyTo(outputStream) } imageViewModel.uploadImage(file) } }.onFailure { it.printStackTrace() // 可添加Toast提示用户文件处理失败 } }) { Text(text = "Upload") }
2. 指定正确的请求MIME类型
默认file.asRequestBody()会使用application/octet-stream作为Content-Type,服务器可能期望图片专属MIME类型(如image/png)。修改Repository中的RequestBody创建逻辑:
val requestBody = file.asRequestBody("image/*".toMediaTypeOrNull()) MultipartBody.Part.createFormData( "image", file.name, requestBody )
3. 完善上传结果反馈与日志
当前仅返回Boolean无法定位失败原因,修改为返回Result类型并增加日志:
Repository修改
suspend fun uploadImage(file: File): Result<Unit> { return try { val requestBody = file.asRequestBody("image/*".toMediaTypeOrNull()) val imagePart = MultipartBody.Part.createFormData( "image", file.name, requestBody ) imageService.uploadImage(imagePart) Result.success(Unit) } catch (e: IOException) { e.printStackTrace() Result.failure(e) } catch (e: HttpException) { Log.e("UploadError", "Code: ${e.code()}, Message: ${e.message()}") e.response()?.errorBody()?.string()?.let { Log.e("UploadError", "Error Body: $it") } Result.failure(e) } }
ViewModel修改
private val _uploadResult = mutableStateOf<Result<Unit>?>(null) val uploadResult: State<Result<Unit>?> = _uploadResult fun uploadImage(file: File) { viewModelScope.launch { _uploadResult.value = imageRepository.uploadImage(file) } }
界面添加结果监听
LaunchedEffect(imageViewModel.uploadResult.value) { when(val result = imageViewModel.uploadResult.value) { is Result.Success -> Toast.makeText(context, "上传成功", Toast.LENGTH_SHORT).show() is Result.Failure -> Toast.makeText(context, "上传失败: ${result.exception.message}", Toast.LENGTH_SHORT).show() null -> {} } }
4. 检查权限与Retrofit配置
- 若targetSdkVersion ≥33,需在Manifest添加
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />,并运行时请求权限 - 确认Retrofit实例配置了正确BaseUrl、ConverterFactory(如
GsonConverterFactory),OkHttpClient无拦截器阻碍请求
内容的提问来源于stack exchange,提问作者asmatullah khan
相关产品推荐
相关产品推荐

