구현 영상
다음과 같이, URL 형식으로 서버에서 받아온 이미지 파일을 외부로 공유하는 로직을 구현했습니다.
앱 외부로 공유하는 방식이 아닌, 단순히 캐시 디렉토리를 활용하여 이미지를 처리하는 방법은 FileProvider 사용이 필요없으며 cacheDir를 활용하여 File을 저장하는 방식으로 구현하면 됩니다.
관련해서 작성된 글이 있으니 참고해주세요.
[Android] cacheDir 활용으로 카메라 이미지 저장 없이 업로드 기능 구현하기
구현 영상다음과 같은 과정으로 카메라 사진 촬영 및 캐시 저장 로직을 구현했습니다.카메라 권한 동의 획득캐시 디렉토리에 임시 파일 생성 및 URI 저장해당 URI를 카메라 런처에 전달한 후 실행
marchbreeze.tistory.com
1. FileProvider란
ContentProvider
앱 간에 데이터를 공유할 수 있도록 해주는 Android의 컴포넌트이며, 데이터를 추상화하여 URI를 통해서 접근할 수 있도록 합니다.
URI
1. Content URI (content:// ): ContentProvider를 통해 접근 가능한 리소스입니다.
2. File URI (file://) : 파일 시스템 상의 실제 경로입니다.
StrictMode API 보안정책
Android Nougat(7.0) 이후 보안상의 이유로 앱 간에 File URI를 공유하는 것이 제한되며, 파일의 시스템상 경로(File URI) 노출 시 FileUriExposedException이 발생합니다.
따라서 URI를 공유하는 경우, Content URI를 활용하여 앱 내 데이터를 내보내고 임시 엑세스 권한을 부여하여 파일을 공유해야 합니다.
FileProvider
ContentProvider의 한 종류로, 앱 내부의 파일을 다른 앱과 안전하게 공유할 때 사용됩니다.
FileProvider의 getUriForFile() 함수를 활용한다면, 실제 파일 경로를 노출하지 않고 Content URI를 생성하여 활용할 수 있습니다.
2. FileProvider 사용 준비
(1) Manifest 등록
FileProvider는 앱의 진입점이 될 수 있는 4대 컴포넌트로, manifest에 등록이 필요합니다.
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="kr.genti.android.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
- authorities는 앱 패키지와 일치시켜야 충돌이 없습니다.
- grantUriPermissions=true 로 외부 앱(카메라)에게 URI 접근 권한을 임시 부여합니다.
- 파일 공유가 가능한 경로를 정의한 XML 파일을 meta-data로 등록해야 합니다.
(2) file_paths 정의
앱에서 공유가 가능하도록 설정할 루트를 file_paths 파일을 통해 정의해야 합니다.
<paths>
<cache-path name="images" path="." />
</paths>
- 디렉토리에 하위 폴더로 관리하고자 한다면 path에 폴더명을 입력하면 됩니다.
- XML 태그들로 앱의 파일 경로를 지정해줍니다.
XML 태그 | 설명 | 활용 코드 |
files-path | 앱 내부 저장소 디렉토리 | Context.getFilesDir() |
cache-path | 앱 내부 캐시 디렉토리 | getCacheDir() |
external-path | 기기의 외부 저장소 전체의 루트 경로 (권장 X) | Environment.getExternalStorageDirectory() |
external-files-path | 앱 전용 외부 저장소 디렉토리 | Context.getExternalFilesDir() |
external-cache-path | 앱 전용 외부 캐시 저장소 디렉토리 | Context.getExternalCacheDir() |
3. 이미지 URL로 캐시 파일 Content URI 생성
주어진 URL에서 이미지를 다운로드 받아 앱의 캐시 디렉토리에 임시 파일로 저장한 후, FileProvider를 사용하여 해당 파일의 URI를 반환하는 함수를 설정합니다.
suspend fun getCacheImageUri(id: Long, imageUrl: String) =
runCatching {
withContext(Dispatchers.IO) {
val bitmap = downloadBitmapFromUrl(imageUrl) ?: throw Exception()
val tempFile = createNamedCacheFile(id)
FileOutputStream(tempFile).saveBitmapToFile(bitmap)
FileProvider.getUriForFile(appContext, "kr.genti.android.fileprovider", tempFile)
}
}
1. 이미지 비트맵 생성
private suspend fun downloadBitmapFromUrl(imageUrl: String): Bitmap? =
// 1) Coil ImageLoader 초기화
val coilImageLoader = ImageLoader.Builder(context).build()
// 2) 요청 생성 및 실행
val request = ImageRequest.Builder(appContext)
.data(imageUrl)
.allowHardware(false) // 비트맵 처리 안전성
.build()
coilImageLoader.execute(
ImageRequest.Builder(appContext).data(imageUrl).build()
).image?.toBitmap()
// 3) 결과에서 비트맵 추출
loader.execute(request).image?.toBitmap()
}
- coil3 라이브러리의 imageLoader.execute()를 활용하여, 지정된 URL에서 이미지를 다운로드하고 디코딩하여 비트맵 형태로 결과를 가져옵니다.
2. 임시 파일 cacheDir에 생성
private val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss")
private fun createNamedCacheFile(id: Long): File =
val timestamp = dateTimeFormatter.format(LocalDateTime.now())
File(appContext.cacheDir, "img_genti_${id}_${timestamp}.jpeg").apply {
if (!exists()) createNewFile()
}
- 앱의 내부 캐시 폴더 경로에 시간을 추가한 고유한 이름의 File을 생성합니다.
3. 비트맵을 변환한 후, 임시 파일에 저장
private fun OutputStream?.saveBitmapToFile(bitmap: Bitmap) {
this?.use { outputStream ->
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
} ?: throw Exception()
}
- FileOutputStream을 사용하여 비트맵 이미지를 File에 저장합니다.
- use를 활용해서 스트림을 자동으로 클로즈하도록 보장하며, 비트맵 객체를 JPEG 형태로 압축하여 바이트로 기록합니다.
4. FileProvider.getUriForFile를 통해서 안전한 Content URI를 생성한 후 반환합니다.
4. 이미지 공유 Intent 실행
Intent.ACTION_SEND를 활용하여 앱의 URI를 공유하는 기능을 실행합니다.
context.startActivity(
Intent().apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_STREAM, imageUri)
type = "image/*"
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
createChooser(this, "SHARE_IMAGE_CHOOSER")
}
)
- Intent.ACTION_SEND: 안드로이드 시스템에 "데이터를 보내겠다"는 의도를 알립니다.
- putExtra(Intent.EXTRA_STREAM, imageUri): 공유할 컨텐츠의 URI를 EXTRA_STREAM 키에 담아 받는 앱이 스트림 형태의 데이터를 얻을 수 있습니다.
- type = "image/*": 인텐트에 포함된 데이터의 MIME 타입을 지정하여 시스템이 이미지 공유가 가능한 앱 목록을 필터링합니다.
- addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION): FileProvider로 생성한 Content URI는 기본적으로 안전하지만, 받는 앱이 URI를 읽을 수 있도록 임시 권한을 부여합니다.
- Intent.createChooser(): 직접 startActivity(intent) 대신 createChooser()로 감싸면, 사용자에게 공유 가능한 앱 목록을 보여주는 선택 다이얼로그가 표시되고, 사용자가 원하는 앱을 선택할 때까지 앱이 죽지 않습니다.
다음과 같은 코드를 이용하면, 앱 내에서 이미지 URL을 통해 만든 임시 파일의 Content URI를 외부 앱과 안전하게 공유할 수 있습니다.
참고 자료 :
FileProvider | API reference | Android Developers
[Android] Content URI와 File URI의 차이점, File Provider와 Content URI를 활용하여 카메라 이미지 가져오기
'Feature' 카테고리의 다른 글
[Android] In-App Update 기능을 포함한 스플래시 뷰 로직 구현하기 (0) | 2025.05.26 |
---|---|
[Android] Scoped Storage에서 MediaStore를 활용한 이미지 다운로드 기능 구현하기 (0) | 2025.05.26 |
[Android] cacheDir 활용으로 카메라 이미지 저장 없이 업로드 기능 구현하기 (0) | 2025.05.26 |
[Android] ConnectivityManager를 활용한 네트워크 상태 모니터링 기능 구현하기 (1) | 2025.05.24 |
[Android] Photo Picker를 활용한 갤러리 이미지 다중 선택 기능 구현하기 (0) | 2025.05.24 |