Android용 클라우드 미디어 제공자 만들기

클라우드 미디어 제공업체는 Android 사진 선택 도구를 선택합니다. 사용자는 Google에서 제공한 사진 또는 동영상을 앱이 ACTION_PICK_IMAGES 또는 사용자에게 미디어 파일을 요청하는 ACTION_GET_CONTENT 클라우드 미디어 제공업체는 앨범에 대한 정보를 제공할 수 있으며, 이러한 앨범은 Android 사진 선택 도구

시작하기 전에

클라우드 빌드를 시작하기 전에 다음 항목을 고려하세요. 선택합니다.

자격요건

Android는 OEM 후보 앱을 클라우드로 전환할 수 있는 파일럿 프로그램을 진행하고 있습니다. 되었습니다. OEM에서 지명한 앱만 하여 현재 Android용 클라우드 미디어 제공업체가 되는 방법을 알아보세요. 각 OEM은 최대 3개의 앱을 추천할 수 있습니다. 승인되면 다음 앱으로 이러한 앱에 액세스할 수 있습니다. GMS Android 지원 기기의 클라우드 미디어 제공업체 설치해야 합니다.

Android에서는 요건을 충족하는 모든 클라우드 제공업체의 서버 측 목록을 유지합니다. 각 OEM 구성 가능한 오버레이를 사용하여 기본 클라우드 제공업체를 선택할 수 있습니다. 후보작 앱은 모든 기술 요구사항을 충족하고 모든 품질 테스트를 통과해야 합니다. 배우기 위해 OEM 클라우드 미디어 제공업체 파일럿 프로그램의 프로세스에 대해 자세히 알아보고 문의 양식을 작성해 주세요.

클라우드 미디어 제공업체를 만들어야 하는지 결정

클라우드 미디어 제공업체는 사용자의 역할을 하는 앱 또는 서비스를 말합니다 클라우드에서 사진과 동영상을 백업하고 검색하기 위한 기본 소스입니다. 앱에 유용한 콘텐츠 라이브러리가 있지만 일반적으로 사진 저장 솔루션을 사용하려면 문서 제공업체를 만드는 것이 좋습니다. 하세요.

프로필당 활성 클라우드 제공업체 1개

Android에 대해 한 번에 최대 하나의 활성 클라우드 미디어 제공업체가 있을 수 있습니다. 프로필을 선택합니다. 사용자가 선택한 클라우드 미디어 제공업체를 삭제하거나 변경할 수 있음 앱을 언제든지 사용 중지할 수 있습니다.

기본적으로 Android 사진 선택 도구는 클라우드 제공업체를 선택하려고 시도합니다. 자동으로 확장 및 축소할 수 있습니다

  • 기기에 사용 가능한 클라우드 제공업체가 하나만 있는 경우 해당 앱은 현재 제공업체로 자동 선택됩니다.
  • 기기에 사용 가능한 클라우드 제공업체가 2개 이상이고 OEM이 선택한 기본값과 일치하면 OEM에서 선택한 앱이 선택됩니다.

  • 기기에 적격한 클라우드 제공업체가 두 개 이상 있고 OEM에서 선택한 기본값과 일치한다면 앱이 선택되지 않습니다.

클라우드 미디어 제공업체 빌드

다음 다이어그램은 이벤트 이전과 도중의 이벤트 순서를 보여줍니다. Android 앱, Android 사진 선택 도구, 로컬 기기의 MediaProviderCloudMediaProvider입니다.

<ph type="x-smartling-placeholder">
</ph> 사진 선택 도구에서 클라우드 미디어 제공업체로의 흐름을 보여주는 시퀀스 다이어그램
그림 1: 사진 선택 세션 중의 이벤트 시퀀스 다이어그램
  1. 시스템은 사용자가 선호하는 클라우드 제공업체를 초기화하고 주기적으로 미디어 메타데이터를 Android 사진 선택 도구 백엔드에 동기화합니다.
  2. Android 앱이 사진 선택 도구를 실행한 후 병합된 로컬을 표시할 때 또는 클라우드 항목 그리드를 사용자에게 표시하는 경우 사진 선택 도구는 지연 시간에 민감한 클라우드 제공업체와 증분 동기화하여 결과를 최신 상태로 유지 할 수 있습니다. 응답을 받은 후 또는 기한에 도달한 경우, 이제 사진 선택 도구 그리드에 저장된 사진을 결합하여 액세스 가능한 모든 사진을 표시합니다. 클라우드에서 동기화 된 파일을 사용하여 기기에 로컬로 저장할 수 있습니다.
  3. 사용자가 스크롤하면 사진 선택 도구가 UI에 표시할 수 있습니다.
  4. 사용자가 세션을 완료하고 결과에 클라우드 미디어가 포함된 경우 사진 선택 도구가 콘텐츠의 파일 설명자를 요청하고 URI에 권한을 부여하고 호출하는 애플리케이션에 파일 액세스 권한을 부여합니다.
  5. 이제 앱에서 URI를 열 수 있으며 미디어에 대한 읽기 전용 액세스 권한이 있습니다. 있습니다. 민감한 메타데이터는 기본적으로 수정됩니다. 사진 선택 도구 FUSE 파일 시스템을 활용하여 Android 앱 및 클라우드 미디어 제공업체

일반적인 문제

다음은 전환 최적화와 관련하여 고려해야 할 몇 가지 중요한 사항입니다. 구현:

중복 파일 방지

Android 사진 선택 도구에는 클라우드 미디어 상태를 검사할 방법이 없으므로 CloudMediaProvider는 커서에 MEDIA_STORE_URI를 제공해야 합니다. 또는 로컬 장치에 존재하는 모든 파일의 행 또는 사진 선택 도구에 중복 파일이 표시됩니다.

미리보기 표시를 위한 이미지 크기 최적화

onOpenPreview에서 반환된 파일이 전체 해상도 이미지이며 요청 중인 Size를 준수합니다. 이미지가 너무 큽니다. UI에 로드 시간이 발생하고 너무 작으면 이미지가 깨지거나 기기의 화면 크기에 따라 흐릿하게 표시될 수 있습니다.

올바른 방향 처리

onOpenPreview에서 반환된 썸네일에 EXIF 데이터가 포함되지 않은 경우 썸네일이 회전되지 않도록 올바른 방향으로 반환해야 합니다. 잘못 배치될 수 있습니다

무단 액세스 방지

데이터를 반환하기 전에 MANAGE_CLOUD_MEDIA_PROVIDERS_PERMISSION 확인 있습니다. 이렇게 하면 승인되지 않은 앱이 클라우드 데이터에 액세스하는 방법을 알아보겠습니다

CloudMediaProvider 클래스

android.content.ContentProvider에서 파생된 CloudMediaProvider 클래스에는 다음 예에 표시된 것과 같은 메서드가 포함됩니다.

Kotlin

abstract class CloudMediaProvider : ContentProvider() {

    @NonNull
    abstract override fun onGetMediaCollectionInfo(@NonNull bundle: Bundle): Bundle

    @NonNull
    override fun onQueryAlbums(@NonNull bundle: Bundle): Cursor = TODO("Implement onQueryAlbums")

    @NonNull
    abstract override fun onQueryDeletedMedia(@NonNull bundle: Bundle): Cursor

    @NonNull
    abstract override fun onQueryMedia(@NonNull bundle: Bundle): Cursor

    @NonNull
    abstract override fun onOpenMedia(
        @NonNull string: String,
        @Nullable bundle: Bundle?,
        @Nullable cancellationSignal: CancellationSignal?
    ): ParcelFileDescriptor

    @NonNull
    abstract override fun onOpenPreview(
        @NonNull string: String,
        @NonNull point: Point,
        @Nullable bundle: Bundle?,
        @Nullable cancellationSignal: CancellationSignal?
    ): AssetFileDescriptor

    @Nullable
    override fun onCreateCloudMediaSurfaceController(
        @NonNull bundle: Bundle,
        @NonNull callback: CloudMediaSurfaceStateChangedCallback
    ): CloudMediaSurfaceController? = null
}

자바

public abstract class CloudMediaProvider extends android.content.ContentProvider {

  @NonNull
  public abstract android.os.Bundle onGetMediaCollectionInfo(@NonNull android.os.Bundle);

  @NonNull
  public android.database.Cursor onQueryAlbums(@NonNull android.os.Bundle);

  @NonNull
  public abstract android.database.Cursor onQueryDeletedMedia(@NonNull android.os.Bundle);

  @NonNull
  public abstract android.database.Cursor onQueryMedia(@NonNull android.os.Bundle);

  @NonNull
  public abstract android.os.ParcelFileDescriptor onOpenMedia(@NonNull String, @Nullable android.os.Bundle, @Nullable android.os.CancellationSignal) throws java.io.FileNotFoundException;

  @NonNull
  public abstract android.content.res.AssetFileDescriptor onOpenPreview(@NonNull String, @NonNull android.graphics.Point, @Nullable android.os.Bundle, @Nullable android.os.CancellationSignal) throws java.io.FileNotFoundException;

  @Nullable
  public android.provider.CloudMediaProvider.CloudMediaSurfaceController onCreateCloudMediaSurfaceController(@NonNull android.os.Bundle, @NonNull android.provider.CloudMediaProvider.CloudMediaSurfaceStateChangedCallback);
}

CloudMediaProviderContract 클래스

기본 CloudMediaProvider 구현 클래스 외에도 Android 사진 선택 도구는 CloudMediaProviderContract 클래스를 통합합니다. 이 클래스에서는 사진 선택 도구와 클라우드 간의 상호 운용성에 관해 간략히 설명합니다. 미디어 제공자에는 MediaCollectionInfo과 같은 측면이 포함됩니다. 동기화 작업, 예상되는 Cursor 열, 추가 항목 Bundle개.

Kotlin

object CloudMediaProviderContract {

    const val EXTRA_ALBUM_ID = "android.provider.extra.ALBUM_ID"
    const val EXTRA_LOOPING_PLAYBACK_ENABLED = "android.provider.extra.LOOPING_PLAYBACK_ENABLED"
    const val EXTRA_MEDIA_COLLECTION_ID = "android.provider.extra.MEDIA_COLLECTION_ID"
    const val EXTRA_PAGE_SIZE = "android.provider.extra.PAGE_SIZE"
    const val EXTRA_PAGE_TOKEN = "android.provider.extra.PAGE_TOKEN"
    const val EXTRA_PREVIEW_THUMBNAIL = "android.provider.extra.PREVIEW_THUMBNAIL"
    const val EXTRA_SURFACE_CONTROLLER_AUDIO_MUTE_ENABLED = "android.provider.extra.SURFACE_CONTROLLER_AUDIO_MUTE_ENABLED"
    const val EXTRA_SYNC_GENERATION = "android.provider.extra.SYNC_GENERATION"
    const val MANAGE_CLOUD_MEDIA_PROVIDERS_PERMISSION = "com.android.providers.media.permission.MANAGE_CLOUD_MEDIA_PROVIDERS"
    const val PROVIDER_INTERFACE = "android.content.action.CLOUD_MEDIA_PROVIDER"

    object MediaColumns {
        const val DATE_TAKEN_MILLIS = "date_taken_millis"
        const val DURATION_MILLIS = "duration_millis"
        const val HEIGHT = "height"
        const val ID = "id"
        const val IS_FAVORITE = "is_favorite"
        const val MEDIA_STORE_URI = "media_store_uri"
        const val MIME_TYPE = "mime_type"
        const val ORIENTATION = "orientation"
        const val SIZE_BYTES = "size_bytes"
        const val STANDARD_MIME_TYPE_EXTENSION = "standard_mime_type_extension"
        const val STANDARD_MIME_TYPE_EXTENSION_ANIMATED_WEBP = 3 // 0x3
        const val STANDARD_MIME_TYPE_EXTENSION_GIF = 1 // 0x1
        const val STANDARD_MIME_TYPE_EXTENSION_MOTION_PHOTO = 2 // 0x2
        const val STANDARD_MIME_TYPE_EXTENSION_NONE = 0 // 0x0
        const val SYNC_GENERATION = "sync_generation"
        const val WIDTH = "width"
    }

    object AlbumColumns {
        const val DATE_TAKEN_MILLIS = "date_taken_millis"
        const val DISPLAY_NAME = "display_name"
        const val ID = "id"
        const val MEDIA_COUNT = "album_media_count"
        const val MEDIA_COVER_ID = "album_media_cover_id"
    }

    object MediaCollectionInfo {
        const val ACCOUNT_CONFIGURATION_INTENT = "account_configuration_intent"
        const val ACCOUNT_NAME = "account_name"
        const val LAST_MEDIA_SYNC_GENERATION = "last_media_sync_generation"
        const val MEDIA_COLLECTION_ID = "media_collection_id"
    }
}

자바

public final class CloudMediaProviderContract {

  public static final String EXTRA_ALBUM_ID = "android.provider.extra.ALBUM_ID";
  public static final String EXTRA_LOOPING_PLAYBACK_ENABLED = "android.provider.extra.LOOPING_PLAYBACK_ENABLED";
  public static final String EXTRA_MEDIA_COLLECTION_ID = "android.provider.extra.MEDIA_COLLECTION_ID";
  public static final String EXTRA_PAGE_SIZE = "android.provider.extra.PAGE_SIZE";
  public static final String EXTRA_PAGE_TOKEN = "android.provider.extra.PAGE_TOKEN";
  public static final String EXTRA_PREVIEW_THUMBNAIL = "android.provider.extra.PREVIEW_THUMBNAIL";
  public static final String EXTRA_SURFACE_CONTROLLER_AUDIO_MUTE_ENABLED = "android.provider.extra.SURFACE_CONTROLLER_AUDIO_MUTE_ENABLED";
  public static final String EXTRA_SYNC_GENERATION = "android.provider.extra.SYNC_GENERATION";
  public static final String MANAGE_CLOUD_MEDIA_PROVIDERS_PERMISSION = "com.android.providers.media.permission.MANAGE_CLOUD_MEDIA_PROVIDERS";
  public static final String PROVIDER_INTERFACE = "android.content.action.CLOUD_MEDIA_PROVIDER";
}

// Columns available for every media item
public static final class CloudMediaProviderContract.MediaColumns {

  public static final String DATE_TAKEN_MILLIS = "date_taken_millis";
  public static final String DURATION_MILLIS = "duration_millis";
  public static final String HEIGHT = "height";
  public static final String ID = "id";
  public static final String IS_FAVORITE = "is_favorite";
  public static final String MEDIA_STORE_URI = "media_store_uri";
  public static final String MIME_TYPE = "mime_type";
  public static final String ORIENTATION = "orientation";
  public static final String SIZE_BYTES = "size_bytes";
  public static final String STANDARD_MIME_TYPE_EXTENSION = "standard_mime_type_extension";
  public static final int STANDARD_MIME_TYPE_EXTENSION_ANIMATED_WEBP = 3; // 0x3
  public static final int STANDARD_MIME_TYPE_EXTENSION_GIF = 1; // 0x1 
  public static final int STANDARD_MIME_TYPE_EXTENSION_MOTION_PHOTO = 2; // 0x2 
  public static final int STANDARD_MIME_TYPE_EXTENSION_NONE = 0; // 0x0 
  public static final String SYNC_GENERATION = "sync_generation";
  public static final String WIDTH = "width";
}

// Columns available for every album item
public static final class CloudMediaProviderContract.AlbumColumns {

  public static final String DATE_TAKEN_MILLIS = "date_taken_millis";
  public static final String DISPLAY_NAME = "display_name";
  public static final String ID = "id";
  public static final String MEDIA_COUNT = "album_media_count";
  public static final String MEDIA_COVER_ID = "album_media_cover_id";
}

// Media Collection metadata that is cached by the OS to compare sync states.
public static final class CloudMediaProviderContract.MediaCollectionInfo {

  public static final String ACCOUNT_CONFIGURATION_INTENT = "account_configuration_intent";
  public static final String ACCOUNT_NAME = "account_name";
  public static final String LAST_MEDIA_SYNC_GENERATION = "last_media_sync_generation";
  public static final String MEDIA_COLLECTION_ID = "media_collection_id";
}

onGetMediaCollectionInfo

onGetMediaCollectionInfo() 메서드는 운영체제에서 캐시된 클라우드 미디어 항목의 유효성을 평가하고 클라우드 미디어 제공업체와의 동기화입니다. 빈번하게 액세스 가능한 운영체제별 호출에서 onGetMediaCollectionInfo()가 성능이 중요합니다. 장기 실행 작업이나 부하가 많이 걸리는 작업을 피하는 것이 실적에 부정적인 영향을 줄 수 있는 영향을 최소화합니다. 운영체제 캐시는 이 메서드의 이전 응답을 가져와 후속 응답과 비교합니다. 적절한 조치를 결정합니다.

Kotlin

abstract fun onGetMediaCollectionInfo(extras: Bundle): Bundle

자바

@NonNull
public abstract Bundle onGetMediaCollectionInfo(@NonNull Bundle extras);

반환된 MediaCollectionInfo 번들에는 다음 상수가 포함됩니다.

onQueryMedia

onQueryMedia() 메서드는 사진 선택 도구를 사용할 수 있습니다. 이러한 호출은 지연 시간에 민감할 수 있으며, 백그라운드 선제적 동기화의 일부로 또는 사진 선택 도구 중에 호출할 수 있습니다. 전체 또는 증분 동기화 상태가 필요한 경우 세션 사진 선택 도구 사용자 인터페이스는 응답이 결과를 표시할 때까지 무기한 대기하지 않을 것입니다. 사용자 인터페이스 목적으로 이러한 요청이 타임아웃될 수 있습니다. 반환된 커서 향후에 사용할 수 있도록 사진 선택 도구의 데이터베이스로 계속 처리하려고 시도합니다. 세션.

이 메서드는 미디어의 모든 미디어 항목을 나타내는 Cursor를 반환합니다. 제공된 추가 항목으로 선택적으로 필터링하고 역순으로 정렬된 컬렉션 MediaColumns#DATE_TAKEN_MILLIS 시간순 (최근 항목) 입니다.

반환된 CloudMediaProviderContract 번들에는 다음이 포함됩니다. 상수:

클라우드 미디어 제공업체는 CloudMediaProviderContract#EXTRA_MEDIA_COLLECTION_ID: 반환 시 Bundle입니다. 이를 설정하지 않으면 오류가 발생하며 반환된 Cursor가 무효화됩니다. 만약 클라우드 미디어 제공업체가 제공된 Extras의 필터를 모두 처리한 경우 ContentResolver#EXTRA_HONORED_ARGS에 대한 키를 반환된 Cursor#setExtras입니다.

onQueryDELETEMedia

onQueryDeletedMedia() 메서드는 클라우드 계정이 사진 선택 도구 사용자 인터페이스에서 올바르게 삭제됩니다. 원인 이러한 호출은 다음과 같은 작업의 일부로 시작될 수 있습니다.

  • 백그라운드 선제적 동기화
  • 사진 선택 도구 세션 (전체 또는 증분 동기화 상태가 필요한 경우)

사진 선택 도구의 사용자 인터페이스는 반응형 사용자 환경을 우선시하며 응답을 무한히 기다리지 않습니다 원활한 상호작용을 유지하려면 시간 초과가 발생할 수 있습니다 반환된 Cursor은(는) 계속 처리를 시도합니다. 사진 선택 도구의 데이터베이스에 삽입됩니다.

이 메서드는Cursor 현재 제공자 버전 내의 전체 미디어 컬렉션에서 반환한 값 onGetMediaCollectionInfo()입니다. 이러한 항목은 선택적으로 추가 항목으로 필터링할 수 있습니다. 클라우드 미디어 제공업체는 CloudMediaProviderContract#EXTRA_MEDIA_COLLECTION_ID: 반환 시 Cursor#setExtras 이를 설정하지 않으면 오류가 발생하며 Cursor가 무효화됩니다. 만약 제공자가 제공된 Extras의 필터를 모두 처리한 경우에는 키를 ContentResolver#EXTRA_HONORED_ARGS

onQuery앨범

onQueryAlbums() 메서드는 그리고 관련 메타데이터가 있습니다. 자세한 내용은 CloudMediaProviderContract.AlbumColumns에서 자세한 내용을 확인하세요.

이 메서드는 미디어의 모든 앨범 항목을 나타내는 Cursor를 반환합니다. 제공된 추가 항목으로 선택적으로 필터링하고 역순으로 정렬된 컬렉션 AlbumColumns#DATE_TAKEN_MILLIS 시간순 , 최근 항목 있습니다. 클라우드 미디어 제공업체는 CloudMediaProviderContract#EXTRA_MEDIA_COLLECTION_ID: 반환 시 Cursor입니다. 이를 설정하지 않으면 오류가 발생하며 반환된 Cursor가 무효화됩니다. 만약 제공자가 제공된 Extras의 필터를 모두 처리한 경우에는 키를 반환된 Cursor의 일부로 ContentResolver#EXTRA_HONORED_ARGS입니다.

onOpenMedia

onOpenMedia() 메서드는 제공된 mediaId 이 메서드가 주기적으로 제공된 CancellationSignal를 확인하여 취소해야 합니다. 처리할 수 있습니다.

onOpenPreview

onOpenPreview() 메서드는 제공된 제공된 mediaId 항목의 size입니다. 썸네일은 원래 CloudMediaProviderContract.MediaColumns#MIME_TYPE이며 onOpenMedia에서 반환한 항목보다 해상도가 훨씬 낮습니다. 이 방법이 차단되는 경우 정기적으로 제공된 CancellationSignal를 확인하여 포기된 요청을 취소합니다.

Cloud MediaSurfaceController

onCreateCloudMediaSurfaceController() 메서드는 미디어 항목의 미리보기를 렌더링하는 데 사용되는 CloudMediaSurfaceController 미리보기 렌더링이 지원되지 않는 경우 null입니다.

CloudMediaSurfaceController는 미디어 항목의 미리보기 렌더링을 관리합니다. (Surface의 지정된 인스턴스에서) 이 클래스의 메서드는 비동기적이며 큰 작업을 수행하여 차단해서는 안 됩니다. 단일 CloudMediaSurfaceController 인스턴스는 여러 여러 표면과 연결된 미디어 항목입니다.

CloudMediaSurfaceController는 수명 주기 콜백에 따라 다릅니다.