본문 바로가기

[Android] FileProvider 활용으로 이미지 저장 없이 사진 공유 기능 구현하기

@Marchbreeze2025. 5. 26. 05:59

구현 영상

다음과 같이, 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")
    }
)
  1. Intent.ACTION_SEND: 안드로이드 시스템에 "데이터를 보내겠다"는 의도를 알립니다.
  2. putExtra(Intent.EXTRA_STREAM, imageUri): 공유할 컨텐츠의 URI를 EXTRA_STREAM 키에 담아 받는 앱이 스트림 형태의 데이터를 얻을 수 있습니다.
  3. type = "image/*": 인텐트에 포함된 데이터의 MIME 타입을 지정하여 시스템이 이미지 공유가 가능한 앱 목록을 필터링합니다.
  4. addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION): FileProvider로 생성한 Content URI는 기본적으로 안전하지만, 받는 앱이 URI를 읽을 수 있도록 임시 권한을 부여합니다.
  5. Intent.createChooser(): 직접 startActivity(intent) 대신 createChooser()로 감싸면, 사용자에게 공유 가능한 앱 목록을 보여주는 선택 다이얼로그가 표시되고, 사용자가 원하는 앱을 선택할 때까지 앱이 죽지 않습니다.

 

 

다음과 같은 코드를 이용하면, 앱 내에서 이미지 URL을 통해 만든 임시 파일의 Content URI를 외부 앱과 안전하게 공유할 수 있습니다.


참고 자료 :

FileProvider  |  API reference  |  Android Developers

[Android] Content URI와 File URI의 차이점, File Provider와 Content URI를 활용하여 카메라 이미지 가져오기

Marchbreeze
Marchbreeze

안드로이드 개발자, 김상호입니다.

https://github.com/Marchbreeze

목차