Проблема с загрузкой изображений при обновлении для Android 10 kotlin

#android #kotlin #retrofit2 #multipartform-data #scoped-storage

#Android #kotlin #retrofit2 #multipartform-данные #область действия-хранилище

Вопрос:

Интерфейс 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 amp;amp; 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? = null

     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 для загрузки файлов. Я могу загрузить файл с помощью postman, но не удается из моего мобильного приложения. Что не так с моим кодом? Может ли это быть проблема с хранилищем в Android?

Комментарии:

1. В чем ошибка?

2. Что у вас targetSdkVersion ? если 30 requestLegacyExternalStorage не будет работать.

3. @ronnieotieno я не получаю никаких ошибок. Я также мог бы зарегистрировать свой файловый файл. Я получаю undefined как файловый объект в серверной части. Я проверил свой сервер, и он работает отлично.

4. @sdex мой ‘targetSdkVersion’ равен 30.

5. @sdex я удалил requestLegacyExternalStorage , и он все еще не работал.

Ответ №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()
        }
    }
 }
 

Комментарии:

1. Вы действительно можете отлаживать Uri то, что получаете. Он закодирован, и после моих исследований нет СПОСОБА вернуть для него точный путь к файлу. Нет выбора, кроме inputStream как сначала скопировать в другой файл все, что вам нужно, чтобы что-то загрузить.

2. что подразумевается под не работает? можете ли вы показать, в чем ошибка? или вы можете попробовать отладить его и посмотреть, где ошибка?

3. Объект LOG — filePartImage при вызове api -> E/TAG: updateItemApiCall: okhttp3.MultipartBody$Part@c6d66ae

4. можете ли вы дважды подтвердить, что ваш путь к файлу работает? кроме того, вы можете использовать режим отладки, чтобы проверить, какова точная ошибка в строке MultipartBody

5. можете попробовать и проверить это asRequestBody(fileType?.toMediaTypeOrNull()) ? не уверен, повлияет это или нет

Ответ №2:

Замена .toRequestBody("image/*".toMediaType()) .toRequestBody(imageType.toMediaTypeOrNull) выполнила свою работу

 if (requestCode == UPLOAD_IMAGE amp;amp; 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())
                    )
                }
            }
 

Комментарии:

1. но он может получить каталог уровня приложения только правильно? если я хочу получить что-то из общего каталога, такого как /Downloads , /Picture и т. Д., Путь к файлу будет возвращен content://document8 примерно так.

2. ДА. Вы можете добавить случайную строку вместо файла. назовите, если это действительно не имеет значения. Но этот метод правильно считывает выбранный вами файл.