Как сохранить PDF в хранилище с ограниченным объемом?

До введения ограниченного хранилища я использовал Диспетчер загрузок для загрузки PDF-файла в свое приложение и получения PDF-файла из getExternalStorageDirectory, но из-за ограниченного хранилища я больше не могу использовать getExternalStorageDirectory, поскольку он устарел. Я решил отказаться от диспетчера загрузок, так как он загружает файлы в общедоступный каталог, и вместо этого использовать модификацию для загрузки файла pdf. Я знаю, что могу использовать тег requiredLegacyStorage в Android Manifest, но он неприменим к Android 11, поэтому я его не использую.

Вот мой код

fun readAndDownloadFile(context: Context) {
        readQuraanInterface?.downloadFile()
        Coroutines.io {
            file = File(context.filesDir,"$DESTINATION_DIRECTORY/$FILE_NAME$FILE_EXTENSION")
            if (file?.exists() == true) {
                renderPDF()
                showPdf(mPageIndex, Direction.None)
            } else {

                Log.i("new","new0")
                val response = readQuraanRepository.downloadPdf()
                if (response.isSuccessful) {
                    Log.i("new","new00 ${file!!.path} ${response.body()?.byteStream().toString()}")
                    response.body()?.byteStream()?.let {
                        file!!.copyInputStreamToFile(
                            it
                        )
                    }
                    Log.i("new","new1")
//                    renderPDF()
//                    showPdf(mPageIndex, Direction.None)
                } else {
                    Log.i("new","new2")
                    Coroutines.main {
                        response.errorBody()?.string()
                            ?.let { readQuraanInterface?.downloadFailed(it) }
                    }
                }
            }

        }

    }

    private fun File.copyInputStreamToFile(inputStream: InputStream) {
        this.outputStream().use { fileOut ->
            Log.i("new","new30")
            inputStream.copyTo(fileOut)
        }
    }

Хотя идентификатор pdf загружен, но файл никогда не сохраняется с помощью вспомогательной функции InputStream, которую я написал. Мне нужно добавить этот PDF-файл во внутреннее хранилище моего приложения, а также отрендерить его, который я визуализирую с помощью PDFRenderer.


person Neat    schedule 09.05.2020    source источник
comment
Как вы проверяете, сохранен ли файл? Если он не сохранен, возникнет ошибка / исключение.   -  person blackapps    schedule 09.05.2020
comment
renderPDF()? Почему у этой функции нет параметра пути?   -  person blackapps    schedule 09.05.2020
comment
@blackapps Как только pdf загружается, мое приложение вылетает. Невозможно создать файл   -  person Pritish    schedule 09.05.2020
comment
Ты не ответил на мой вопрос. Странный. Он выйдет из строя, потому что вы не поймаете исключения. Logcat сообщит вам о проблеме.   -  person blackapps    schedule 09.05.2020
comment
@blackapps Logcat не показывает ошибок. Приложение точно не вылетает. Он вылетает, но восстанавливается автоматически   -  person Neat    schedule 09.05.2020


Ответы (3)


Вы можете использовать приведенный ниже код для загрузки и сохранения PDF с использованием хранилища с ограниченным объемом. Здесь я использую каталог Загрузки. Не забудьте предоставить необходимые разрешения.

@RequiresApi(Build.VERSION_CODES.Q)
fun downloadPdfWithMediaStore() {
    CoroutineScope(Dispatchers.IO).launch {
        try {
            val url =
                URL("https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf")
            val connection: HttpURLConnection = url.openConnection() as HttpURLConnection
            connection.requestMethod = "GET"
            connection.doOutput = true
            connection.connect()
            val pdfInputStream: InputStream = connection.inputStream

            val values = ContentValues().apply {
                put(MediaStore.Downloads.DISPLAY_NAME, "test")
                put(MediaStore.Downloads.MIME_TYPE, "application/pdf")
                put(MediaStore.Downloads.IS_PENDING, 1)
            }

            val resolver = context.contentResolver

            val collection =
                MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)

            val itemUri = resolver.insert(collection, values)

            if (itemUri != null) {
                resolver.openFileDescriptor(itemUri, "w").use { parcelFileDescriptor ->
                    ParcelFileDescriptor.AutoCloseOutputStream(parcelFileDescriptor)
                        .write(pdfInputStream.readBytes())
                }
                values.clear()
                values.put(MediaStore.Downloads.IS_PENDING, 0)
                resolver.update(itemUri, values, null, null)
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }
}
person Shahbaz Hashmi    schedule 19.05.2020
comment
Как использовать эту функцию для Java? - person jazzbpn; 19.08.2020
comment
Есть ли какие-либо выводы для xamarin.forms? - person Monica; 09.03.2021
comment
не работает андроид 11 !! если я использую настраиваемый URL-адрес со следующими ошибками, downloadPdfWithMediaStore $ 1.invokeSuspend System.err: java.io.FileNotFoundException: - person Paramjit Singh Rana; 28.05.2021

Это более чистое решение, если вы сохраняете файл с помощью модифицированных динамических URL-адресов.

  1. Создать Api
interface DownloadFileApi {

   @Streaming
   @GET
   suspend fun downloadFile(@Url fileUrl: String): Response<ResponseBody>
}

И вы можете создать экземпляр, например

 Retrofit.Builder()
         .baseUrl("http://localhost/") /* We use dynamic URL (@Url) the base URL will be ignored */
         .build()
         .create(DownloadFileApi::class.java)

ПРИМЕЧАНИЕ. Вам необходимо установить действительный baseUrl, даже если вы его не используете, поскольку он требуется разработчиком модернизации.

  1. Сохраните результат InputStream на устройстве хранения (для этого вы можете создать UseCase)
class SaveInputStreamAsPdfFileOnDirectoryUseCase {

    /**
     * Create and save inputStream as a file in the indicated directory
     * the inputStream to save will be a PDF file with random UUID as name
     */
    suspend operator fun invoke(inputStream: InputStream, directory: File): File? {
        var outputFile: File? = null
        withContext(Dispatchers.IO) {
            try {
                val name = UUID.randomUUID().toString() + ".pdf"
                val outputDir = File(directory, "outputPath")
                outputFile = File(outputDir, name)
                makeDirIfShould(outputDir)
                val outputStream = FileOutputStream(outputFile, false)
                inputStream.use { fileOut -> fileOut.copyTo(outputStream) }
                outputStream.close()
            } catch (e: IOException) {
                // Something went wrong
            }
        }
        return outputFile
    }

    private fun makeDirIfShould(outputDir: File) {
        if (outputDir.exists().not()) {
            outputDir.mkdirs()
        }
    }
}
  1. Вызовите api и примените вариант использования: D
class DownloadFileRepository constructor(
    private val service: DownloadFileApi,
    private val saveInputStreamAsPdfFileOnDirectory: SaveInputStreamAsPdfFileOnDirectoryUseCase
) {

    /**
     * Download pdfUrl and save result as pdf file in the indicated directory
     *
     * @return Downloaded pdf file
     */
    suspend fun downloadFileIn(pdfUrl: String, directory: File): File? {
        val response = service.downloadFile(pdfUrl)
        val responseBody = responseToBody(response)
        return responseBody?.let { saveInputStreamAsFileOnDirectory(it.byteStream(), directory) }
    }
    
    fun responseToBody(response: Response<ResponseBody>): ResponseBody? {
        if (response.isSuccessful.not() || response.code() in 400..599) {
            return null
        }
        return response.body()
    }
}

ПРИМЕЧАНИЕ. Вы можете использовать ContextCompat.getExternalFilesDirs(applicationContext, "documents").firstOrNull() в качестве каталога для сохранения.

person Juan Fraga    schedule 10.02.2021

Я использую приведенный ниже код с целевым API 30 и после загрузки его сохранения во внутреннем каталоге загрузки

       DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));//url=The download url of file
                    request.setMimeType(mimetype);
                    //------------------------COOKIE!!------------------------
                    String cookies = CookieManager.getInstance().getCookie(url);
                    request.addRequestHeader("cookie", cookies);
                    //------------------------COOKIE!!------------------------
                    request.addRequestHeader("User-Agent", userAgent);
                    request.setDescription("Qawmi Library Downloading");//Description
                    request.setTitle(pdfFileName);//pdfFileName=String Name of Pdf file
                    request.allowScanningByMediaScanner();
                    request.setAllowedOverMetered(true);
                    request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
                    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
                        request.setDestinationInExternalPublicDir("/Qawmi Library"/*Custom directory name below api 29*/, pdfFileName);
                    } else {
//Higher then or equal api-29                        
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS,"/"+pdfFileName);
                    }
                    DownloadManager dm = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
                    dm.enqueue(request);
person Eazy Class    schedule 09.02.2021