hyeon.s
개발로그
hyeon.s
전체 방문자
오늘
어제
  • 분류 전체보기 (150)
    • Web 및 인프라 (1)
      • Web (1)
      • Terraform (2)
      • Docker (1)
    • Android (1)
      • 공부 (28)
      • 트러블슈팅 (12)
      • 프로젝트 개발 (10)
      • Compose (2)
      • 우테코 프리코스 (0)
    • Server (5)
      • 공부 (1)
      • Spring (4)
    • 알고리즘 (68)
      • 문제풀이 (C++,Kotlin) (54)
      • 공부 (13)
    • 디자인 (3)
      • UI (3)
    • Language (5)
      • Kotlin (5)
      • JAVA (0)
    • IT 동아리 (8)
      • UMC 3기 (Android) (7)
      • Sopt 32기 (Android) (1)

Github

글쓰기 / 관리자
hELLO · Designed By 정상우.
hyeon.s

개발로그

[Android] PDF 뷰어 띄우기 / Activity 내에 PDF 뷰어 넣기 feat. 파일관리
Android/프로젝트 개발

[Android] PDF 뷰어 띄우기 / Activity 내에 PDF 뷰어 넣기 feat. 파일관리

2024. 9. 20. 11:19
728x90

기능 요구사항

  1. Activity내가 아닌 pdf 뷰어만 보여준다.
  2. Activity 내에 pdf 뷰어를 띄워 보여준다.

각각 요구사항을 만족시키는 방법에 대해 작성하였다.

[공통] Android Studio PDF File 준비하기

1. assets 폴더 생성

projcet > app > src > main 아래에 new directory 진행

assets 를 선택 한 후 폴더를 생성한다.


2. 폴더에 pdf 담기

생성한 assets 폴더 안에 뷰어에 띄우고자 하는 pdf 파일을 넣는다.

[들어가기 전] IputStream / OutputStream 이란

file의 copy를 다루면서 inputStream과 outputStream을 사용하게 되는데

해당 개념이 헷갈려서 정리해둔다.

image.png

이미지 출처 : https://lannstark.tistory.com/34

InputStream

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

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

OutputStream

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

  • 데이터 쓰기
  • 버퍼 비우기
  • Stream close 의 기능을 한다.

[기능 1] Intent로 PDF 뷰어 열기

    private fun getFileToAssets(fileName: String): File {
        val assetManager = requireContext().assets
        val file = File(requireContext().cacheDir, fileName)
        // val file = File(requireContext().filesDir, fileName)

        assetManager?.open(fileName).use { inputStream ->
            file.outputStream().use { outputStream ->
                inputStream?.copyTo(outputStream)
            }
        }
        return file
    }

    private fun initPdfViewer(fileName: String) {
        val pdfUri = FileProvider.getUriForFile(
            requireContext(),
            "${applicationId}.provider",
            getFileToAssets(fileName)
        )

        startActivity(Intent(Intent.ACTION_VIEW).apply {
            addCategory(Intent.CATEGORY_DEFAULT)
            addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
            setDataAndType(pdfUri, "application/pdf")
        })
    }

Intent 를 활용해 pdf 파일을 열기 위해서는 아래와 같은 과정이 진행된다.

  1. assets 파일 copy해서 file 생성
  2. FileProvider를 통해 위 file → Uri 생성
  3. Uri로 Intent ACTION_VIEW 활용하여 pdf 뷰어 띄우기
    1. 단 기기 내에 pdf 뷰어가 있어야지 뷰어가 제대로 띄워진다.

좀 더 자세히 코드들을 살펴보면

Assets File Copy 과정

    private fun getFileToAssets(fileName: String): File {
        val assetManager = requireContext().assets
        val file = File(requireContext().cacheDir, fileName)
        // val file = File(requireContext().filesDir, fileName)

        assetManager?.open(fileName).use { inputStream ->
            file.outputStream().use { outputStream ->
                inputStream?.copyTo(outputStream)
            }
        }
        return file
    }

copy가 이루어지는 과정은 다음과 같다.

  1. assetManager를 통해 file을 open 한다.
  2. 해당 file을 inputStream으로 읽어 fileName의 이름으로 outputStream으로 데이터를 쓴다.
  3. 해당 file은 val file = File(requireContext.cacheDir, fileName) 에 작성한 것 처럼 cache directories에 저장된다.
    1. cache나 files 중 내부 저장소 어디에 저장할 지는 각자 선택할 수 있다.
  4. outputStream 된 file을 return 한다.

Intent PDF_Viewer 띄우는 과정

 private fun initPdfViewer(fileName: String) {
        val pdfUri = FileProvider.getUriForFile(
            requireContext(),
            "${applicationId}.provider",
            getFileToAssets(fileName)
        )

        startActivity(Intent(Intent.ACTION_VIEW).apply {
            addCategory(Intent.CATEGORY_DEFAULT)
            addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
            setDataAndType(pdfUri, "application/pdf")
        })
    }
  1. assets에서 copy한 file을 FileProvider를 통해서 불러온다.
  2. getUriForFile을 통해 pdfUri를 생성한다.
  3. Intent(Intent.ACTION_VIEW)를 실행한다. setDataAndType에 생성한 pdfUri와 pdf type을 명시해준다.

위 과정을 따라하면 원하는 파일의 pdf 뷰어 창이 띄워지는 것을 볼 수 있다.

(단 기기에 pdf를 보여줄 수 있는 뷰어가 깔려있어야 한다.)

기능을 구현하면서 생긴 의문들은 다음과 같다.

Q. 왜 Assets에 있는 파일을 복사해서 파일을 생성해야 하는가?

Assets 디렉토리의 특성

assets 디렉토리에 포함된 파일은 단순한 리소스 패키지의 일부로 취급되며, Android 애플리케이션이 실행 중에 그 파일을 직접 접근하거나 일반적인 파일 시스템 경로로 다룰 수 없다.

Android는 assets 디렉토리 내의 파일들에 대해 일반 디렉토리처럼 경로를 제공하지 않으며, 이를 VM의 통제하에 패키징한다.

즉, assets 폴더에서 파일을 열어 파일의 내용을 InputStream으로만 읽을 수 있으며,

직접 파일 URI나 File 객체로 가져올 수 없다.

만약 FileProvider를 통해 asset 파일을 제공하려고 해도, 직접적으로 파일 URI를 생성할 수 없기 때문에 실패하게 된다.

⇒ 따라서 FileProvider로 URI 객체를 가져오기 위해서 Assets 파일을 Copy 한다.

Q. 디렉토리 선택 : cacheDir 과 filesDir의 차이는?

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

  • cacheDir:
    • 임시 파일을 저장하는 용도로 사용된다.
    • 파일이 더 이상 필요하지 않을 때, 앱이 이를 강제로 삭제할 수 있다.
    • 시스템이 디스크 공간이 부족할 때 자동으로 캐시를 비울 수 있기 때문에, 매우 임시적인 파일 사용에 적합하다.
  • filesDir:
    • 영구적으로 유지해야 하거나, 안정성이 필요한 파일은 filesDir에 저장해야 한다.
    • 이 경로에 저장된 파일은 사용자가 앱을 삭제하기 전까지 안전하게 유지된다.
    • 시스템이 임의로 이 파일들을 제거하지 않으므로, 중요한 파일을 다룰 때 적합하다.

따라서 파일을 매번 열 때 마다 필요하면 filesDir 을 사용하는 것이 더 적합할 수 있고,

파일이 더 이상 필요 없을 때만 앱이 명시적으로 삭제하면 된다.

만약 파일이 필요할 때 마다 임시적으로만 사용하고, 자동으로 시스템이 제거해도 괜찮다면

cacheDir 을 사용하는 것도 괜찮다.

Q. Kotlin Use 함수란 무엇인가?

해당코드에도 사용되며 리소스를 다룰 때 사용되는 kotlin 의 use 함수가 무엇인지 궁금해졌다.

Use 함수에 대해서 더 자세한 포스팅에 남기도록 하겠다.

[Kotlin] use 확장 함수 알아보기

[기능 2] Activity 내에 PDF 뷰어 띄우기

Android Activity 내에 pdf 뷰어를 띄우기 위해서 아래 라이브러리를 사용하였다.

https://github.com/afreakyelf/Pdf-Viewer

build.gradle (app) 의존성 추가

dependencies {
    // Replace 'latest-version' with the actual latest version number
    implementation("io.github.afreakyelf:Pdf-Viewer:latest-version")
}

Layout PDF Viewer 생성

pdf 뷰어를 넣고싶은 activity에 xml을 아래와 같이 작성한다.

<com.rajat.pdfviewer.PdfRendererView
    android:id="@+id/pdfView"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

PDF View 연결

initWithUrl 메서드를 통해 위에서 생성한 url을 넣어준다.

binding.pdfView.initWithUrl(
  url = "your_pdf_url_here"
)

그러면 activity 안에 pdf 뷰어가 띄워진 것을 볼 수 있다.

앱 내에 pdf 뷰어 기능을 구현하기 위해서 여러 방법들을 시도하였다.

[기능1]을 구현하면서 inputStream, outputStream, 안드로이드에서 데이터를 관리하는 디렉토리 등 여러 개념들을 알 수 있어서 좋았다.

[기능2]는 사실상 라이브러리만 사용했기 때문에 실제로 Activity 내에 어떻게 pdf 뷰어를 넣을 수 있는지 원리를 알기는 어려웠다.

따라서 다음 기회에 실제 activity 내에 pdf 뷰어를 넣는 것을 직접 구현해보고싶다.

728x90
저작자표시 (새창열림)

'Android > 프로젝트 개발' 카테고리의 다른 글

[Android] implemantation을 Version Catalog로 바꿔보자  (0) 2024.12.12
[Android] Github Actions로 업무 생산성 향상 시키기 feat. APK 자동 추출  (0) 2024.12.12
[Android/JAVA] Zxing 활용 바코드/QR 스캐너 구현하기  (0) 2024.08.07
[Android] Retrofit에서 XML 데이터 통신 및 파싱 방법 / RecyclerView XML 데이터 받아오기  (0) 2024.02.26
[Android] 인앱 업데이트 구현하기 / 인앱 업데이트 테스트  (1) 2023.11.01
'Android/프로젝트 개발' 카테고리의 다른 글
  • [Android] implemantation을 Version Catalog로 바꿔보자
  • [Android] Github Actions로 업무 생산성 향상 시키기 feat. APK 자동 추출
  • [Android/JAVA] Zxing 활용 바코드/QR 스캐너 구현하기
  • [Android] Retrofit에서 XML 데이터 통신 및 파싱 방법 / RecyclerView XML 데이터 받아오기
hyeon.s
hyeon.s
이유있는 코드를 짜자

티스토리툴바