4

在引入范围存储之前,我使用下载管理器在我的应用程序中下载 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 id,但该文件从未使用我编写的 InputStream 辅助函数存储。我需要将该 pdf 添加到我的应用程序的内部存储中,并使用 PDFRenderer 渲染它。

4

3 回答 3

4

您可以使用以下代码使用范围存储下载和保存 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()
        }
    }
}
于 2020-05-19T07:59:41.933 回答
3

如果您使用 Retrofit 动态 Urls 保存文件,这是一个更干净的解决方案。

  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()用作保存目录

于 2021-02-10T03:16:02.613 回答
0

我将以下代码与目标 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);
于 2021-02-09T20:26:27.570 回答