You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 10:33:11