はじめに
Android アプリで PDF を作成・表示することは、レポート生成、請求書作成、ドキュメント共有など、実務的なアプリケーション開発で頻繁に必要になります。
この記事では、Android で PDF を扱う 2 つの主要な方法(PdfDocument と PdfRenderer)を完全解説します。2026 年時点での最新の API、Kotlin での実装、トラブルシューティングまでをカバーしています。
PdfDocument とは
概要
PdfDocument は、Android 5.0(API 21)で導入された PDF 生成用のクラスです。アプリケーション内でプログラマティックに PDF を作成・レンダリングできます。
- 用途:PDF ファイルの作成(ジェネレーション)
- 対応 API:API 19 以上
- クラス:
android.graphics.pdf.PdfDocument
メリット・デメリット
| 項目 | メリット | デメリット |
|---|---|---|
| 柔軟性 | 完全なカスタマイズが可能 | 実装が複雑 |
| 依存性 | 外部ライブラリ不要 | Android Framework に依存 |
| パフォーマンス | 中程度(小~中規模 PDF) | 大規模 PDF では遅延の可能性 |
| 用途 | レポート、請求書、領収書作成 | 複雑なレイアウトは困難 |
PdfRenderer とは
概要
PdfRenderer は、Android 5.0(API 21)で導入された PDF 表示・レンダリング用のクラスです。既存の PDF ファイルを画像としてレンダリングして表示します。
- 用途:PDF ファイルの表示・レンダリング
- 対応 API:API 21 以上
- クラス:
android.graphics.pdf.PdfRenderer
メリット・デメリット
| 項目 | メリット | デメリット |
|---|---|---|
| シンプル | 実装が簡潔 | カスタマイズが限定的 |
| パフォーマンス | 高速(ネイティブレンダリング) | メモリ使用量が多い可能性 |
| 出力形式 | 高品質な画像として表示 | PDF ファイル自体の編集は不可 |
| 用途 | PDF ファイルの表示・閲覧 | PDF 生成には不適 |
PdfDocument vs PdfRenderer 比較表
| 項目 | PdfDocument | PdfRenderer |
|---|---|---|
| 目的 | PDF 生成 | PDF 表示 |
| 対応 API | API 19+ | API 21+ |
| 入力 | アプリケーションコード | PDF ファイル |
| 出力 | PDF ファイル | ビットマップ画像 |
| 使用難度 | 中程度(複雑) | 簡単 |
| カスタマイズ性 | 高い | 低い |
| パフォーマンス | 中速 | 高速 |
| 主な用途 | レポート、請求書、データエクスポート | PDF ビューア、ドキュメント表示 |
実装方法
前提条件(環境セットアップ)
// build.gradle.kts (Module: app)
android {
compileSdk = 34 // 2026年推奨:API 34-35
defaultConfig {
minSdk = 21 // PdfDocument/PdfRenderer対応の最小値
targetSdk = 34
}
}
dependencies {
// Kotlin stdlib
implementation("org.jetbrains.kotlin:kotlin-stdlib:1.9.0")
}
PdfDocument での実装例
用途:レポート、請求書、ドキュメント作成
// Kotlin での実装例(2026年版)
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.pdf.PdfDocument
import android.os.Environment
import java.io.File
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class PdfReportGenerator(private val context: Context) {
suspend fun generatePdfReport(): File = withContext(Dispatchers.IO) {
// PDF ドキュメント作成
val pdfDocument = PdfDocument()
val pageInfo = PdfDocument.PageInfo.Builder(595, 842, 1).create()
val page = pdfDocument.startPage(pageInfo)
val canvas = page.canvas
// ページに描画
val paint = Paint().apply {
textSize = 16f
}
canvas.drawText("サンプル PDF レポート", 50f, 50f, paint)
canvas.drawText("生成日時: 2026年4月15日", 50f, 100f, paint)
canvas.drawText("内容: Android PDF 生成のデモンストレーション", 50f, 150f, paint)
pdfDocument.finishPage(page)
// ファイル保存
val pdfFile = File(context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS), "report.pdf")
pdfDocument.writeTo(pdfFile.outputStream())
pdfDocument.close()
return@withContext pdfFile
}
}
// 使用例
// val generator = PdfReportGenerator(context)
// val pdfFile = generator.generatePdfReport()
PdfRenderer での実装例
用途:PDF ファイルの表示・閲覧
// Kotlin での実装例(2026年版)
import android.content.Context
import android.graphics.Bitmap
import android.graphics.pdf.PdfRenderer
import android.os.ParcelFileDescriptor
import java.io.File
class PdfViewerUtil(private val context: Context) {
fun renderPdfPage(pdfFile: File, pageNumber: Int): Bitmap? {
return try {
val fileDescriptor = ParcelFileDescriptor.open(pdfFile, ParcelFileDescriptor.MODE_READ_ONLY)
val pdfRenderer = PdfRenderer(fileDescriptor)
// ページ数確認
if (pageNumber >= pdfRenderer.pageCount) {
return null
}
// ページをビットマップとしてレンダリング
val page = pdfRenderer.openPage(pageNumber)
val bitmap = Bitmap.createBitmap(page.width, page.height, Bitmap.Config.ARGB_8888)
page.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY)
page.close()
pdfRenderer.close()
bitmap
} catch (e: Exception) {
e.printStackTrace()
null
}
}
}
// 使用例
// val viewer = PdfViewerUtil(context)
// val bitmap = viewer.renderPdfPage(pdfFile, 0)
// imageView.setImageBitmap(bitmap)
ライブラリを使った簡単実装
より高度な PDF 操作が必要な場合、以下のライブラリを検討してください。
iText(商用・オープンソース)
dependencies {
// iText 5 (無料版)
implementation("com.itextpdf:itextg:5.5.13.3")
}
PDFBox(Apache オープンソース)
dependencies {
// Apache PDFBox
implementation("org.apache.pdfbox:pdfbox-android:2.0.27.0")
}
権限設定(AndroidManifest.xml)
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- ファイル読み書き権限(Android 13以前)-->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- Android 12+: Scoped Storage自動対応 -->
</manifest>
Kotlin での実装(2026年版 - モダン Android)
coroutine を使った非同期 PDF 生成
// Kotlin Coroutine での実装例
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
class PdfGeneratorViewModel(private val generator: PdfReportGenerator) : ViewModel() {
fun generatePdfAsync() {
viewModelScope.launch {
try {
val pdfFile = generator.generatePdfReport()
// UI 更新
println("PDF 生成完了: ${pdfFile.absolutePath}")
} catch (e: Exception) {
// エラーハンドリング
println("PDF 生成エラー: ${e.message}")
}
}
}
}
// UI 層での使用例(Jetpack Compose)
@Composable
fun PdfGeneratorScreen(viewModel: PdfGeneratorViewModel) {
Button(onClick = { viewModel.generatePdfAsync() }) {
Text("PDF を生成")
}
}
Android 12+ での変更点
Scoped Storage
Android 12 以降、外部ストレージへのアクセス方法が変わりました。
- 変更点:
WRITE_EXTERNAL_STORAGE権限が無視される - 推奨方法:
getExternalFilesDir()を使用(アプリ固有ディレクトリ) - 代替手段:
FileProviderまたはIntent.ACTION_CREATE_DOCUMENT
getExternalFilesDir() を使用してアプリ固有ディレクトリにファイルを保存することが標準実装です。
// Android 12+ での推奨実装
val pdfDir = context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS)
val pdfFile = File(pdfDir, "report_${System.currentTimeMillis()}.pdf")
// ファイルの保存・共有
val fileUri = FileProvider.getUriForFile(context, "${context.packageName}.fileprovider", pdfFile)
// 共有インテント
val shareIntent = Intent(Intent.ACTION_SEND).apply {
type = "application/pdf"
putExtra(Intent.EXTRA_STREAM, fileUri)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
よくあるエラーと対処法
FileNotFoundException
java.io.FileNotFoundException: /storage/emulated/0/Documents/report.pdf (Permission denied)
- ファイルの保存先に権限がない
- ディレクトリが存在しない
下記のように、Android 12+ 推奨の方法でファイルを保存してください。
// ❌ 間違い
val pdfFile = File("/sdcard/Documents/report.pdf")
// ✓ 正解(Android 12+ 推奨)
val pdfFile = File(context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS), "report.pdf")
// または
val pdfFile = File(context.cacheDir, "report.pdf")
SecurityException(Android 12+)
java.lang.SecurityException: Permission Denial: opening provider android.content.ContentProvider
- 外部ストレージへのアクセス権限不足
- FileProvider の設定不備
1. FileProvider を res/xml/file_paths.xml で設定
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-files-path name="documents" path="Documents/" />
</paths>
2. AndroidManifest.xml で宣言
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
OutOfMemoryError(大規模 PDF)
java.lang.OutOfMemoryError: Failed to allocate [size] bytes
- 複数ページの PDF を一度にメモリに読み込み
- 高解像度でレンダリング
// ページごとにレンダリング(メモリ効率化)
fun renderPdfPageSafely(pdfFile: File, pageNumber: Int): Bitmap? {
return try {
val fileDescriptor = ParcelFileDescriptor.open(pdfFile, ParcelFileDescriptor.MODE_READ_ONLY)
val pdfRenderer = PdfRenderer(fileDescriptor)
val page = pdfRenderer.openPage(pageNumber)
// 低解像度でレンダリング(メモリ節約)
val scale = 1.5f
val bitmap = Bitmap.createBitmap(
(page.width * scale).toInt(),
(page.height * scale).toInt(),
Bitmap.Config.RGB_565 // ARGB_8888 より軽い
)
page.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY)
page.close()
pdfRenderer.close()
bitmap
} catch (e: OutOfMemoryError) {
e.printStackTrace()
null
}
}
実装例のリソース
- 公式ドキュメント:PdfDocument - Android Developers
- サンプルコード:Android Graphics PDF GitHub
- iText ライブラリ:iText - PDF Software
関連記事
まとめ
Android で PDF を扱う場合、以下の判断基準で実装方法を選択してください。
- PDF を生成したい場合:PdfDocument を使用(または iText などのライブラリ)
- PDF を表示・閲覧したい場合:PdfRenderer を使用
- 複雑な PDF 操作が必要な場合:iText や PDFBox などのライブラリを導入
2026 年時点では、Kotlin + Coroutine での非同期処理が標準的な実装パターンです。また、Android 12 以降の Scoped Storage への対応は必須です。
この記事で紹介したコード例は、実際のプロジェクトに適応させて使用してください。不明な点や実装時のトラブルは、コメント欄でお気軽にお尋ねください。