Android PDF 作成と表示【完全ガイド】PdfDocument vs PdfRenderer【2026年版】

はじめに

Android アプリで PDF を作成・表示することは、レポート生成、請求書作成、ドキュメント共有など、実務的なアプリケーション開発で頻繁に必要になります。

この記事では、Android で PDF を扱う 2 つの主要な方法(PdfDocumentPdfRenderer)を完全解説します。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
💡 推奨実装パターン:Android 12+ では 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
    }
}

実装例のリソース

関連記事

まとめ

Android で PDF を扱う場合、以下の判断基準で実装方法を選択してください。

  • PDF を生成したい場合PdfDocument を使用(または iText などのライブラリ)
  • PDF を表示・閲覧したい場合PdfRenderer を使用
  • 複雑な PDF 操作が必要な場合iTextPDFBox などのライブラリを導入

2026 年時点では、Kotlin + Coroutine での非同期処理が標準的な実装パターンです。また、Android 12 以降の Scoped Storage への対応は必須です。

この記事で紹介したコード例は、実際のプロジェクトに適応させて使用してください。不明な点や実装時のトラブルは、コメント欄でお気軽にお尋ねください。