Загрузка изображений из приложения Android на сервер с отсутствующими данными EXIF

Я работаю над приложением для Android, в котором пользователи могут выбирать несколько фотографий из галереи своего устройства и загружать их на внутренний сервер. Для этого я использую PickMultipleVisualMedia(), чтобы получить URI выбранных изображений, а затем передаю их работнику для загрузки.

Вот как я реализовал средство выбора фотографий и работника:

Реализация средства выбора фотографий:

private fun launchNewPhotoPicker(){
    newPicker.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly))
}

val newPicker = registerForActivityResult(ActivityResultContracts.PickMultipleVisualMedia(5)) { uris ->
    if (uris != null) {
        Log.d("PhotoPicker", "Selected URIs: ${uris}")
        runUploadImageWorker(uris)
    } else {
        Log.d("PhotoPicker", "No media selected")
    }
}

private fun runUploadImageWorker(uris: List<Uri>) {
    val strUris = uris.map { it.toString() }.toTypedArray()
    val data = Data.Builder()
        .putStringArray("uris", strUris)
        .build()

    val uploadImageRequest = OneTimeWorkRequestBuilder<UploadImageWorker>()
        .setInputData(data)
        .build()

    WorkManager.getInstance(requireContext()).enqueue(uploadImageRequest)
}

Реализация работника:

class UploadImageWorker(appContext: Context, workerParams: WorkerParameters) :
    Worker(appContext, workerParams) {

    override fun doWork(): Result {
        val uris = inputData.getStringArray("uris") ?: return Result.failure()

        val client = okhttp3.OkHttpClient()

        for (uriString in uris) {
            val uri = Uri.parse(uriString)
            val inputStream = applicationContext.contentResolver.openInputStream(uri)
            val requestBody = inputStream?.readBytes()?.toRequestBody("image/*".toMediaTypeOrNull())
            val body = requestBody?.let {
                MultipartBody.Part.createFormData("picture", UUID.randomUUID().toString(), it)
            } ?: return Result.failure()

            val multipartBody = MultipartBody.Builder()
                .setType(MultipartBody.FORM)
                .addPart(body)
                .build()

            val request = okhttp3.Request.Builder()
                .url("https://mywonderfullfakeurl.com/api/pictures")
                .post(multipartBody)
                .build()

            val response = client.newCall(request).execute()

            if (!response.isSuccessful) {
                val errorBody = response.body?.string() ?: "Unknown error"
                Log.d("error", errorBody)
                return Result.failure()
            }
        }

        return Result.success()
    }
}

Однако я заметил, что в изображениях, загруженных на мой серверный сервер, отсутствуют данные EXIF. Как я могу гарантировать сохранение данных EXIF ​​при загрузке изображений из приложения Android с помощью Kotlin?

Вам не нужно сначала загружать, а потом на вашем сервере обнаружить, что информация о местоположении отсутствует. Лучше просто попробуйте прочитать exif-информацию, открыв полученный uri самостоятельно.

blackapps 23.04.2024 13:15

Я полностью согласен с вашим предложением. Однако я сталкиваюсь с проблемами при получении данных EXIF ​​из URI в моем приложении. Всякий раз, когда я пытаюсь использовать интерфейс EXIF ​​для чтения данных, я сталкиваюсь с ошибкой «ENOENT» (нет такого файла или каталога). Не могли бы вы предоставить руководство или фрагмент кода о том, как правильно получить информацию EXIF ​​из URI, не столкнувшись с этой ошибкой?

MrSolarius 23.04.2024 15:38

Я уже опробовал подход Maveňツ, и, как вы сказали, он не сработал. Я рассмотрю альтернативные методы для достижения этой цели. А затем обновите этот вопрос

MrSolarius 23.04.2024 16:22

Используйте подход Maven. Вам нужно лишь немного измениться, чтобы это заработало. Просто выясните, где именно что-то идет не так.

blackapps 23.04.2024 17:44
1
4
106
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Даже если вы реализуете ACCESS_MEDIA_LOCATION, поставщик, который вы получаете с помощью PickVisualMedia() и PickMultipleVisualMedia(), будет redact out всю информацию о местоположении exif.

Это особенность ;-).

Вам лучше использовать OpenDocument() или OpenMultipleDocuments().

Я OpenDocument() попробую и посмотрю, решит ли это проблему.

MrSolarius 23.04.2024 15:36
Ответ принят как подходящий

Чтобы сохранить данные EXIF ​​при загрузке изображений из приложения Android с помощью Kotlin, вы можете использовать следующий метод, вдохновленный @Maveňツ, а также использовать временный файл, чтобы избежать ошибок ENOENT. Кроме того, как отметил @blackapps, использование OpenMultipleDocuments() вместо PickMultipleVisualMedia() имеет решающее значение для предотвращения нулевых данных EXIF ​​при чтении из URI.

Реализация средства выбора фотографий:

    private fun launchNewPhotoPicker(){
        newPiker.launch(arrayOf("image/jpeg"))
    }
 
    private val newPiker=registerForActivityResult(ActivityResultContracts.OpenMultipleDocuments()) { uris ->
        if (uris != null) {
            Log.d("PhotoPicker", "Selected URI: ${uris}")
            //launch the worker to upload the images
            runUploadImageWorker(uris)
        } else {
            Log.d("PhotoPicker", "No media selected")
        }
    }

Реализация работника:

override fun doWork(): Result {
    val uris = inputData.getStringArray("uris") ?: return Result.failure()

    val client = okhttp3.OkHttpClient()

    for (uriString in uris) {
        val uri = Uri.parse(uriString)
        // create temp file
        val uuid = UUID.randomUUID().toString()
        val file = File(applicationContext.cacheDir, uuid)
        // copy selected file to temp file
        uri?.let { applicationContext.contentResolver.openInputStream(it) }.use { input ->
            file.outputStream().use { output ->
                input?.copyTo(output)
            }
        }

        val inputStream = file.inputStream()
        val requestBody = inputStream.readBytes().toRequestBody("image/*".toMediaTypeOrNull())
        val body = requestBody.let {
            MultipartBody.Part.createFormData("picture", UUID.randomUUID().toString(), it)
        }

        val multipartBodyBuilder = MultipartBody.Builder()
            .setType(MultipartBody.FORM)
            .addPart(body)

        // Preserving EXIF data
        val exifInterface = ExifInterface(file)
        val tagsToCheck = arrayOf(
            ExifInterface.TAG_DATETIME,
            ExifInterface.TAG_GPS_LATITUDE,
            ExifInterface.TAG_GPS_LONGITUDE,
            ExifInterface.TAG_GPS_ALTITUDE
        )

        for (tag in tagsToCheck) {
            exifInterface.getAttribute(tag)?.let {
                multipartBodyBuilder.addFormDataPart("exif_$tag", it)
            }
        }

        val multipartBody = multipartBodyBuilder.build()
        // don't forget to delete the temporary file
        file.delete()

        val request = okhttp3.Request.Builder()
            .url("https://mywonderfullfakeurl.com/api/pictures")
            .post(multipartBody)
            .build()

        val response = client.newCall(request).execute()

        if (!response.isSuccessful) {
            val errorBody = response.body?.string() ?: "Unknown error"
            Log.d("error", errorBody)
            return Result.failure()
        }
    }

    return Result.success()
}

В этой исправленной версии:

  • Мы используем OpenMultipleDocuments() вместо PickMultipleVisualMedia() для получения данных EXIF.
  • Мы используем временный файл, чтобы избежать ошибок ERNO.
  • Данные EXIF ​​сохраняются путем чтения их из временного файла с помощью ExifInterface.
  • Атрибуты данных EXIF ​​добавляются к MultipartBody с помощью addFormDataPart.

Это гарантирует, что загруженные изображения сохранят выбранные данные EXIF ​​при отправке на внутренний сервер.

Другие вопросы по теме