Выбор файла с помощью Intent.ACTION_OPEN_DOCUMENT и загрузка на сервер с помощью Retrofit2

Есть несколько связанных вопросов о выборе и загрузке файлов с Android. Но эти ответы устарели, и большинство методов в этих ответах устарели/устарели.

Я пытаюсь получить файл из локального хранилища с помощью Intent.ACTION_OPEN_DOCUMENT и загрузить его на сервер с помощью Retrofit. Но я не могу преобразовать экземпляр Uri в файл. Кроме того, я не могу загрузить этот файл с помощью Retrofit.

Вот код, который я использовал для получения файла:

    class MainActivity : AppCompatActivity() {

    private val DEVICE_PATH: Int = 398
    private val BASE_URL = "https://pdftoworder.com/"
    private lateinit var pdfClient: PDFClient

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val logging = HttpLoggingInterceptor()
        logging.level = (HttpLoggingInterceptor.Level.BODY)

        val okHttpClient = OkHttpClient
                .Builder()
                .addInterceptor(logging)
                .build()

        val retrofit = Retrofit.Builder()
                .baseUrl(BASE_URL)
                .client(okHttpClient)
                .addConverterFactory(MoshiConverterFactory.create())
                .build()

        pdfClient = retrofit.create(PDFClient::class.java)


        getPath.setOnClickListener { getFileUri() }
    }


    fun getFileUri(){
        val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
        //intent.addCategory(Intent.CATEGORY_OPENABLE)
        intent.type = "*/*"
        intent.type = "application/pdf"
        startActivityForResult(intent, DEVICE_PATH)
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {


        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == DEVICE_PATH && resultCode == Activity.RESULT_OK) {
            data?.data?.let {uri ->
                Log.d("PATHURL", "${uri.path} ${File(uri.path!!).name}")

                lifecycleScope.launch {
                    //val api_key =  RequestBody.create(MediaType.parse("text/plain"), "somevalue")
                    try {
                        val api_key = "DzkpCKjktggtCT1ZE8bFqca7anmmkpOcg975".toRequestBody("text/plain".toMediaTypeOrNull())
                        val tool_uid = "PR5".toRequestBody("text/plain".toMediaTypeOrNull())


                        val a = getRealPathFromURI(uri)
                        val file: File = File(uri.path!!)

                        val requestFile: RequestBody = file.asRequestBody("application/pdf".toMediaTypeOrNull())
                        val multipartBody: MultipartBody.Part = MultipartBody.Part.createFormData("input", file.name, requestFile)

                        val result = pdfClient.convert(
                                input = multipartBody,
                                api_key = api_key,
                                tool_uid = tool_uid
                        )




                        Log.d("PATHURL", "${uri.path}")
                    }catch (ex: Exception){
                        Log.d("PATHURL", "fatal ${ex.message}")
                    }
                }
            }
        }
    }

    fun getPath(context: Context, uri: Uri): String? {
        // DocumentProvider
        if (DocumentsContract.isDocumentUri(context, uri)) {
            System.out.println("getPath() uri: " + uri.toString())
            System.out.println("getPath() uri authority: " + uri.getAuthority())
            System.out.println("getPath() uri path: " + uri.getPath())
            // ExternalStorageProvider
            if ("com.android.externalstorage.documents" == uri.getAuthority()) {
                val docId = DocumentsContract.getDocumentId(uri)
                val split = docId.split(":").toTypedArray()
                val type = split[0]
                println("getPath() docId: " + docId + ", split: " + split.size + ", type: " + type)
                // This is for checking Main Memory
                return if ("primary".equals(type, ignoreCase = true)) {
                    if (split.size > 1) {
                        Environment.getExternalStorageDirectory().toString() + "/" + split[1] + "/"
                    } else {
                        Environment.getExternalStorageDirectory().toString() + "/"
                    }
                    // This is for checking SD Card
                } else {
                    "storage" + "/" + docId.replace(":", "/")
                }
            }
        }
        return null
    }

    fun getRealPathFromURI(contentUri: Uri?): String? {
        val proj = arrayOf(MediaStore.Images.Media.DATA)
        val cursor = contentResolver.query(contentUri!!, proj, null, null, null) ?: return null
        val column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)
        cursor.moveToFirst()
        return cursor.getString(column_index)
    }
}


interface PDFClient {

    @POST("api/convert")
    @Multipart
    suspend fun convert(
            @Part input: MultipartBody.Part,
            @Part("api_key") api_key: RequestBody,
            @Part("tool_uid") tool_uid: RequestBody
    ): ResponseBody
}

Ошибка, которую я получаю:

document/raw:/storage/emulated/0/Download/Question_Bank/26_9_day_CSE_summer_final_2018_Wireless Programming.pdf (No such file or directory)
Here my goals are:
  • Получить файл с андроида
  • Загрузите этот файл на сервер, используя модификацию
  • Скачать файл с сервера
  • Сохраните этот файл в локальном хранилище

Любая небольшая помощь будет оценена. Я также создал GitHub Repo, чтобы воспроизвести проблему.


person zoha131    schedule 19.02.2020    source источник
comment
И ACTION_GET_CONTENT, и ACTION_OPEN_DOCUMENT дают вам Uri к некоторому контенту. Нет требования, чтобы этот контент был файлом в файловой системе, не говоря уже о том, что вы можете каким-то образом добраться до этого файла. Используйте InputStreamRequestBody, как указано в дубликате, для загрузки содержимого, указанного Uri, на ваш сервер.   -  person CommonsWare    schedule 19.02.2020
comment
@CommonsWare, не могли бы вы показать мне код? Как я могу использовать InputStreamRequestBody в модификации?   -  person zoha131    schedule 19.02.2020
comment
@CommonsWare Я добавил InputStreamRequestBody и думаю, что это решило проблему с Uri. Теперь столкнулся с проблемой перепрошивки. @POST("api/convert") @Multipart suspend fun convert( @Part("input") input: InputStreamRequestBody, @Part("api_key") api_key: RequestBody, @Part("tool_uid") tool_uid: RequestBody ): ResponseBody законно ли такое использование?   -  person zoha131    schedule 19.02.2020
comment
Мне не нужно было делать составной запрос в Retrofit годами, извините.   -  person CommonsWare    schedule 19.02.2020