【问题标题】:Image Upload issue with retrofit for android 10 kotlinandroid 10 kotlin 改造的图像上传问题
【发布时间】:2021-01-27 07:25:17
【问题描述】:

API接口:

@Multipart
@POST(AppConstants.UPDATE_PRODUCT)
fun updateProduct(
    @Header("Authorization") auth: String,
    @Part("product_id") id: RequestBody,
    @Part("title") title: RequestBody,
    @Part("description") description: RequestBody,
    @Part("price") price: RequestBody,
    @Part image: MultipartBody.Part
) : Call<UpdateProductResponse>?

意图:

val photoIntent = Intent(Intent.ACTION_GET_CONTENT).apply {
                    type = "image/*"
                    addCategory(Intent.CATEGORY_OPENABLE)
                }
                startActivityForResult(photoIntent, UPLOAD_IMAGE)

活动结果:

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    if (requestCode == UPLOAD_IMAGE && resultCode == RESULT_OK) {
        if (data != null) {
            val bitmap = MediaStore.Images.Media.getBitmap(this.contentResolver, data.data)
            update_iv.setImageBitmap(bitmap)

            val imageType = contentResolver.getType(data.data!!)
            val extension = imageType!!.substring(imageType.indexOf("/") + 1)

            val file = File(getPath(data.data!!, this)!!)
            
            //val byte = contentResolver.openInputStream(data.data!!)
            //val contentPart = InputStreamRequestBody("image/*".toMediaType() , contentResolver , data.data!!)

            //filePartImage = MultipartBody.Part.createFormData("image" , file.name , contentPart)
            data.data!!.let {
                application.contentResolver.openInputStream(it)?.use { inputStream ->
                    filePartImage = MultipartBody.Part.createFormData(
                        "image",
                        "" + file.name, //or "image.$extension" none of the two works
                        inputStream.readBytes().toRequestBody("image/*".toMediaType())
                    )
                }
            }
            showMessage(this, "" + getString(R.string.image_uploaded))
        } else {
            showMessage(this, "" + "File upload failed!")
        }
    }
}

API 调用: 这里 filePartImage 是这样的全局变量: private var filePartImage: MultipartBody.Part? = 空

    RetrofitClientInstance().createService(ArtOFiestaServiceClass::class.java).updateProduct(
        "Bearer " + Prefs.getString("token", ""),
        id.trim().toRequestBody("text/plain".toMediaType()),
        update_name.text.toString().trim().toRequestBody("text/plain".toMediaType()),
        update_desc.text.toString().trim().toRequestBody("text/plain".toMediaType()),
        update_price.text.toString().trim().toRequestBody("text/plain".toMediaType()),
        filePartImage!!
    )?.enqueue(object : retrofit2.Callback<UpdateProductResponse> {
        override fun onResponse(
            call: Call<UpdateProductResponse>,
            response: Response<UpdateProductResponse>
        )

清单:

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
android:requestLegacyExternalStorage="true"

我已经在 node js 中开发了它的后端,并使用 multer 来上传文件。我可以使用邮递员上传文件,但我的移动应用程序失败了。我的代码有什么问题?会不会是 android Scoped Storage 问题?

【问题讨论】:

  • 错误是什么?
  • 你的targetSdkVersion 是什么?如果 30 requestLegacyExternalStorage 不起作用。
  • @ronnieotieno 我没有收到任何错误。我还可以记录我的 filePartImage var。我在后端未定义为文件对象。我已经验证了我的后端,它运行良好。
  • @sdex 我的 'targetSdkVersion' 是 30。
  • @sdex 我删除了requestLegacyExternalStorage,但它仍然不起作用。

标签: android kotlin retrofit2 multipartform-data scoped-storage


【解决方案1】:

问题是由于Scoped Storage,因为我也面临同样的问题。

我的解决方案是将inputStream 转换为相同的File 并将其存储在其他地方。然后上传后,您可以删除文件。

思路如下。

contentResolver.openInputStream(it)?.use { inputStream ->
    // STEP 1: Create a tempFile for storing the image from scoped storage. 
    val tempFile = createTempFile(this, fileName, extension)

    // STEP 2: copy inputStream into the tempFile
    copyStreamToFile(it, tempFile)

    // STEP 3: get file path from tempFile for further upload process. 
    val filePath = tempFile.absolutePath

    val requestFile: RequestBody = File(filePath).asRequestBody(fileType?.toMediaTypeOrNull())
    val body: MultipartBody.Part = MultipartBody.Part.createFormData("file", fileName, requestFile)
}
@Throws(IOException::class)
fun createTempFile(context: Context, fileName: String?, extension: String?): File {
    val storageDir: File? = context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS)
    return File(storageDir, "$fileName.$extension")
}

fun copyStreamToFile(inputStream: InputStream, outputFile: File) {
    inputStream.use { input ->
        val outputStream = FileOutputStream(outputFile)
        outputStream.use { output ->
            val buffer = ByteArray(4 * 1024) // buffer size
            while (true) {
                val byteCount = input.read(buffer)
                if (byteCount < 0) break
                output.write(buffer, 0, byteCount)
            }
            output.flush()
        }
    }
 }

【讨论】:

  • 你实际上可以调试你得到的Uri。它是经过编码的,经过我的研究,没有办法为其返回确切的文件路径。别无选择,只能先将inputStream 复制到另一个文件,然后再上传您需要的所有内容。
  • 没有工作是什么意思?你能显示什么是错误吗?或者你可以尝试调试它,看看错误在哪里?
  • LOG - 调用 api 时的 filePartImage 对象 -> E/TAG: updateItemApiCall: okhttp3.MultipartBody$Part@c6d66ae
  • 您能否再次确认您的文件路径正常工作?而且,你可以使用调试模式来检查在线@​​987654329@的确切错误是什么
  • 可以试试这个asRequestBody(fileType?.toMediaTypeOrNull())吗?不确定是否会受到影响
【解决方案2】:

.toRequestBody("image/*".toMediaType()) 替换为.toRequestBody(imageType.toMediaTypeOrNull) 完成了这项工作

if (requestCode == UPLOAD_IMAGE && resultCode == RESULT_OK) {
        if (data != null) {
            val bitmap = MediaStore.Images.Media.getBitmap(this.contentResolver, data.data)
            update_iv.setImageBitmap(bitmap)

            val imageType = contentResolver.getType(data.data!!)
            val extension = imageType!!.substring(imageType.indexOf("/") + 1)

            val file = File(getPath(data.data!!, this)!!)

            data.data!!.let {
                application.contentResolver.openInputStream(it).use { inputStream ->
                    filePartImage = MultipartBody.Part.createFormData(
                        "image",
                        file.name,
                        inputStream!!.readBytes().toRequestBody(imageType.toMediaTypeOrNull())
                    )
                }
            }

【讨论】:

  • 但它只能获取应用程序的级别目录对吗?如果我想从共享目录中获取一些东西,例如 /Downloads、/Picture 等,文件路径将返回为 content://document%038 类似这样的东西。
  • 是的。如果并不重要,您可以添加一个随机字符串来代替 file.name。但是这种方法确实可以正确读取您选择的文件。
猜你喜欢
  • 2021-05-08
  • 2020-05-13
  • 1970-01-01
  • 2022-01-11
  • 1970-01-01
  • 1970-01-01
  • 2022-01-08
  • 2013-10-12
  • 2018-11-01
相关资源
最近更新 更多