안드로이드 WebView로 PDF 띄우기: 라이브러리 없이 한 줄로 시작하는 실전 가이드

Prompt Architect 편집팀 · 2026-06-18 · 7분

TL;DR — 별도 PDF 라이브러리 없이 WebView만으로 PDF를 표시하는 방법을 단계별로 정리했다. Google 뷰어 활용법부터 흔한 빈 화면 오류, 보안 설정, 오프라인 대안까지 실무 관점으로 다룬다.

안드로이드 앱을 만들다 보면 "PDF 한 번만 보여주면 되는데" 싶은 상황이 자주 생긴다. 약관, 영수증, 매뉴얼, 견적서 같은 문서를 화면에 띄우는 일이다. 그런데 막상 구현하려고 보면 PDF 렌더링 라이브러리는 용량이 크고, PdfRenderer API는 페이지를 비트맵으로 직접 그려야 해서 코드가 길어진다. 단순히 "보여주기만" 하면 되는데 배보다 배꼽이 커지는 셈이다.

이럴 때 가장 가벼운 해법이 바로 WebView를 이용하는 방식이다. 이 글에서는 라이브러리 의존성 없이 WebView로 PDF를 표시하는 방법을, 자주 막히는 지점과 함께 단계별로 정리한다.

왜 WebView로 PDF를 여는가

안드로이드의 네이티브 컴포넌트(TextView, ImageView 등)는 PDF 포맷을 인식하지 못한다. WebView도 사실은 PDF 렌더링 엔진을 내장하고 있지 않다. 즉, PDF 파일의 URL을 그대로 loadUrl()에 넘기면 대부분 다운로드만 되거나 빈 화면이 뜬다.

그럼에도 WebView를 쓰는 이유는, 외부 문서 뷰어 서비스를 경유하면 추가 라이브러리 없이 렌더링을 위임할 수 있기 때문이다. 가장 널리 쓰이는 것이 Google Docs Viewer다. PDF 주소를 뷰어 엔드포인트의 쿼리 파라미터로 넘기면, 서버가 PDF를 HTML로 변환해 돌려주고 WebView는 그 결과만 표시하면 된다.

동작 원리: 안드로이드는 PDF를 직접 못 그린다

핵심을 한 줄로 요약하면 이렇다.

WebView 자체는 PDF를 못 그린다. 대신 "PDF를 HTML로 바꿔주는 외부 서비스"의 화면을 띄운다.

원본 글에서 소개한 핵심 코드는 다음 형태였다.

// 원격 뷰어 URL로 PDF를 감싸서 로드
loadUrl("https://docs.google.com/viewer?url=" + PDF_URL);

이 한 줄의 의미는 "내 PDF 주소(PDF_URL)를 Google 뷰어에 넘겨, 뷰어가 그려준 페이지를 WebView로 본다"는 것이다. 라이브러리도, 권한 처리도, 비트맵 변환도 필요 없다.

1단계 — 가장 빠른 방법: 원격 뷰어 활용

먼저 가장 단순한 형태를 보자. 변수명과 구성을 실무에 맞게 새로 작성했다.

// 표시할 PDF의 원격 주소 (반드시 외부에서 접근 가능한 URL이어야 함)
val documentUrl = "https://example.com/files/contract-2026.pdf"

// URL은 반드시 인코딩한다 — 한글/공백/특수문자가 있으면 깨진다
val encodedUrl = java.net.URLEncoder.encode(documentUrl, "UTF-8")

// Google 뷰어에 PDF 주소를 파라미터로 전달 (embedded=true로 깔끔한 뷰)
val viewerUrl = "https://docs.google.com/viewer?embedded=true&url=$encodedUrl"

pdfWebView.loadUrl(viewerUrl)

여기서 두 가지가 원본보다 중요해진 포인트다.

  • embedded=true: 뷰어의 상단 툴바/헤더를 줄여 앱 안에 자연스럽게 들어가게 한다.
  • URLEncoder.encode: PDF 주소에 한글 파일명이나 쿼리스트링이 들어 있으면 인코딩하지 않으면 깨진다. 실무에서 가장 자주 빠뜨리는 부분이다.

2단계 — WebView 기본 설정

loadUrl만 호출하면 보통 빈 화면이 나온다. 외부 뷰어는 JavaScript로 동작하고, 리다이렉트가 일어나기 때문이다. 아래 설정이 사실상 필수다.

pdfWebView.settings.apply {
    javaScriptEnabled = true          // 뷰어가 JS로 렌더링하므로 필수
    loadWithOverviewMode = true       // 콘텐츠를 화면 폭에 맞춤
    useWideViewPort = true            // 뷰포트 메타태그 인식
    builtInZoomControls = true        // 확대/축소 허용
    displayZoomControls = false       // 화면 위 줌 버튼은 숨김
}

// 외부 뷰어로의 이동을 WebView 내부에서 처리 (브라우저로 튕기지 않게)
pdfWebView.webViewClient = WebViewClient()

WebViewClient()를 지정하지 않으면, 뷰어 서버가 다른 도메인으로 리다이렉트할 때 외부 브라우저 앱이 열려버린다. 사용자가 앱 밖으로 나가는 셈이라 반드시 설정한다.

3단계 — 코틀린 실전 코드

Activity 전체 흐름을 정리하면 다음과 같다.

class PdfViewerActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_pdf_viewer)

        val pdfWebView = findViewById<WebView>(R.id.pdfWebView)

        // 1) WebView 기본 설정
        pdfWebView.settings.apply {
            javaScriptEnabled = true
            loadWithOverviewMode = true
            useWideViewPort = true
            builtInZoomControls = true
            displayZoomControls = false
        }
        pdfWebView.webViewClient = WebViewClient()

        // 2) PDF 주소를 인코딩 후 뷰어 URL 구성
        val documentUrl = intent.getStringExtra("pdf_url")
            ?: "https://example.com/files/manual.pdf"
        val encodedUrl = java.net.URLEncoder.encode(documentUrl, "UTF-8")
        val viewerUrl =
            "https://docs.google.com/viewer?embedded=true&url=$encodedUrl"

        // 3) 로드
        pdfWebView.loadUrl(viewerUrl)
    }
}

그리고 매니페스트에 인터넷 권한을 반드시 추가한다. 이걸 빠뜨려서 "왜 빈 화면이지?" 하며 헤매는 경우가 정말 많다.

<!-- AndroidManifest.xml -->
<uses-permission android:name="android.permission.INTERNET" />

빈 화면이 뜨는 흔한 원인과 해결

실제로 막히는 지점은 코드보다 환경에 있는 경우가 많다. 자주 겪는 케이스를 정리했다.

  • 인터넷 권한 누락: INTERNET 권한이 없으면 외부 뷰어를 못 부른다. 매니페스트 1순위 확인.
  • JavaScript 비활성: javaScriptEnabled = true가 빠지면 뷰어가 그려지지 않는다.
  • URL 미인코딩: PDF 주소에 한글·공백·?·&가 들어 있으면 그냥 깨진다. URLEncoder.encode 필수.
  • 로컬/사설망 PDF: Google 뷰어는 외부에서 접근 가능한 공개 URL만 변환할 수 있다. localhost, 사내망, 로그인 필요 URL은 보이지 않는다.
  • HTTP(평문) 차단: 안드로이드 9(API 28)부터 평문 HTTP가 기본 차단이다. PDF가 http://라면 표시되지 않는다. 가능하면 https://로 제공하고, 불가피하면 network-security-config로 예외를 둔다.
  • 외부 서비스 의존: 뷰어 서버가 느리거나 일시적으로 막히면 그대로 영향을 받는다. 안정성이 중요하면 다음 절의 대안을 검토한다.

로컬 PDF / 오프라인 대안

외부 뷰어 방식은 "공개된 원격 PDF + 인터넷 연결"이라는 전제가 있다. 앱에 번들로 포함된 PDF나 오프라인 표시가 필요하면 다른 길을 택해야 한다.

가장 견고한 대안은 안드로이드 표준 PdfRenderer(API 21+)를 쓰는 것이다. 외부 서비스에 의존하지 않고, 인터넷 없이도 동작하며, 페이지 단위로 비트맵을 그려 ImageViewRecyclerView에 표시한다. 단점은 코드가 길고 페이지 캐싱·확대 처리를 직접 해야 한다는 점이다.

정리하면 선택 기준은 이렇다.

  • 빠르게, 원격 공개 PDF만 보여주면 된다 → WebView + 원격 뷰어
  • 오프라인·번들 PDF·민감 문서를 다룬다 → PdfRenderer (또는 검증된 뷰어 라이브러리)

마무리 체크리스트

WebView로 PDF를 띄울 때 빠르게 점검할 항목이다.

  • AndroidManifest.xmlINTERNET 권한 추가
  • javaScriptEnabled = true 설정
  • PDF 주소를 URLEncoder.encode로 인코딩
  • WebViewClient() 지정 (외부 브라우저로 튕김 방지)
  • PDF가 외부에서 접근 가능한 https:// 공개 URL인지 확인
  • 오프라인/민감 문서면 PdfRenderer로 전환 검토

한 줄짜리 해법으로 시작하되, "왜 안 보이지?" 단계에서 위 체크리스트를 따라가면 대부분 30분 안에 해결된다.

AI에게 물어볼 때 (프롬프트 팁)

이런 안드로이드 구현 문제는 ChatGPT나 Claude 같은 AI에게 물어볼 때 맥락(버전·언어·에러 메시지)을 구체적으로 주는 것이 정답률을 크게 높인다. Prompt Architect에서 강조하는 원칙대로, 모호한 질문 대신 조건을 박아 넣어 보자.

[역할] 너는 안드로이드 시니어 개발자다.
[상황] Kotlin, minSdk 26, targetSdk 34 환경에서 WebView로
        원격 PDF(https URL)를 띄우려는데 화면이 비어 있다.
[원하는 것] 빈 화면의 가능한 원인을 우선순위대로 나열하고,
        각 원인별 수정 코드 스니펫을 함께 제시해줘.
[제약] 외부 PDF 라이브러리는 추가하지 않는다.

특정 에러가 있을 때는 로그를 그대로 붙이는 프롬프트가 강력하다.

아래 Logcat 에러가 WebView로 PDF를 로드할 때 발생한다.
원인을 한 줄로 진단하고, 매니페스트/네트워크 보안 설정 관점에서
해결 방법을 단계별로 알려줘. (에러 로그 붙여넣기)

오프라인 요구처럼 설계 결정이 필요한 경우엔 "비교"를 요청한다.

번들로 포함된 로컬 PDF를 인터넷 없이 표시해야 한다.
WebView+원격 뷰어 방식과 PdfRenderer 방식을
구현 난이도, 오프라인 지원, 유지보수 관점에서 표로 비교하고
내 요구사항에 맞는 쪽을 추천해줘.

이처럼 역할·상황·제약·원하는 출력 형식을 명시하면, AI가 일반론 대신 바로 쓸 수 있는 답을 돌려준다. 좋은 코드는 좋은 질문에서 시작된다.