使用具有向后兼容性的 iText 保存 PDF 以供共享
Saving PDF for sharing using iText with backwards compatibility
在 Android 上保存 PDF 文件的问题困扰了我几个星期,每次 Google 搜索我都会得到一个紫色链接列表。
我尝试了很多方法,但只能使用内置的 PDF 创建器保存 PDF 文件,这不能满足我的目的。
我有很多问题想寻求答案,希望你们中的一些人能提供帮助。
问题 1:要分享然后立即删除的 PDF 文件应该保存在哪里?
Q1 a) 是否需要 ContentResolver 和 ContentProvider,或者这仅在应用程序之间共享 'directory' 时才需要?
直觉上,将 PDF 保存在内部缓存目录中、从那里共享然后删除它对我来说最有意义。
问题2:向后兼容的文件存储方式是什么?
据我了解,不同的 SDK 级别需要不同的文件存储方法,即:
- <= SDK 28使用传统的存储方式,即(using File API)?
- <= SDK 29 如果使用外部存储,您应该请求旧存储的权限
- >= SDK 30 Android 强制分区存储
使用 SAF(存储访问框架)似乎是 recommended way 存储 PDF 等文档的方法。但是,当用户只是生成和共享 PDF 时,我不打算向他们显示文件选择器。
我遇到了这个非常普遍的异常,它发生在 iText 关闭输出流时。下面是一些代码示例及其产生的错误。
SDK 30 文件API测试
fun createPdfInternalSDK30(context: Context) {
val fileName = "test.pdf"
// MODE_PRIVATE creates in internal storage, right?
val out = context.openFileOutput(fileName, Context.MODE_PRIVATE)
// out path: /data/user/0/dk.overlevelsesguiden.de10her/files/test.pdf
try {
val writer = PdfWriter(out)
val pdf = PdfDocument(writer)
Document(pdf, PageSize.A4, false).apply {
add(Paragraph("Test"))
close()
}
} catch (e: IOException) {
e.printStackTrace()
}
}
错误
java.lang.ExceptionInInitializerError
at com.itextpdf.commons.actions.producer.ProducerBuilder.modifyProducer(ProducerBuilder.java:97)
at com.itextpdf.kernel.actions.events.FlushPdfDocumentEvent.doAction(FlushPdfDocumentEvent.java:103)
at com.itextpdf.commons.actions.EventManager.onEvent(EventManager.java:71)
at com.itextpdf.kernel.pdf.PdfDocument.close(PdfDocument.java:849)
at com.itextpdf.layout.Document.close(Document.java:117)
at dk.overlevelsesguiden.de10her.business.PDFService.createPdfInternalSDK30(PDFService.kt:118)
Caused by: java.util.regex.PatternSyntaxException: Syntax error in regexp pattern near index 12
$\{([^}]*)}
SDK 29+ 分区存储测试
@RequiresApi(api = Build.VERSION_CODES.Q)
fun createPdfSharedSDK29plus(context: Context) {
val pdfCollection = MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, "title")
put(MediaStore.MediaColumns.MIME_TYPE, "application/pdf")
}
var uri: Uri? = null
try {
uri = context.contentResolver.insert(pdfCollection, contentValues) ?: throw IOException("Failed to create new MediaStore record")
val out = context.contentResolver.openOutputStream(uri)
val writer = PdfWriter(out)
val pdf = PdfDocument(writer)
Document(pdf, PageSize.A4, false).apply {
add(Paragraph("Test"))
close()
}
} catch (e: IOException) {
uri?.let { orphanUri ->
context.contentResolver.delete(orphanUri, null, null)
}
}
}
错误
java.lang.ExceptionInInitializerError
at com.itextpdf.commons.actions.producer.ProducerBuilder.modifyProducer(ProducerBuilder.java:97)
at com.itextpdf.kernel.actions.events.FlushPdfDocumentEvent.doAction(FlushPdfDocumentEvent.java:103)
at com.itextpdf.commons.actions.EventManager.onEvent(EventManager.java:71)
at com.itextpdf.kernel.pdf.PdfDocument.close(PdfDocument.java:849)
at com.itextpdf.layout.Document.close(Document.java:117)
at dk.overlevelsesguiden.de10her.business.PDFService.finalTest(PDFService.kt:118)
Caused by: java.util.regex.PatternSyntaxException: Syntax error in regexp pattern near index 12
$\{([^}]*)}
清单权限
<!-- Without this folders will be inaccessible in Android-11 and above devices -->
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<!-- Without this entry storage-permission entry will not be visible under app-info permissions list Android-10 and below -->
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28"
/>
您提出了很多问题。此答案侧重于您显示的实际错误。
您显示的实际错误都会导致
java.util.regex.PatternSyntaxException: Syntax error in regexp pattern near index 12
$\{([^}]*)}
在 iText 初始化期间 class ProducerBuilder
。它发生在这里:
private static final String PATTERN_STRING = "\$\{([^}]*)}";
private static final Pattern PATTERN = Pattern.compile(PATTERN_STRING);
实际上,JRE 中的正则表达式与 Android 中的不同。在普通的 Java 中,正则表达式 $\{([^}]*)}
被接受,一个 }
表示如果没有未转义的开头 而不是 则字符需要转义 {
之前。 Android,另一方面,要求它即使在那时也被转义。
昨天已提交对此的修复 - https://github.com/itext/itext7/commit/32bf2552770866dce4516798e7b11f26631ae92f - 现在的模式字符串是:
private static final String PATTERN_STRING = "\$\{([^}]*)\}";
因此,在下一个 iText 版本中(或在真正当前的 SNAPSHOT 中),不会再出现这个错误。
在 Android 上保存 PDF 文件的问题困扰了我几个星期,每次 Google 搜索我都会得到一个紫色链接列表。
我尝试了很多方法,但只能使用内置的 PDF 创建器保存 PDF 文件,这不能满足我的目的。
我有很多问题想寻求答案,希望你们中的一些人能提供帮助。
问题 1:要分享然后立即删除的 PDF 文件应该保存在哪里?
Q1 a) 是否需要 ContentResolver 和 ContentProvider,或者这仅在应用程序之间共享 'directory' 时才需要?
直觉上,将 PDF 保存在内部缓存目录中、从那里共享然后删除它对我来说最有意义。
问题2:向后兼容的文件存储方式是什么?
据我了解,不同的 SDK 级别需要不同的文件存储方法,即:
- <= SDK 28使用传统的存储方式,即(using File API)?
- <= SDK 29 如果使用外部存储,您应该请求旧存储的权限
- >= SDK 30 Android 强制分区存储
使用 SAF(存储访问框架)似乎是 recommended way 存储 PDF 等文档的方法。但是,当用户只是生成和共享 PDF 时,我不打算向他们显示文件选择器。
我遇到了这个非常普遍的异常,它发生在 iText 关闭输出流时。下面是一些代码示例及其产生的错误。
SDK 30 文件API测试
fun createPdfInternalSDK30(context: Context) {
val fileName = "test.pdf"
// MODE_PRIVATE creates in internal storage, right?
val out = context.openFileOutput(fileName, Context.MODE_PRIVATE)
// out path: /data/user/0/dk.overlevelsesguiden.de10her/files/test.pdf
try {
val writer = PdfWriter(out)
val pdf = PdfDocument(writer)
Document(pdf, PageSize.A4, false).apply {
add(Paragraph("Test"))
close()
}
} catch (e: IOException) {
e.printStackTrace()
}
}
错误
java.lang.ExceptionInInitializerError
at com.itextpdf.commons.actions.producer.ProducerBuilder.modifyProducer(ProducerBuilder.java:97)
at com.itextpdf.kernel.actions.events.FlushPdfDocumentEvent.doAction(FlushPdfDocumentEvent.java:103)
at com.itextpdf.commons.actions.EventManager.onEvent(EventManager.java:71)
at com.itextpdf.kernel.pdf.PdfDocument.close(PdfDocument.java:849)
at com.itextpdf.layout.Document.close(Document.java:117)
at dk.overlevelsesguiden.de10her.business.PDFService.createPdfInternalSDK30(PDFService.kt:118)
Caused by: java.util.regex.PatternSyntaxException: Syntax error in regexp pattern near index 12
$\{([^}]*)}
SDK 29+ 分区存储测试
@RequiresApi(api = Build.VERSION_CODES.Q)
fun createPdfSharedSDK29plus(context: Context) {
val pdfCollection = MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, "title")
put(MediaStore.MediaColumns.MIME_TYPE, "application/pdf")
}
var uri: Uri? = null
try {
uri = context.contentResolver.insert(pdfCollection, contentValues) ?: throw IOException("Failed to create new MediaStore record")
val out = context.contentResolver.openOutputStream(uri)
val writer = PdfWriter(out)
val pdf = PdfDocument(writer)
Document(pdf, PageSize.A4, false).apply {
add(Paragraph("Test"))
close()
}
} catch (e: IOException) {
uri?.let { orphanUri ->
context.contentResolver.delete(orphanUri, null, null)
}
}
}
错误
java.lang.ExceptionInInitializerError
at com.itextpdf.commons.actions.producer.ProducerBuilder.modifyProducer(ProducerBuilder.java:97)
at com.itextpdf.kernel.actions.events.FlushPdfDocumentEvent.doAction(FlushPdfDocumentEvent.java:103)
at com.itextpdf.commons.actions.EventManager.onEvent(EventManager.java:71)
at com.itextpdf.kernel.pdf.PdfDocument.close(PdfDocument.java:849)
at com.itextpdf.layout.Document.close(Document.java:117)
at dk.overlevelsesguiden.de10her.business.PDFService.finalTest(PDFService.kt:118)
Caused by: java.util.regex.PatternSyntaxException: Syntax error in regexp pattern near index 12
$\{([^}]*)}
清单权限
<!-- Without this folders will be inaccessible in Android-11 and above devices -->
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<!-- Without this entry storage-permission entry will not be visible under app-info permissions list Android-10 and below -->
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28"
/>
您提出了很多问题。此答案侧重于您显示的实际错误。
您显示的实际错误都会导致
java.util.regex.PatternSyntaxException: Syntax error in regexp pattern near index 12
$\{([^}]*)}
在 iText 初始化期间 class ProducerBuilder
。它发生在这里:
private static final String PATTERN_STRING = "\$\{([^}]*)}";
private static final Pattern PATTERN = Pattern.compile(PATTERN_STRING);
实际上,JRE 中的正则表达式与 Android 中的不同。在普通的 Java 中,正则表达式 $\{([^}]*)}
被接受,一个 }
表示如果没有未转义的开头 而不是 则字符需要转义 {
之前。 Android,另一方面,要求它即使在那时也被转义。
昨天已提交对此的修复 - https://github.com/itext/itext7/commit/32bf2552770866dce4516798e7b11f26631ae92f - 现在的模式字符串是:
private static final String PATTERN_STRING = "\$\{([^}]*)\}";
因此,在下一个 iText 版本中(或在真正当前的 SNAPSHOT 中),不会再出现这个错误。