본문 바로가기

개발 노트/Kotlin

[Android-kotlin] pdf 뷰어 띄우기

회원가입할때 보여줄 이용약관 동의서를 pdf로 보여주려고한다.

pdf를 보여주는 방법도 다양한데 나는 pdf뷰어를 통해서 보여주는 방식으로 개발해보려고한다.

 

 

1. 먼저 보여줄 pdf파일을 넣어주기

project 모드로 바꾸기

-> app -> src -> main -> assets 폴더에 pdf파일을 넣어준다.

 

 

 

2. Android 7.0이상에서는 파일 uri를 노출하려면 FileProvider를 통해서 노출시켜야한다

FileProvider - 앱 내 파일을 안전하게 다른 앱에 제공하기위한 매커니즘 / uri를 반환하는 역할

 

그럴려면 FileProvider로 uri객체를 가져와야하는데,

직접적으로 파일 uri객체로 가져올 수 없기때문에 assets파일을 복사해서 진행해야한다

 

res/xml/file_paths.xml 파일을 생성한다.

<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path name="files" path="/" />
</paths>

 

-> 이때 복사한 파일을 저장할 수 있는 디렉토리는 cacheDir 과 filesDir 두가지가 있다.

 

cacheDir

  • 임시 파일을 저장하는 용도로 사용
  • 파일이 더 이상 필요하지 않을 때, 앱이 이를 강제로 삭제할 수 있음
  • 시스템이 디스크 공간이 부족할 때 자동으로 캐시를 비울 수 있기 때문에, 매우 임시적인 파일 사용에 적합함

filesDir

  • 영구적으로 유지해야 하거나, 안정성이 필요한 파일은 filesDir에 저장해야함
  • 이 경로에 저장된 파일은 사용자가 앱을 삭제하기 전까지 안전하게 유지됨
  • 시스템이 임의로 이 파일들을 제거하지 않으므로, 중요한 파일을 다룰 때 적합함

나는 이용약관 관련 pdf파일을 로드하려하는것이라서 filesDir로 선택했다. 중요한 파일이기 때문에...

사용하려는 목적에 따라 선택하여 작성하면될것같다.

 

그래서 files-path name="files" 이렇게 files라고 써준다.

cacheDir를 사용하려한다면 files-path name="cache" 이렇게 수정해주면 된다.

 

 

3. AndroidManifest에 application안에 아래와 같이 작성해준다.

방금 만든 file_paths를 resoure로 지정해준다.

<application
    ... >
    <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>
</application>

 

 

4. 이제 Activity나 fragment에 들어가서 아래와 같이 수정해준다.

fileName에는 내가 아까 asset폴더에 넣은 파일이름을 작성해주면된다.

        // ivArrow 클릭 시 secret.pdf 열기
        binding.ivArrow.setOnClickListener {
            openPdf("secret.pdf")
        }
        
        // ivArrow2 클릭 시 map.pdf 열기
        binding.ivArrow2.setOnClickListener {
            openPdf("map.pdf")
        }
        
        private fun openPdf(fileName: String) {
        try {
            // assets 디렉토리에서 filesDir 디렉토리로 복사
            val inputStream = requireContext().assets.open(fileName)
            val outputFile = File(requireContext().filesDir, fileName)
            val outputStream = FileOutputStream(outputFile)

            inputStream.use { input ->
                outputStream.use { output ->
                    input.copyTo(output)
                }
            }

            // FileProvider를 통해 Uri 생성
            val uri = FileProvider.getUriForFile(
                requireContext(),
                "${requireContext().packageName}.fileprovider",
                outputFile
            )

            // Intent를 통해 PDF 열기
            val intent = Intent(Intent.ACTION_VIEW).apply {
                setDataAndType(uri, "application/pdf")
                addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
            }
            startActivity(intent)
        } catch (e: Exception) {
            (activity as MainActivity).showToast("PDF를 열 수 없습니다: ${e.message}")
        }
    }

 

InputStream

: 데이터를 byte 단위로 읽는 통로이며, 읽은 데이터를 byte로 돌려준다.

  • 데이터 읽기
  • 특정 시점으로 되돌아가기
  • 남은 데이터 확인
  • Stream close의 기능

OutputStream

: 데이터가 나가는 통로의 역할에 관해 규정, 데이터를 내보내는 기능을 한다.

  • 데이터 쓰기
  • 버퍼 비우기
  • Stream close 의 기능

 

이 방식은 따로 의존성 추가나 새로운 레이아웃이 필요하지 않는다는 장점이 있지만, Android의 기본 Intent를 사용하여 PDF 뷰어를 호출하기 때문에 사용자의 기기에 PDF 뷰어가 설치되어 있어야만 작동한다.

만약 기기에 PDF 뷰어가 없는 경우, 앱에서 기본 뷰어를 제공하고 싶다면 라이브러리를 추가해서 다른방식으로 진행해야한다

 

기기에 PDF 뷰어가 설치가 되어있다면 올바르게 작동할 것이다.