[Android] Access

더 나은 사용자 경험을 제공하기 위해 많은 앱은 사용자가 미디어 파일을 만들고 액세스할 수 있는 기능을 제공합니다.

이러한 파일은 외부 저장 매체에 저장 및 사용됩니다.

Android 프레임워크를 사용하면 Media Store라는 미디어 파일에 최적화된 인덱스를 제공하여 미디어 파일에 쉽게 액세스하고 업데이트할 수 있습니다.

그리고 앱을 삭제한 후에도 해당 파일은 사용자의 장치에 남아 있습니다.

앱에서 해당 앱 내에서만 관리되는 미디어 파일을 사용하려는 경우 외부 메모리의 앱별 디렉터리(https://developer.android.com/training/data-storage/app-specific#media)에서 미디어 파일 관리 권장 ~도

사진 선택

미디어 저장소를 사용하는 다른 방법은 Androd Photo Picker 도구를 사용하는 것입니다.

Androd Photo Picker는 사용자가 사진 파일을 선택할 수 있는 더 안전한 내장 방법을 제공합니다.

Androd Photo Picker를 사용하면 앱이 전체 미디어 라이브러리에 액세스하기 위해 권한을 찾을 필요가 없습니다.

Androd Photo Picker는 지원되는 장치에서만 사용할 수 있습니다.

자세한 내용은 사진 선택 가이드(https://developer.android.com/training/data-storage/shared/photopicker) 보다

미디어 스토어

추상화된 미디어 스토어 레이어와 상호 작용하기 위해 ContentResolver(https://developer.android.com/reference/android/content/ContentResolver) 물체. ContentResolver 개체는 앱의 컨텍스트에서 액세스할 수 있습니다.

val projection = arrayOf(media-database-columns-to-retrieve)
val selection = sql-where-clause-with-placeholder-variables
val selectionArgs = values-of-placeholder-variables
val sortOrder = sql-order-by-clause

applicationContext.contentResolver.query(
    MediaStore.media-type.Media.EXTERNAL_CONTENT_URI,
    projection,
    selection,
    selectionArgs,
    sortOrder
)?.use { cursor ->
    while (cursor.moveToNext()) {
        // Use an ID column from the projection to get
        // a URI representing the media item itself.
    }
}

시스템은 자동으로 외부 스토리지 볼륨을 스캔하고 아래에 정의된 컬렉션에 미디어 파일을 추가합니다.

  • 영화 : DCIM/ 폴더 부서 영화/ 폴더에 저장된 사진과 스크린샷을 말합니다.

    시스템은 이러한 파일을 저장합니다.

    MediaStore 이미지 테이블에 추가합니다.

  • 동영상 : DCIM/ 폴더 부서 영화/ 접합재, /영화 산업 폴더에 저장된 케이크를 의미합니다.

    시스템이 자동으로 MediaStore 비디오 테이블에 추가합니다.

  • 오디오 파일 : 알람 시계/, 오디오북/, 음악/, 알림/, /팟캐스트/, 벨소리/ 디렉토리에 저장된 파일을 말합니다.

    또한 시스템 음악/동영상/ 디렉토리에서 오디오 재생 목록을 감지하고, 녹음/ 디렉토리에 있는 음성 녹음도 인식합니다.

    (Records/Directory는 Android 11(API 레벨 30)에서 사용할 수 없습니다.

    ) 미디어 스토어 오디오 테이블에 추가합니다.

  • 다운로드한 파일 : 다운로드/ 하나의 디렉토리에 있는 파일을 의미합니다.

    Android 10(API 레벨 29) 이상부터 이러한 파일은 미디어 스토어 다운로드 테이블에 추가합니다.

    이 표는 Android 9 이하를 실행하는 기기에서는 사용할 수 없습니다.

미디어 스토어도 MediaStore.Files 또한 이 테이블에 저장된 파일이라는 콜렉션이 포함되어 있습니다.

범위 지정 저장소 사용하느냐에 따라 다릅니다.

(범위 지정 저장소는 Android 10부터 적용됩니다.

)

  • 앱에서 범위 지정 저장소를 사용하는 경우 이 컬렉션에는 앱에서 추가한 사진, 동영상 및 오디오 파일만 포함됩니다.

    대부분의 경우 다른 앱에서 만든 미디어 파일에 액세스하려면 MediaStore.Files 당신은 사용할 필요가 없습니다.

    하지만 사용해야 하는 사용 사례가 있는 경우 READ_EXTERNAL_STORAGE 권한을 선언해야 하며 해당 권한은 사용자가 부여해야 합니다.

    그러나 다른 앱에서 만든 미디어 파일에 액세스하려면 MediaStore API를 사용하는 것이 좋습니다.

  • 범위 지정 저장소를 사용하지 않거나 사용할 수 없는 경우 이 컬렉션에는 모든 유형의 미디어 파일이 포함됩니다.

필요한 권한 요청

미디어 파일에 대한 작업을 수행하기 전에 앱이 해당 파일에 액세스할 수 있는 적절한 권한을 선언하고 부여했는지 확인해야 합니다.

사용하지 않는 범위를 넘어서는 권한을 선언하지 마십시오.

저장소 권한

다른 앱에서 만든 미디어 파일에 접근하지 않는 경우 권한 설정이 필요하지 않습니다.

Android 10 이상 기기에서는 MediaStore.Downloads 컬렉션을 포함하여 앱에서 만든 미디어 파일에 액세스하기 위해 저장소 관련 권한을 설정할 필요가 없습니다.

카메라 앱을 개발하는 경우 이 앱은 미디어 스토어에서 생성한 사진 파일만 관리하므로 저장 권한이 필요하지 않습니다.

다른 앱에서 미디어 파일에 액세스

다른 앱에서 생성한 미디어 파일에 접근하기 위해서는 다음과 같은 저장소 관련 권한을 선언해야 합니다.

액세스하려는 파일은 다음 미디어 파일 컬렉션에 포함되어야 합니다.

  • MediaStore 이미지
  • MediaStore 비디오
  • 미디어 스토어 오디오

파일이 MediaStore.Images, MediaStore.Video 또는 MediaStore.Audio 쿼리를 통해 표시되는 경우 MediaStore.Files 쿼리를 통해서도 표시됩니다.

다음 코드는 올바른 저장소 권한을 설정하는 방법을 설명합니다.

<!
-- Required only if your app needs to access images or photos that other apps created. --> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> <!
-- Required only if your app needs to access videos that other apps created. --> <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" /> <!
-- Required only if your app needs to access audio files that other apps created. --> <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" /> <!
-- 위쪽은 Target SDK 33 이상인 경우에만 적용됨 --> <!
-- 아래쪽은 Target SDK 32 이하에서 다른 앱의 미디어 파일을 접근할때 적용 --> <!
-- If your app doesn't need to access media files that other apps created, set the "maxSdkVersion" attribute to "28" instead. --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="29" />

READ_MEDIA_IMAGES 및 READ_MEDIA_VIDEO 권한을 동시에 선언하고 이러한 권한을 동시에 요청하면 Android 시스템은 권한 요청 대화 상자에서 두 권한을 동시에 요청합니다.

이전 장치에서 실행되는 앱에 필요한 추가 권한

Android 9에서 작동하거나 Scoped Storage 기능이 일시적으로 비활성화된 경우 모든 미디어 파일에 액세스하려면 READ_EXTERNAL_STORAGE 권한이 필요합니다.

미디어 파일을 편집하려면 WRITE_EXTERNAL_STORAGE가 필요합니다.

다른 앱에서 다운로드한 파일에 액세스하려면 저장소 액세스 프레임워크를 사용해야 합니다.

앱이 다른 앱에서 만든 MediaStore.Downloads 컬렉션에 액세스하려는 경우 저장소 액세스 프레임워크를 사용해야 합니다.

https://developer.android.com/training/data-storage/shared/documents-files

공유 저장소의 문서 및 기타 파일 액세스 | 안드로이드 개발자 | 안드로이드 개발자

공유 저장소에서 문서 및 기타 파일에 액세스합니다.

컬렉션을 사용하여 구성합니다.

기본 설정에 따라 콘텐츠를 저장하고 분류합니다.

앱은 Android 4.4(API 레벨 19) 이상을 실행하는 기기의 저장소에 액세스할 수 있습니다.

developer.android.com

미디어 위치 권한

앱이 Android 10(API 레벨 29) 이상을 대상으로 하고 사진에서 수정되지 않은 EXIF ​​메타데이터를 검색하려는 경우 ACCESS_MEDIA_LOCATION 권한을 선언하고 런타임 시 사용자에게 명시적으로 권한을 부여해야 합니다.

Media Store 업데이트 확인

미디어 파일에 보다 안정적으로 액세스하려면(특히 앱이 미디어 스토어의 URI 또는 ​​데이터를 캐시하는 경우) 마지막 미디어 스토어 동기화 이후 미디어 스토어 버전이 변경되었는지 확인해야 합니다.

이렇게 하려면 getVersion() 메서드를 호출하면 됩니다.

반환되는 값은 고유한 문자열이며 이 반환 값은 미디어 저장소에서 많은 변경이 발생하는 경우에도 변경됩니다.

마지막 동기화 이후 이 값이 변경된 경우 앱의 미디어 캐시를 다시 동기화해야 합니다.

앱을 시작할 때 이 확인을 수행하는 것이 좋습니다.

미디어 파일을 쿼리할 때마다 이 확인을 실행할 필요가 없습니다.

파일을 미디어 저장소에 저장하는 것만으로는 이 릴리스가 변경되지 않습니다.

미디어 컬렉션 쿼리

특정 조건을 충족하는 미디어 파일 찾기(예: 나. 5분 이상의 미디어 파일은 아래와 같이 SQL 형식의 select 문을 작성하여 사용할 수 있다.

// Need the READ_EXTERNAL_STORAGE permission if accessing video files that your
// app didn't create.

// Container for information about each video.
data class Video(val uri: Uri,
    val name: String,
    val duration: Int,
    val size: Int
)
val videoList = mutableListOf<Video>()

val collection =
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        MediaStore.Video.Media.getContentUri(
            MediaStore.VOLUME_EXTERNAL
        )
    } else {
        MediaStore.Video.Media.EXTERNAL_CONTENT_URI
    }

val projection = arrayOf(
    MediaStore.Video.Media._ID,
    MediaStore.Video.Media.DISPLAY_NAME,
    MediaStore.Video.Media.DURATION,
    MediaStore.Video.Media.SIZE
)

// Show only videos that are at least 5 minutes in duration.
val selection = "${MediaStore.Video.Media.DURATION} >= ?"
val selectionArgs = arrayOf(
    TimeUnit.MILLISECONDS.convert(5, TimeUnit.MINUTES).toString()
)

// Display videos in alphabetical order based on their display name.
val sortOrder = "${MediaStore.Video.Media.DISPLAY_NAME} ASC"

val query = ContentResolver.query(
    collection,
    projection,
    selection,
    selectionArgs,
    sortOrder
)
query?.use { cursor ->
    // Cache column indices.
    val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID)
    val nameColumn =
            cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DISPLAY_NAME)
    val durationColumn =
            cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATION)
    val sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.SIZE)

    while (cursor.moveToNext()) {
        // Get values of columns for a given video.
        val id = cursor.getLong(idColumn)
        val name = cursor.getString(nameColumn)
        val duration = cursor.getInt(durationColumn)
        val size = cursor.getInt(sizeColumn)

        val contentUri: Uri = ContentUris.withAppendedId(
            MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
            id
        )

        // Stores column values and the contentUri in a local object
        // that represents the media file.
        videoList += Video(contentUri, name, duration, size)
    }
}

위와 같은 쿼리를 실행할 때 다음 사항에 유의하십시오.

  • query() 메서드는 작업자 스레드에서 실행되어야 합니다.

  • 열 인덱스는 캐시되므로 쿼리 결과의 행을 처리할 때마다 getColumnIndexOrThrow() 메서드를 호출할 필요가 없습니다.

  • 위의 코드와 같이 콘텐츠 URL 끝에 ID를 추가합니다.

  • Android 10 이상을 실행하는 기기의 경우 MediaStore API에 정의된 열 이름을 사용해야 합니다.

    앱이 의존하는 라이브러리가 이 API에 정의되지 않은 열 이름(예: MimeType과 같은 이름)을 사용해야 하는 경우 앱 프로세스 내에서 CursorWrapper를 사용하여 적절한 이름으로 동적으로 변환해야 합니다.