Android Kotlin如何使用Retrofit将本地路径图片上传到服务端
Android Kotlin Retrofit 本地路径图片上传完整实现方案
1. 修改Retrofit API接口
原有接口缺少图片上传对应的Part参数,调整为如下代码:
@Multipart @POST("add_passport.php") suspend fun addPassport( @Part("userid") userid: String, @Part("fistname") fistname:String, @Part("surname") surname:String, @Part("nationality") nationality:String, @Part("dof") dof:String, @Part("gender") gender:String, @Part("age") age:String, @Part("sig") sig: String, @Part("salt") salt: String, // 新增图片参数,注意part的名称要和服务端接收的字段名完全一致,此处示例为passport_img @Part imagePart: MultipartBody.Part ):Response<PassportInsertApiClass>
2. 新增本地文件转MultipartBody.Part工具方法
写通用工具函数,将本地图片路径转换为Retrofit上传需要的MultipartBody.Part对象:
fun getImageMultipart(imagePath: String, fieldName: String = "passport_img"): MultipartBody.Part? { val file = File(imagePath) if (!file.exists()) return null // 构造RequestBody,指定媒体类型为图片 val requestBody = file.asRequestBody("image/*".toMediaTypeOrNull()) // 封装为MultipartBody.Part,第二个参数为文件名,可自定义 return MultipartBody.Part.createFormData(fieldName, file.name, requestBody) }
注意:fieldName参数值必须和服务端接收图片的字段名完全一致,比如服务端PHP用$_FILES['passport_img']接收,此处就传passport_img。
3. 调整Repository层代码
修改原有Repository逻辑,将传入的图片路径转换为Part后传给API接口:
// 原有image参数改为String类型的imagePath,存储本地图片路径 suspend fun addPassport( userid: String, fistname:String, surname:String, nationality:String, dof:String, gender:String, age:String, sig:String, salt:String, imagePath:String ):Response<PassportInsertApiClass>{ val imagePart = getImageMultipart(imagePath) ?: throw IllegalArgumentException("图片文件不存在") return RetrofitInstance.api.addPassport(userid,fistname,surname,nationality,dof,gender,age,sig,salt,imagePart) }
4. 调整ViewModel层代码
同步修改ViewModel的参数类型,传入图片路径即可,新增异常捕获逻辑:
fun addPassport( userid: String, fistname:String, surname:String, nationality:String, dof:String, gender:String, age:String, sig:String, salt:String, imagePath: String ): MutableLiveData<Response<PassportInsertApiClass>>{ val addPassport = MutableLiveData<Response<PassportInsertApiClass>>() viewModelScope.launch { try { val result = repository.addPassport(userid, fistname, surname, nationality, dof, gender, age, sig, salt, imagePath) addPassport.postValue(result) } catch (e: Exception) { // 异常处理,比如文件不存在、网络错误等 addPassport.postValue(null) } } return addPassport }
5. Fragment层备份逻辑实现
用户触发备份操作时,先从SQLite查询所有待上传的护照数据,依次调用ViewModel方法上传即可,示例代码如下:
// 备份按钮点击事件 btnBackup.setOnClickListener { // 1. 先检查存储权限,Android 13+需要申请READ_MEDIA_IMAGES,低版本需要申请READ_EXTERNAL_STORAGE if (!checkStoragePermission()) { requestStoragePermission() return@setOnClickListener } lifecycleScope.launch { // 2. 从SQLite查询所有未上传的护照数据 val pendingUploadList = sqliteDao.queryAllPendingUploadPassport() // 3. 遍历上传 pendingUploadList.forEach { passport -> viewModel.addPassport( userid = passport.userId, fistname = passport.firstName, surname = passport.surName, nationality = passport.nationality, dof = passport.dof, gender = passport.gender, age = passport.age, sig = passport.sig, salt = passport.salt, imagePath = passport.imageLocalPath ).observe(viewLifecycleOwner) { response -> response?.let { if (it.isSuccessful) { // 上传成功,更新SQLite中该条数据的上传状态,避免重复上传 sqliteDao.updateUploadStatus(passport.id, UPLOAD_SUCCESS) } else { // 上传失败,可标记为待重试 sqliteDao.updateUploadStatus(passport.id, UPLOAD_FAILED) } } ?: run { // 异常提示 Toast.makeText(requireContext(), "上传失败:${passport.firstName}的护照信息", Toast.LENGTH_SHORT).show() } } } // 全部上传完成后提示用户 Toast.makeText(requireContext(), "备份操作完成", Toast.LENGTH_SHORT).show() } }
注意事项
- 适配分区存储:Android 10及以上版本如果访问公共目录下的图片,不需要申请存储权限,直接通过Uri加载即可,如果是应用私有目录下的图片,全程不需要权限。
- 图片压缩:上传前可先对图片进行压缩,避免上传大文件导致超时或流量消耗过高。
- 批量上传优化:如果数据量较大,建议添加上传进度条,且支持断点续传或失败重试机制。
- 服务端校验:服务端需要对上传的图片格式、大小进行校验,避免恶意文件上传。
内容的提问来源于stack exchange,提问作者androi useru
相关产品推荐
相关产品推荐

