android - 使用 Intent.ACTION_CREATE_DOCUMENT 选择文件后保存从 WebView 生成的 PDF 文件

标签 android android-studio kotlin android-fragments android-intent

上下文:安卓 10,API 29 .
我打印了一个由 WebView 生成的 PDF 文件,但现在我想将其保存到文件中。所以我尝试了Intent.ACTION_CREATE_DOCUMENT选择文件并通过 printAdapter 保存的onWrite方法。
问题是 文件始终为空 - 0 个字节 - 并且不会引发错误。它只是调用onWriteFailed ,但有一个空的错误消息。choosenFileUri具有类似 content://com.android.providers.downloads.documents/document/37 的值

我用来开始选择新文件的 Intent 的方法。请注意,此 Activity 的结果是 Uri :

fun startIntentToCreatePdfFile(fragment: Fragment, filename : String) {

    val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
        addCategory(Intent.CATEGORY_OPENABLE)
        type = "application/pdf"
        putExtra(Intent.EXTRA_TITLE, filename)
    }

    fragment.startActivityForResult(intent, IntentCreatePdfDocument)
}
我用来将PDF“打印”到文件的方法。 fileUri来自Intent.ACTION_CREATE_DOCUMENT :
fun printPdfToFile(
    context: Context,
    webView: WebView,
    fileUri: Uri
) {

    (context.getSystemService(Context.PRINT_SERVICE) as? PrintManager)?.let {
        val jobName = "Print PDF to save it"
        val printAdapter = webView.createPrintDocumentAdapter(jobName)

        val printAttributes = PrintAttributes.Builder()
            .setMediaSize(PrintAttributes.MediaSize.ISO_A4)
            .setResolution(PrintAttributes.Resolution("pdf", "pdf", 600, 600))
            .setMinMargins(PrintAttributes.Margins.NO_MARGINS).build()

        printAdapter.onLayout(null, printAttributes, null, object : LayoutResultCallback() {
            override fun onLayoutFinished(info: PrintDocumentInfo, changed: Boolean) {

                context.contentResolver.openFileDescriptor(fileUri, "w")?.use {
                    printAdapter.onWrite(
                        arrayOf(PageRange.ALL_PAGES),
                        it,
                        CancellationSignal(),
                        object : WriteResultCallback() {

                        })
                }

            }
        }, null)
    }
}
我做什么选择文件onActivityResult :
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)

    if (resultCode != Activity.RESULT_OK) {
        return null
    }

    if (requestCode != IntentCreatePdfDocument) {
        throw Exception("RequestCode not implemented: $requestCode")
    }

    val choosenFileUri = data?.data

    // If it is null, nothing to do
    if (choosenFileUri == null) {
        return
    }

    try {

        HtmlHelpers.savePdfFromHtml(
            requireContext(),
            "html document to be represented in the WebView",
            choosenFileUri)

    } catch (exception: Exception) {
        _logger.error(exception)
        Helpers.showError(requireActivity(), getString(R.string.generic_error))
    }

    dismiss()
}
...在哪里 HtmlHelpers.savePdfFromHtml是:
fun savePdfFromHtml(
    context: Context,
    htmlContent: String,
    fileUri: Uri
) {
    generatePdfFromHtml(
        context,
        htmlContent
    ) { webView ->

        PrintHelpers.printPdfToFile(
            context,
            webView,
            fileUri)
    }
}
...和 ​​generatePdfFromHtml是:
private fun generatePdfFromHtml(
    context: Context,
    htmlContent: String,
    onPdfCreated: (webView: WebView) -> Unit
) {

    val webView = WebView(context)
    webView.settings.javaScriptEnabled = true
    webView.webViewClient = object : WebViewClient() {

        override fun onPageFinished(webView: WebView, url: String) {
            onPdfCreated(webView)
        }

    }

    webView.loadDataWithBaseURL(
        null,
        htmlContent,
        "text/html; charset=utf-8",
        "UTF-8",
        null);

}

我检查了有关此主题的所有其他答案,但每个人都手动创建 ParcelFileDescriptor而不是 itonWrite方法。每个人都会做这样的事情:
fun getOutputFile(path: File, fileName: String): ParcelFileDescriptor? {
    val file = File(path, fileName)
    return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE)
}
但我不能这样做,因为我只有 Uri。

编辑:正如@blackapps 所建议的,我在得到 FileDescriptor 后尝试打开输出流, 但是 我仍然得到相同的结果 :
context.contentResolver.openFileDescriptor(fileUri, "w")?.use {

    val fileDescriptor = it
    FileOutputStream(it.fileDescriptor).use {
        printAdapter.onWrite(
            arrayOf(PageRange.ALL_PAGES),
            fileDescriptor,
            CancellationSignal(),
            object : WriteResultCallback() {

            })

    }


}

最佳答案

为了从 HTML 内容生成 PDF 文件,我使用了 Library
我将 pdf 文件存储在 download 中共享存储文件夹(外部)。使用以下方法检索位置。

       //fileName is the name of the file that you want to save. 
      fun getSavedFile(context: Context, fileName: String): File {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                return File(
                    context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)!!,
                    "$fileName.pdf"
                )
            }
            return File(
                Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
                "$fileName.pdf"
            )
        }
然后使用库内置方法从 HTML 生成 PDF WebView 内的内容加载
//webView is the ID of WebView where we are loading the html content
// fileName : Pass whatever name you want.
            
   PDFUtil.generatePDFFromWebView(pdfDownloadUtil.getSavedFile(getApplicationContext(), fileName), webView, new PDFPrint.OnPDFPrintListener() {
                @Override
                public void onSuccess(File file) {
                    savedPdfFile = file;
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                        createFile(Uri.fromFile(file));
                    } 
                }

                @Override
                public void onError(Exception exception) {

                }
            });
        }
现在,我们需要在获取传递的 html 内容的 pdf 后触发 Intent 。
  private void createFile(Uri pickerInitialUri) {
        Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
        intent.addCategory(Intent.CATEGORY_OPENABLE);
        intent.setType("application/pdf");
        intent.putExtra(Intent.EXTRA_TITLE, fileName);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri);
        }
        createFileResult.launch(intent);
    }

   ActivityResultLauncher<Intent> createFileResult = registerForActivityResult(
            new ActivityResultContracts.StartActivityForResult(),
            result -> {
                if (result.getResultCode() == AppCompatActivity.RESULT_OK) {
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                        pdfDownloadUtil.writeFileContent(this, savedPdfFile, result.getData().getData(), () -> {
                            showSnackToOpenPdf();
                            return null;
                        });
                    }
                }
            }
    );
在 oreo 或以上设备中,我们使用上述方法,即 写入文件内容
@RequiresApi(Build.VERSION_CODES.O)
    fun writeFileContent(
        context: @NotNull Context,
        savedPdfFile: @NotNull File,
        uri: @NotNull Uri,
        listener: () -> Unit
    ) {
        try {
            val file = uri.let { context.contentResolver.openFileDescriptor(it, "w") }

            file?.let {
                val fileOutputStream = FileOutputStream(it.fileDescriptor)
                fileOutputStream.write(Files.readAllBytes(savedPdfFile.toPath()))
                fileOutputStream.close()
                it.close()
            }
            listener()


        } catch (e: FileNotFoundException) {
            //print logs
            e.printStackTrace()
        } catch (e: IOException) {
            //print logs
            e.printStackTrace()
        }
    }
注:如果页数很大,即超过 200 页。然后它将无法在内部工作,因为它会缓存 WebView 中的页面然后加载它。因此,另一种方法是从 API 获取 PDF 文件的链接。

关于android - 使用 Intent.ACTION_CREATE_DOCUMENT 选择文件后保存从 WebView 生成的 PDF 文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63922751/

相关文章:

android - 使用 Retrofit 方法更具表现力

kotlin - Gradle 子模块 compileOnly 函数不存在

android - 陀螺仪没有任何漂移

android - 如何将 Ionic 设置为默认呈现 iOS 样式?

android - 屏幕底部的 ActionBarSherlock

android - 在 android 中使用 jts 拓扑套件

java - findViewByID 中的变量 - Android Studio - Java

android - Android Studio内部构建失败(命令行有效)

java - 我在 Android Studio 中遇到 "inflate exception"错误

android - 数据库更新协程完成后,设置 bool 标志