首页 > 解决方案 > 在 Android 中使用带有 MultipartBody 的 Retrofit 2 发布图像时 EXIF 详细信息丢失?

问题描述

我们使用 CameraX 捕获多张图像并将它们存储在设备上,并使用 MultipartBody.Part 和 RequestBody 发布这些图像和文本数据。我们发现我们没有在服务器端收到 EXIF 详细信息。这是 Retrofit 2 Multipart 的问题吗?我们如何在不丢失其 EXIF 详细信息的情况下在服务器端获取图像?

这是我们正在使用的代码

private lateinit var activityCaptureImageBinding: ActivityCaptureImageBinding

private var preview: Preview? = null
private var imageCapture: ImageCapture? = null
private var camera: Camera? = null
private lateinit var outputDirectory: File
private lateinit var cameraExecutor: ExecutorService

private lateinit var fusedLocationClient: FusedLocationProviderClient
private var metadata = ImageCapture.Metadata()

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    activityCaptureImageBinding = DataBindingUtil.setContentView(this@CaptureImageActivity, R.layout.activity_capture_image)
    window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)

    fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
    if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
        return
    }
    fusedLocationClient.lastLocation
            .addOnSuccessListener { location: Location? ->
                metadata.apply {
                    setLocation(location)
                }
            }

    activityCaptureImageBinding.btnCaptureImage.setOnClickListener {
        takePhoto()
    }

    activityCaptureImageBinding.btnCancel.setOnClickListener {
        val intent = Intent()
        intent.putStringArrayListExtra(AppData.IMAGE_PATH, AppData.capturedCatsList)
        setResult(Activity.RESULT_OK, intent)
        finish()
    }

    outputDirectory = getOutputDirectory()
    cameraExecutor = Executors.newSingleThreadExecutor()
}

private fun startCamera() {
    val cameraProviderFuture = ProcessCameraProvider.getInstance(this)

    cameraProviderFuture.addListener(Runnable {
        val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()

        preview = Preview.Builder()
                .build()

        imageCapture = ImageCapture.Builder()
                .build()

        val cameraSelector = CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build()

        try {
            cameraProvider.unbindAll()

            camera = cameraProvider.bindToLifecycle(
                    this, cameraSelector, preview, imageCapture)
            preview?.setSurfaceProvider(viewFinder.createSurfaceProvider())
        } catch (exc: Exception) {
            showLog(TAG, "Use case binding failed" + exc)
        }

    }, ContextCompat.getMainExecutor(this))
}


private fun takePhoto() {

    val imageCapture = imageCapture ?: return

    val photoFile = File(
            outputDirectory,
            SimpleDateFormat(FILENAME_FORMAT, Locale.getDefault()
            ).format(System.currentTimeMillis()) + ".jpg")

    showLog(TAG, "created file")

    val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile)
            .setMetadata(metadata)
            .build()

    imageCapture.takePicture(
            outputOptions, ContextCompat.getMainExecutor(this), object : ImageCapture.OnImageSavedCallback {
        override fun onError(exc: ImageCaptureException) {
            showLog(TAG, "Photo capture failed: ${exc.message}")
        }

        override fun onImageSaved(output: ImageCapture.OutputFileResults) {
            showLog(TAG, "on image saved")
            val savedUri = Uri.fromFile(photoFile)
            AppData.capturedCatsList.add(savedUri.toString())
        }
    })
}

以下是改造代码

@Multipart
@POST("SimpleEncounterForm")
fun postCatImagesWithData(@Header("Cookie") cookie: String,
                          @Part catImages: List<MultipartBody.Part>,
                          @Part("datetime") dateTime: RequestBody,
                          @Part("lat") lat: RequestBody,
                          @Part("lon") lon: RequestBody,
                          @Part("locationID") locationId: RequestBody,
                          @Part("behavior") catBehaviour: RequestBody): Call<ResponseBody>

我们用来从存储图像的 Uri 创建 MultipartBody.Part 的方法

@NonNull
private fun postFiles(fileUri: Uri): MultipartBody.Part {
    val file = FileUtils.getFile(this@DashboardActivity, fileUri)
    val fileReqBody = RequestBody.create(MediaType.parse("image/jpeg"), file)
    return MultipartBody.Part.createFormData("upload", file.name, fileReqBody)
}

将 Uri 转换为文件

public static File getFile(Context context, Uri uri) {
    if (uri != null) {
        String path = getPath(context, uri);
        if (path != null && isLocal(path)) {
            return new File(path);
        }
    }
    return null;
}

标签: androidkotlinretrofit2android-camerax

解决方案


推荐阅读