기능 요구사항
- Activity내가 아닌 pdf 뷰어만 보여준다.
- 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을 사용하게 되는데
해당 개념이 헷갈려서 정리해둔다.
이미지 출처 : 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 파일을 열기 위해서는 아래와 같은 과정이 진행된다.
- assets 파일 copy해서 file 생성
- FileProvider를 통해 위 file → Uri 생성
- Uri로 Intent ACTION_VIEW 활용하여 pdf 뷰어 띄우기
- 단 기기 내에 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가 이루어지는 과정은 다음과 같다.
- assetManager를 통해 file을 open 한다.
- 해당 file을 inputStream으로 읽어 fileName의 이름으로 outputStream으로 데이터를 쓴다.
- 해당 file은
val file = File(requireContext.cacheDir, fileName)
에 작성한 것 처럼 cache directories에 저장된다.- cache나 files 중 내부 저장소 어디에 저장할 지는 각자 선택할 수 있다.
- 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")
})
}
- assets에서 copy한 file을 FileProvider를 통해서 불러온다.
- getUriForFile을 통해 pdfUri를 생성한다.
- 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 함수에 대해서 더 자세한 포스팅에 남기도록 하겠다.
[기능 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 뷰어를 넣는 것을 직접 구현해보고싶다.
'Android > 프로젝트 개발' 카테고리의 다른 글
[Android] Github Actions로 업무 생산성 향상 시키기 feat. APK 자동 추출 (0) | 2024.12.12 |
---|---|
[Android] Menu 데이터는 어디에 저장하는 게 성능이 좋을까? (RoomDB vs Object) (0) | 2024.12.05 |
[Android/JAVA] Zxing 활용 바코드/QR 스캐너 구현하기 (0) | 2024.08.07 |
[Android] Retrofit에서 XML 데이터 통신 및 파싱 방법 / RecyclerView XML 데이터 받아오기 (0) | 2024.02.26 |
[Android] 인앱 업데이트 구현하기 / 인앱 업데이트 테스트 (1) | 2023.11.01 |