使用 Intent.ACTION_CREATE_DOCUMENT 选择文件后保存从 WebView 生成的 PDF 文件
Posted
技术标签:
【中文标题】使用 Intent.ACTION_CREATE_DOCUMENT 选择文件后保存从 WebView 生成的 PDF 文件【英文标题】:Save a PDF file generated from a WebView after picking the file with Intent.ACTION_CREATE_DOCUMENT 【发布时间】:2021-01-03 10:40:38 【问题描述】:上下文:Android 10,API 29。
我打印了一个从WebView
生成的 PDF 文件,但现在我想将它保存到一个文件中。所以我尝试使用Intent.ACTION_CREATE_DOCUMENT
选择文件并通过printAdapter
的onWrite
方法保存。
问题在于文件始终为空 - 0 字节 - 并且没有引发错误。它只是调用onWriteFailed
,但错误消息为空。
choosenFileUri
的值类似于 content://com.android.providers.downloads.documents/document/37
我用来启动 Intent 以选择新文件的方法。请注意,此活动的结果是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);
我检查了有关此主题的所有其他答案,但每个人都在 onWrite
方法中手动创建 ParcelFileDescriptor
而不是 it
。每个人都这样做:
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()
)
【问题讨论】:
同时显示您的 onActivityResult() 代码。 您是否忘记为该描述符打开 FileOutputStream? @blackapps 我更新了答案! 不,仅此而已。希望其他人能提供更好的帮助。 我有同样的问题content://com.android.providers.downloads.documents/document/37
是一个下载文件夹,然后我使用库来解决这个库 github.com/HBiSoft/PickiT 的 uri,希望这有帮助
【参考方案1】:
为了从 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
内容加载到WebView
中生成PDF
//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 或以上设备中,我们使用上述方法,即 writeFileContent
@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 文件的链接。
【讨论】:
【参考方案2】:我检查了有关此主题的所有其他答案,但每个人都手动创建 ParcelFileDescriptor 而不是在 onWrite 方法中创建它。每个人都这样做:
fun getOutputFile(path: File, fileName: String): ParcelFileDescriptor?
val file = File(path, fileName)
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE)
但我不能这样做,因为我只有 Uri。
-> Uri 包含 getPath() 方法。您可以使用它来创建文件对象,
val file = File(uri.path)
并在 ParcelFileDescriptor 中使用。空检查 getPath() 方法。
【讨论】:
如果我这样做,它会在打开 ParcelFileDescription 时抛出异常,提示该文件不存在!以上是关于使用 Intent.ACTION_CREATE_DOCUMENT 选择文件后保存从 WebView 生成的 PDF 文件的主要内容,如果未能解决你的问题,请参考以下文章
在使用加载数据流步骤的猪中,使用(使用 PigStorage)和不使用它有啥区别?
Qt静态编译时使用OpenSSL有三种方式(不使用,动态使用,静态使用,默认是动态使用)