Как напрямую загрузить файл в каталог загрузок на Android Q (Android 10)

Допустим, я разрабатываю приложение для чата, которое может делиться с другими файлами ЛЮБОГО типа (без ограничений типа mime): например, изображения, видео, документы, а также сжатые файлы, такие как zip, rar, apk или даже менее частые типы файлов. например, файлы Photoshop или AutoCAD.

В Android 9 или ниже я напрямую загружаю эти файлы в каталог Download, но теперь это невозможно в Android 10, не показывая пользователю намерение спросить, где их загрузить ...

Невозможно? но тогда почему Google Chrome или другие браузеры могут это делать? Фактически они по-прежнему загружают файлы в каталог загрузки, не спрашивая пользователя в Android 10.

Сначала я проанализировал Whatsapp, чтобы увидеть, как они этого достигают, но они используют атрибут requestLegacyExternalStorage в AndroidManifest. Но затем я проанализировал Chrome, и он нацелен на Android 10 без использования requestLegacyExternalStorage. Как такое возможно?

Я уже несколько дней гуглил, как приложения могут загружать файл прямо в каталог загрузки на Android 10 (Q), не спрашивая пользователя, где его разместить, так же, как это делает Chrome.

Я прочитал документацию по Android для разработчиков, множество вопросов по Stackoverflow, сообщения в блогах в Интернете и группы Google, но все же я не нашел способа продолжать делать то же самое, что и в Android 9, и даже не нашел решения, которое меня во многом удовлетворяет.

Что я пробовал на данный момент:

  • Откройте SAF с намерением ACTION_CREATE_DOCUMENT, чтобы запросить разрешение, но, по-видимому, нет возможности открыть его в автоматическом режиме. Всегда открывается Activity, чтобы спросить пользователя, куда поместить файл. Но должен ли я открывать это намерение для каждого файла? Мое приложение может автоматически загружать файлы чата в фоновом режиме. Неприемлемое решение.

  • Получите доступ с помощью SAF в начале приложения с uri, указывающим на любой каталог для загрузки содержимого:

        StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
        i = sm.getPrimaryStorageVolume().createOpenDocumentTreeIntent();
    

    Что за уродливое занятие - спрашивать разрешения у пользователя, не так ли? Хотя это НЕ то, что делает Google Chrome.

  • Или снова, используя ACTION_CREATE_DOCUMENT, сохраните Uri, который я получаю в onActivityResult (), и используйте grantPermission () и getContentResolver (). TakePersistableUriPermission (). Но при этом создается не каталог, а файл.

  • Я также попытался получить MediaStore.Downloads.INTERNAL_CONTENT_URI или MediaStore.Downloads.EXTERNAL_CONTENT_URI и сохранить файл с помощью Context.getContentResolver.insert (), но какое совпадение, хотя они аннотированы как @NonNull, они фактически возвращают ... НУЛЕВОЙ

  • Добавление requestLegacyExternalStorage = "false" в качестве атрибута метки приложения моего AndroidManifest.xml. Но это всего лишь патч для разработчиков, чтобы выиграть время, пока они не внесут изменения и не адаптируют свой код. К тому же это не то, что делает Google Chrome.

  • getFilesDir () и getExternalFilesDir () и getExternalFilesDirs () по-прежнему доступны, но файлы, хранящиеся в этих каталогах, удаляются при удалении моего приложения. Пользователи ожидают, что при удалении моего приложения их файлы сохранятся. Опять же, это не подходящее решение для меня.

Мое временное решение:

Я нашел обходной путь, который позволяет загружать куда угодно без добавления requestLegacyExternalStorage = "false".

Он заключается в получении Uri из объекта File с помощью:

val downloadDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
val file = File(downloadDir, fileName)
val authority = "${context.packageName}.provider"
val accessibleUri = FileProvider.getUriForFile(context, authority, file)

Наличие provider_paths.xml

<paths>
    <external-path name="external_files" path="."/>
</paths>

И установив его на AndroidManifest.xml:

<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="${applicationId}.provider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/provider_paths" />
</provider>

Проблема:

Он использует метод getExternalStoragePublicDirectory, который устарел в Android Q и, скорее всего, будет удален на Android 11. Вы можете подумать, что можете создать свой собственный путь вручную, поскольку знаете реальный путь (/ storage / emulated / 0 / Download / ) и продолжайте создавать объект File, но что, если Google решит изменить путь к каталогу загрузки на Android 11?

Боюсь, это не долгосрочное решение, поэтому

Мой вопрос:

Как я могу добиться этого, не используя устаревший метод? И дополнительный вопрос: Как, черт возьми, Google Chrome получает доступ к каталогу загрузок?


person Rubén Viguera    schedule 29.11.2019    source источник
comment
Здесь есть несколько альтернативных вариантов: developer.android.com/reference/android/os/   -  person Saurabh Thorat    schedule 29.11.2019
comment
but that's now impossible in Android 10 without showing an Intent to the user to ask where to download them... Нет. Вовсе нет. Вы по-прежнему можете использовать getFilesDir (), getExternalFilesDir () и getExternalFilesDirs ().   -  person blackapps    schedule 29.11.2019
comment
В вашем списке мне не хватает ACTION_OPEN_DOCUMENT_TREE.   -  person blackapps    schedule 29.11.2019
comment
how apps can download a file directly to Download directory on Android 10 (Q) without having to ask user where to place it,. Непроверено: не загружается ли DownloadManager по умолчанию в эту папку?   -  person blackapps    schedule 29.11.2019
comment
@ aaurabh-thorat Ах, я забыл это комментировать, я загружаю в каталог Download, чтобы иметь постоянные файлы. Пользователи ожидают сохранить свои файлы при удалении моего приложения, поэтому getFilesDir () и getExternalFilesDir () и getExternalFilesDirs () не являются для меня подходящими решениями.   -  person Rubén Viguera    schedule 29.11.2019
comment
Вы можете скачать по ссылке getExternalFIlesDir (). Затем сохраните файл в MediaStore и удалите первый.   -  person blackapps    schedule 29.11.2019
comment
@blackapps В вашем списке я пропустил ACTION_OPEN_DOCUMENT_TREE. Это вторая точка, но вместо этого я использую метод createOpenDocumentTreeIntent (), который внутренне использует это действие   -  person Rubén Viguera    schedule 29.11.2019
comment
Нет. Это совсем другое. Используйте ACTION_OPEN_DOCUMENT_TREE, и вы увидите.   -  person blackapps    schedule 29.11.2019
comment
@blackapps Вы можете загрузить с помощью getExternalFIlesDir (). Затем сохраните файл в MediaStore и удалите первый. но, как я уже сказал, MediaStore.Downloads.INTERNAL_CONTENT_URI возвращает значение null. Возможно, мне нужно объединить некоторые другие решения с этим, чтобы иметь ненулевой Uri, но я его еще не нашел.   -  person Rubén Viguera    schedule 29.11.2019
comment
Если у вас возникли проблемы с сохранением в MediaStore, опубликуйте свой код в новой ветке, и мы сможем посмотреть. Он работает и на Android Q. Ok. Извините. Тогда он не попадет в папку загрузки. Так бесполезно. Извините. Он перейдет в DCIM или изображения. Но кого это волнует??   -  person blackapps    schedule 29.11.2019
comment
@blackapps Он перейдет в DCIM или изображения. Но кого это волнует?? Я мог бы разделить изображения, видео, аудио или что-то еще по их фактическому каталогу, но все же есть некоторые другие типы файлов, которые не принадлежат к этим категориям, такие как сжатые файлы, такие как ZIP, RAR или APK, или проприетарные файлы, такие как photoshop, autocad, guitar pro вкладки или что-то еще, даже некоторые типы изображений, видео и т. д., которые недопустимы в этих каталогах, потому что они не подходят для фильтрации их mimetype (например, image / * или video / *)   -  person Rubén Viguera    schedule 29.11.2019
comment
@blackapps Нет. Это совсем другое дело. Используйте ACTION_OPEN_DOCUMENT_TREE, и вы увидите, Хорошо, я посмотрю   -  person Rubén Viguera    schedule 29.11.2019
comment
but still there are some other kind of files that does not belong to that categories Да. Но кого это волнует? Приложения MediaStore или Галерея их просто не покажут. Пора немного поэкспериментировать.   -  person blackapps    schedule 29.11.2019
comment
Используйте ACTION_OPEN_DOCUMENT_TREE, чтобы позволить пользователю выбрать основной раздел или карту micro SD. Затем создайте свою собственную папку приложения. В папке приложения вы можете создать столько папок, сколько хотите, чтобы организовать свои файлы.   -  person blackapps    schedule 29.11.2019
comment
Если вы находитесь только в магазине MediaStore (в DCIM или изображениях), то у вас больше нет доступа к ним, если вы не использовали один раз ACTION_OPEN_DOCUMENT_TREE. Так что это был плохой совет. Извините. Используйте только ACTION_OPEN_DOCUMENT_TREE, как указано выше. Вы пробовали DownloadManager?   -  person blackapps    schedule 29.11.2019
comment
получить MediaStore.Downloads.INTERNAL_CONTENT_URI или MediaStore.Downloads.EXTERNAL_CONTENT_URI и сохранить файл с помощью Context.getContentResolver.insert () должен быть решением. Вы можете задать отдельный вопрос о переполнении стека с минимальным воспроизводимым примером, демонстрирующим, как вы это пробовали.   -  person CommonsWare    schedule 29.11.2019
comment
@CommonsWare. Благодарю за подсказку. Пробовал оба. Внутренний вызывает исключение UnsuppotedOperationException. Внешние работы, но я еще не видел возможности создать подкаталог. Но проблема остается: как только все ваши файлы будут загружены, вам все равно понадобится ACTION_OPEN_DOCUMENT_TREE для их обработки.   -  person blackapps    schedule 29.11.2019
comment
@CommonsWare, я снова попытался использовать MediaStore.Downloads.EXTERNAL_CONTENT_URI, и, что удивительно, теперь он возвращает notNull Uri. Я пробовал метод вставки, и он успешно создает файл, но он недоступен для файлов Google. Я думаю, это происходит потому, что файл принадлежит моему приложению, а Google Files не запрашивает разрешение READ_EXTERNAL_STORAGE.   -  person Rubén Viguera    schedule 29.11.2019
comment
@blackapps, вы можете создать подкаталог, поскольку CommonsWare ответит на этот другой вопрос: stackoverflow.com/questions/56468539/   -  person Rubén Viguera    schedule 29.11.2019
comment
Да, я уже использовал это. Он работает для каталога DCIM и изображений, но не для загрузки, как я уже говорил.   -  person blackapps    schedule 30.11.2019
comment
Эх ... ну можно в Download сделать подкаталоги. .. Просто надо уметь ;-).   -  person blackapps    schedule 30.11.2019


Ответы (4)


Прошло более 10 месяцев, а удовлетворительный ответ для меня так и не был получен. Так что я отвечу на свой вопрос.

Как указано в комментарии @CommonsWare, получите MediaStore.Downloads.INTERNAL_CONTENT_URI или MediaStore.Downloads.EXTERNAL_CONTENT_URI и сохраните файл с помощью Context.getContentResolver.insert () должно быть решением. Я дважды проверил и выяснил, что это правда, и ошибался, говоря, что это не работает. Но...

Мне было сложно использовать ContentResolver, и я не мог заставить его работать должным образом. Я задам ему отдельный вопрос, но я продолжал исследовать и нашел какое-то удовлетворительное решение.

МОЕ РЕШЕНИЕ:

Обычно вам нужно загрузить в любой каталог, принадлежащий вашему приложению, а затем скопировать в папку загрузок.

  1. Настройте свое приложение:

    • Добавьте provider_paths.xml в xml папку ресурсов

      <paths xmlns:android="http://schemas.android.com/apk/res/android">
          <external-path name="external_files" path="."/>
      </paths>
      
    • В своем манифесте добавьте FileProvider:

      <application>
          <provider
               android:name="androidx.core.content.FileProvider"
               android:authorities="${applicationId}.provider"
               android:exported="false"
               android:grantUriPermissions="true">
               <meta-data
                   android:name="android.support.FILE_PROVIDER_PATHS"
                   android:resource="@xml/provider_paths" />
           </provider>
       </application>
      
  2. Подготовьтесь к загрузке файлов в любой каталог, которым владеет ваше приложение, например getFilesDir (), getExternalFilesDir (), getCacheDir () или getExternalCacheDir ().

    val privateDir = context.getFilesDir()
    
  3. Скачать файл с учетом его прогресса (DIY):

    val downloadedFile = myFancyMethodToDownloadToAnyDir(url, privateDir, fileName)
    
  4. После загрузки вы можете сделать любую угрозу для файла, если хотите.

  5. Скопируйте его в папку Загрузки:

    //This will be used only on android P-
    private val DOWNLOAD_DIR = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
    
    val finalUri : Uri? = copyFileToDownloads(context, downloadedFile)
    
    fun copyFileToDownloads(context: Context, downloadedFile: File): Uri? {
        val resolver = context.contentResolver
        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            val contentValues = ContentValues().apply {
                put(MediaStore.MediaColumns.DISPLAY_NAME, getName(downloadedFile))
                put(MediaStore.MediaColumns.MIME_TYPE, getMimeType(downloadedFile))
                put(MediaStore.MediaColumns.SIZE, getFileSize(downloadedFile))
            }
            resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)
        } else {
            val authority = "${context.packageName}.provider"
            val destinyFile = File(DOWNLOAD_DIR, getName(downloadedFile))
            FileProvider.getUriForFile(context, authority, destinyFile)
        }?.also { downloadedUri ->
            resolver.openOutputStream(downloadedUri).use { outputStream ->
                val brr = ByteArray(1024)
                var len: Int
                val bufferedInputStream = BufferedInputStream(FileInputStream(downloadedFile.absoluteFile))
                while ((bufferedInputStream.read(brr, 0, brr.size).also { len = it }) != -1) {
                    outputStream?.write(brr, 0, len)
                }
                outputStream?.flush()
                bufferedInputStream.close()
            }
        }
    }
    
  6. Попав в папку загрузки, вы можете открыть файл из приложения следующим образом:

    val authority = "${context.packageName}.provider"
    val intent = Intent(Intent.ACTION_VIEW).apply {
        setDataAndType(finalUri, getMimeTypeForUri(finalUri))
        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP) {
            addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION)
        } else {
            addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
        }
    }
    try {
        context.startActivity(Intent.createChooser(intent, chooseAppToOpenWith))
    } catch (e: Exception) {
        Toast.makeText(context, "Error opening file", Toast.LENGTH_LONG).show()
    }
    
    //Kitkat or above
    fun getMimeTypeForUri(context: Context, finalUri: Uri) : String =
        DocumentFile.fromSingleUri(context, finalUri)?.type ?: "application/octet-stream"
    
    //Just in case this is for Android 4.3 or below
    fun getMimeTypeForFile(finalFile: File) : String =
        DocumentFile.fromFile(it)?.type ?: "application/octet-stream"
    

Плюсы:

  • Загруженные файлы сохраняются после удаления приложения

  • Также позволяет узнать его прогресс при загрузке

  • Вы по-прежнему можете открывать их из своего приложения после перемещения, поскольку файл по-прежнему принадлежит вашему приложению.

  • Разрешение write_external_storage не требуется для Android Q +, только для этой цели:

    <uses-permission
        android:name="android.permission.WRITE_EXTERNAL_STORAGE"
        android:maxSdkVersion="28" />
    

Минусы:

  • У вас не будет доступа к загруженным файлам один раз после очистки данных вашего приложения или повторного удаления и повторной установки (они больше не принадлежат вашему приложению, если вы не спросите разрешения)
  • На устройстве должно быть больше свободного места, чтобы иметь возможность копировать каждый файл из исходного каталога в конечное место назначения. Это особенно важно для больших файлов. Хотя, если у вас есть доступ к исходному inputStream, вы можете напрямую писать в загруженныйUri вместо копирования из промежуточного файла.

Если этого подхода вам достаточно, попробуйте.

person Rubén Viguera    schedule 14.10.2020
comment
Я думаю, что ваше решение для копирования файла в папку загрузки хорошее, но для успешной вставки contentValues требуется put(MediaStore.MediaColumns.IS_PENDING, 1), чтобы ContentResolver - person Javad Vovin; 22.12.2020
comment
Боюсь, что в этом нет необходимости. Я использую этот подход в продакшене и работаю уже почти год. IS_PENDING может понадобиться, если вы не копируете из собственного каталога, а просто записываете напрямую в каталог Downloads. Я хотел бы увидеть ответ с помощью IS_PENDING, так как я не смог заставить его работать. - person Rubén Viguera; 22.12.2020
comment
В моем случае это было необходимо, я загружаю какой-то файл в папку данных приложения и копирую его в папку загрузки, без IS_PENDING результат Uri равен null, но с ним результат Uri действителен. - person Javad Vovin; 26.12.2020

Вы можете использовать Android DownloadManager.Request. Ему потребуется WRITE_EXTERNAL_STORAGE Persmission до Android 9. Начиная с Android 10 / Q и выше, ему не потребуется никаких разрешений (кажется, он сам обрабатывает разрешение).

Если вы хотите открыть файл позже, вам потребуется разрешение пользователя (также, если вы хотите открыть его только во внешнем приложении (например, PDF-Reader).

Вы можете использовать менеджер загрузок следующим образом:

DownloadManager.Request request = new DownloadManager.Request(<download uri>);
request.addRequestHeader("Accept", "application/pdf");
    
// Save the file in the "Downloads" folder of SDCARD
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS,filename);

DownloadManager downloadManager = (DownloadManager) getActivity().getSystemService(Context.DOWNLOAD_SERVICE);
downloadManager.enqueue(request);

Это ссылки: https://developer.android.com/reference/kotlin/android/app/DownloadManager.Request?hl=en#setDestinationInExternalPublicDir(kotlin.String,%20kotlin.String)

https://developer.android.com/training/data-storage.

person beal    schedule 23.03.2020
comment
Это могло бы быть решением для кого-то еще, но не в моем случае, так как оно не позволяет мне узнать о его ходе при загрузке. Я хотел бы показать прогрессБар с его прогрессом специально для больших файлов. - person Rubén Viguera; 14.10.2020
comment
Вы можете получить прогресс, как описано здесь: stackoverflow.com/questions/7824835/ - person Hyndrix; 12.11.2020
comment
Не для локального файла !! java.lang.IllegalArgumentException: можно загружать только URI HTTP / HTTPS: file: /// storage / emulated / 0 / Android / ... - person Fortran; 17.12.2020
comment
Я могу говорить только о своих проектах, но по состоянию на начало 2021 года в диспетчере загрузок Android возникают проблемы на некоторых устройствах. Я вижу всплеск жалоб (и я проверил это на своем устройстве) по поводу того, что он либо никогда не загружает файлы, либо занимает целый день чтобы начать загрузку. Немного информации для всех, кто хочет пройти этот маршрут. - person PGMacDesign; 23.04.2021

Я подумал, что лучше было бы написать ответ на java, поскольку существует много устаревшего кода, который необходимо обновить. Также я понятия не имею, какой файл вы хотите сохранить, поэтому здесь я взял пример растрового изображения. Если вы хотите сохранить файл любого другого типа, вам просто нужно создать его OutputStream, а для оставшейся части вы можете следовать приведенному ниже коду. Теперь эта функция будет обрабатывать только сохранение для Android 10+ и, следовательно, аннотацию. Надеюсь, у вас есть необходимые знания о том, как сохранять файлы в более ранних версиях Android.

@RequiresApi(api = Build.VERSION_CODES.Q)
public void saveBitmapToDownloads(Bitmap bitmap) {
    ContentValues contentValues = new ContentValues();

    // Enter the name of the file here. Note the extension isn't necessary
    contentValues.put(MediaStore.Downloads.DISPLAY_NAME, "Test.jpg");
    // Here we define the file type. Do check the MIME_TYPE for your file. For jpegs it is "image/jpeg"
    contentValues.put(MediaStore.Downloads.MIME_TYPE, "image/jpeg");
    contentValues.put(MediaStore.Downloads.IS_PENDING, true);

    Uri uri = MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
    Uri itemUri = getContentResolver().insert(uri, contentValues);

    if (itemUri != null) {
        try {
            OutputStream outputStream = getContentResolver().openOutputStream(itemUri);
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
            outputStream.close();
            contentValues.put(MediaStore.Images.Media.IS_PENDING, false);
            getContentResolver().update(itemUri, contentValues, null, null);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Теперь, если вы хотите создать подкаталог, то есть папку внутри папки загрузок, вы можете добавить это в contentValues.

contentValues.put(MediaStore.Downloads.RELATIVE_PATH, "Download/" + "Folder Name");

Это создаст папку с именем Folder Name и сохранит Test.jpg внутри этой папки.

Также обратите внимание, что для этого не требуются какие-либо разрешения в манифесте, что означает, что ему также не нужны разрешения времени выполнения. Если вам нужна версия Kotlin, спрашивайте меня в комментариях. Я бы с радостью предоставил для этого метод Котлина.

person Joel    schedule 11.01.2021
comment
эй, из-за моего требования, что, если я хочу указать расширение ?? - person Vivek Thummar; 23.06.2021
comment
Да, вы можете добавить расширение для своего файла в DISPLAY_NAME и проверить MIME_TYPE своего файла отсюда. developer.mozilla.org/en-US/docs/ Интернет / HTTP / Basics_of_HTTP / - person Joel; 24.06.2021

Если вы хотите сохранить документы из ResponseBody в папке загрузок, используйте это

приватное развлечение saveFileToStorage (

    body: ResponseBody,
    filename: String,
    consignmentId: String,
    context: Context
){

    val contentResolver = context.contentResolver
    var inputStream: InputStream? = null
    var outputStream: OutputStream? = null
    var fileZizeDownloaded: Long = 0


    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        val values = ContentValues().apply {
            put(MediaStore.MediaColumns.DISPLAY_NAME, filename)
            put(MediaStore.MediaColumns.MIME_TYPE, "application/pdf")
            put(
                MediaStore.MediaColumns.RELATIVE_PATH, 
                Environment.DIRECTORY_DOWNLOADS
            )
        }

        val uri = 
           contentResolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, 
            values)
        try {
            var pfd: ParcelFileDescriptor?=null
            try {
                pfd = 
                uri?.let { contentResolver.openFileDescriptor(it, "w")}!!
                val buf = ByteArray(4 * 1024)
                inputStream = body.byteStream()
                outputStream = FileOutputStream(pfd.fileDescriptor)
                var len: Int

                while (true) {
                    val read = inputStream.read(buf)
                    if (read == -1) {
                        break
                    }
                    outputStream.write(buf, 0, read)
                    fileZizeDownloaded += read.toLong()
                }

                outputStream.flush()
              } catch (e: java.lang.Exception) {
                e.printStackTrace()
            } finally {
                inputStream?.close()
                outputStream?.close()
                pfd?.close()
            }
            values.clear()
            values.put(MediaStore.Video.Media.IS_PENDING, 0)
            if (uri != null) {
                context.contentResolver.update(uri, values, null, null)
                outputStream = context.contentResolver.openOutputStream(uri)
            }
            if (outputStream == null) {
                throw IOException("Failed to get output stream.")
            }
            
        } catch (e: IOException) {
            if (uri != null) {
                context.contentResolver.delete(uri, null, null)  
            } 
        } finally {
            outputStream?.close()
        }
    } else {
        val directory_path =
                 Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).absolutePath
        val file = File(directory_path)
        if (!file.exists()) {
            file.mkdirs()
        }
        val targetPdf = directory_path + filename
        val filePath = File(targetPdf)

        try {
            val fileReader = ByteArray(4 *1024)
            inputStream = body.byteStream()
            outputStream = FileOutputStream(filePath)
            while (true) {
                val read = inputStream.read(fileReader)
                if (read == -1) {
                    break
                }
                outputStream.write(fileReader, 0, read)
                fileZizeDownloaded += read.toLong()

            }
            outputStream.flush()
            
        } catch (e: IOException) {
            e.printStackTrace()
           
        } finally {
            inputStream?.close()
            outputStream?.close()
        }
    }
}
person Madhusudan SH    schedule 27.05.2021