외부 저장소에 있는 파일 작업하기

MediaStore API는 다음과 같은 잘 정의된 유형의 미디어 파일에 액세스할 수 있는 인터페이스를 제공합니다.

MediaStore는 또한 MediaStore.Files라는 컬렉션도 포함하며, 이 컬렉션을 통해 모든 유형의 미디어 파일에 액세스할 수 있습니다.

이 가이드에서는 일반적으로 외부 저장소 기기에 저장된 미디어 파일을 액세스하고 공유하는 방법을 설명합니다.

파일 액세스하기

미디어 파일을 로드하려면 ContentResolver에서 다음 메서드 중 하나를 호출하세요.

  • 단일 미디어 파일의 경우 openFileDescriptor()를 사용합니다.
  • 단일 미디어 파일 미리보기 이미지의 경우 loadThumbnail()을 사용하여 로드하려는 미리보기 이미지 크기를 전달합니다.
  • 미디어 파일 컬렉션의 경우 query()를 사용합니다.

다음 코드 스니펫은 미디어 파일에 액세스하는 방법을 보여줍니다.

    val resolver = context.getContentResolver()

    // Open a specific media item.
    resolver.openFileDescriptor(item, mode).use { pfd ->
        // ...
    }

    // Load thumbnail of a specific media item.
    val mediaThumbnail = resolver.loadThumbnail(item, Size(640, 480), null)

    // Find all videos on a given storage device, including pending files.
    val collection = MediaStore.Video.Media.getContentUri(volumeName)
    val collectionWithPending = MediaStore.setIncludePending(collection)
    resolver.query(collectionWithPending, null, null, null).use { c ->
        // ...
    }

    // Publish a video onto an external storage device.
    val values = ContentValues().apply {
        put(MediaStore.Audio.Media.RELATIVE_PATH, "Video/My Videos")
        put(MediaStore.Audio.Media.DISPLAY_NAME, "My Video.mp4")
    }
    val item = resolver.insert(collection, values)
    

네이티브 코드에서 액세스

다른 앱이 내 앱과 공유한 파일이나 사용자 미디어 컬렉션의 미디어 파일과 같이 네이티브 코드로 된 특정 미디어 파일을 앱에서 사용해야 하는 상황이 발생할 수 있습니다. 이러한 경우 자바 기반 코드 또는 Koltin 기반 코드로 미디어 파일 검색을 시작한 후 해당 파일 관련 파일 설명자를 네이티브 코드로 전달합니다.

다음 코드 스니펫은 미디어 객체의 파일 설명자를 앱의 네이티브 코드로 전달하는 방법을 보여줍니다.

Kotlin

    val contentUri: Uri = ContentUris.withAppendedId(
            MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
            cursor.getLong(BaseColumns._ID))
    val fileOpenMode = "r"
    val parcelFd = resolver.openFileDescriptor(uri, fileOpenMode)
    val fd = parcelFd?.detachFd()
    // Pass the integer value "fd" into your native code. Remember to call
    // close(2) on the file descriptor when you're done using it.
    

자바

    Uri contentUri = ContentUris.withAppendedId(
            MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
            cursor.getLong(Integer.parseInt(BaseColumns._ID)));
    String fileOpenMode = "r";
    ParcelFileDescriptor parcelFd = resolver.openFileDescriptor(uri, fileOpenMode);
    if (parcelFd != null) {
        int fd = parcelFd.detachFd();
        // Pass the integer value "fd" into your native code. Remember to call
        // close(2) on the file descriptor when you're done using it.
    }
    

네이티브 코드로 된 파일에 액세스하는 방법을 자세히 알아보려면 Android Dev Summit '18에서 15:20부터 시작하는 Files for Miles 대화를 참조하세요.

콘텐츠 쿼리의 열 이름

앱 코드에서 mime_type AS MimeType과 같은 열 이름 프로젝션을 사용하는 경우 Android 10(API 레벨 29) 이상을 실행하는 기기에는 MediaStore API에 정의된 열 이름이 있어야 한다는 점에 유의하세요.

앱 내의 종속 라이브러리에 Android API에 정의되지 않은 열 이름(예: MimeType)을 사용해야 하는 경우 CursorWrapper를 사용하여 앱 프로세스의 열 이름을 동적으로 변환하세요.

저장할 미디어 파일의 대기중 상태 제공하기

Android 10(API 레벨 29) 이상을 실행하는 기기에서는 미디어 파일을 디스크에 쓸 때 앱은 IS_PENDING 플래그를 사용하여 미디어 파일에 독점적으로 액세스할 수 있습니다.

다음 코드 스니펫은 IS_PENDING 플래그를 사용하여 디렉터리(MediaStore.Images 컬렉션에 상응함)에 사진을 저장하는 방법을 보여줍니다.

Kotlin

    val values = ContentValues().apply {
        put(MediaStore.Images.Media.DISPLAY_NAME, "IMG1024.JPG")
        put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
        put(MediaStore.Images.Media.IS_PENDING, 1)
    }

    val resolver = context.getContentResolver()
    val collection = MediaStore.Images.Media
            .getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
    val item = resolver.insert(collection, values)

    resolver.openFileDescriptor(item, "w", null).use { pfd ->
        // Write data into the pending image.
    }

    // Now that we're finished, release the "pending" status, and allow other apps
    // to view the image.
    values.clear()
    values.put(MediaStore.Images.Media.IS_PENDING, 0)
    resolver.update(item, values, null, null)
    

자바

    ContentValues values = new ContentValues();
    values.put(MediaStore.Images.Media.DISPLAY_NAME, "IMG1024.JPG");
    values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
    values.put(MediaStore.Images.Media.IS_PENDING, 1);

    ContentResolver resolver = context.getContentResolver();
    Uri collection = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
    Uri item = resolver.insert(collection, values);

    try (ParcelFileDescriptor pfd = resolver.openFileDescriptor(item, "w", null)) {
        // Write data into the pending image.
    } catch (IOException e) {
        e.printStackTrace();
    }

    // Now that we're finished, release the "pending" status, and allow other apps
    // to view the image.
    values.clear();
    values.put(MediaStore.Images.Media.IS_PENDING, 0);
    resolver.update(item, values, null, null);
    

다른 앱의 미디어 파일 업데이트하기

앱이 범위 지정 저장소를 사용하는 경우 일반적으로 다른 앱이 미디어 스토어에 제공한 미디어 파일을 업데이트할 수 없습니다. 하지만 플랫폼에서 발생한 RecoverableSecurityException을 발견하여 파일 수정을 위한 사용자 동의를 받는 것은 가능합니다. 그런 다음, 아래의 코드 스니펫에 표시된 것처럼 해당하는 항목에 대한 쓰기 권한을 앱에 부여하도록 사용자에게 요청할 수 있습니다.

Kotlin

    try {
        // ...
    } catch (rse: RecoverableSecurityException) {
        val requestAccessIntentSender = rse.userAction.actionIntent.intentSender

        // In your code, handle IntentSender.SendIntentException.
        startIntentSenderForResult(requestAccessIntentSender, your-request-code,
                null, 0, 0, 0, null)
    }
    

자바

    try {
        // ...
    } catch (RecoverableSecurityException rse) {
        IntentSender requestAccessIntentSender = rse.getUserAction()
                .getActionIntent().getIntentSender();

        // In your code, handle IntentSender.SendIntentException.
        startIntentSenderForResult(requestAccessIntentSender, your-request-code,
                null, 0, 0, 0, null);
    }
    

파일의 위치와 관련된 힌트 제공하기

Android 10(API 레벨 29)을 실행하는 기기에서 앱이 미디어를 제공하면 미디어는 기본적으로 유형에 따라 정리됩니다. 예를 들어 새 이미지 파일은 기본적으로 Environment.DIRECTORY_PICTURES 디렉터리(MediaStore.Images 컬렉션에 상응함)에 배치됩니다.

앱이 파일을 저장해야 하는 구체적인 위치(예: Pictures/MyVacationPictures)를 알고 있는 경우 MediaColumns.RELATIVE_PATH를 설정하여 새로 작성된 파일을 저장할 위치와 관련된 힌트를 시스템에 제공할 수 있습니다. 마찬가지로, update() 호출 중에 디스크의 파일을 이동할 수 있습니다. MediaColumns.RELATIVE_PATH 또는 MediaColumns.DISPLAY_NAME을 변경하면 됩니다.

일반적인 사용 사례

이 섹션에서는 미디어 파일과 관련된 몇 가지 일반적인 사용 사례를 처리하는 방법을 설명합니다.

미디어 파일 공유하기

일부 앱에서는 사용자가 다른 사용자와 미디어 파일을 공유하도록 허용합니다. 예를 들어 소셜 미디어 앱을 사용하면 사용자가 친구와 사진 및 동영상을 공유할 수 있습니다.

사용자가 공유하려는 미디어 파일에 액세스하려면 파일에 액세스하는 방법 및 고유한 이름을 사용하여 볼륨에 액세스하는 방법 섹션에서 설명하는 프로세스를 사용하세요.

메시지 앱 및 프로필 앱과 같은 호환 앱을 제공하는 경우 content:// URI를 사용하여 파일 공유를 설정하세요. 이 워크플로는 보안 권장사항으로 권장됩니다.

문서 작업하기

일부 앱에서는 사용자가 동료와 공유하거나 다른 문서로 가져올 수 있는 데이터를 입력하는 저장소 단위로 문서를 사용합니다. 예를 들어 사용자는 비즈니스 생산성 문서를 열거나 EPUB 파일로 저장된 책을 열 수 있습니다.

이 경우 사용자가 시스템의 파일 선택기 앱을 여는 ACTION_OPEN_DOCUMENT 인텐트를 호출하여 열 파일을 선택할 수 있게 허용하세요. 앱에서 지원하는 파일 형식만 표시하려면 인텐트에 Intent.EXTRA_MIME_TYPES도 포함합니다.

GitHub의 ActionOpenDocument 샘플은 사용자의 동의를 얻은 후 ACTION_OPEN_DOCUMENT를 사용하여 파일을 여는 방법을 보여줍니다.

파일 그룹 관리하기

파일 관리 앱과 미디어 제작 앱은 일반적으로 디렉터리 계층 구조로 파일 그룹을 관리합니다. 이러한 앱은 ACTION_OPEN_DOCUMENT_TREE 인텐트를 호출하여 사용자가 전체 디렉터리 트리 액세스 권한을 부여할 수 있도록 합니다. 이러한 앱은 선택된 디렉터리 및 그 하위 디렉터리에 있는 모든 파일을 편집할 수 있습니다.

사용자는 이 인터페이스를 사용하여 로컬 지원 솔루션 또는 클라우드 기반 솔루션이 지원할 수 있는 DocumentsProvider의 인스턴스에서 파일에 액세스할 수 있습니다.

GitHub의 ActionOpenDocumentTree 샘플은 사용자의 동의를 얻은 후 ACTION_OPEN_DOCUMENT_TREE를 사용하여 파일을 여는 방법을 보여줍니다.