Un proveedor de contenido multimedia en la nube proporciona contenido multimedia adicional de la nube al Android
selector de fotos. Los usuarios pueden seleccionar fotos o videos proporcionados por el
proveedor de contenido multimedia en la nube cuando una app usa ACTION_PICK_IMAGES
o
ACTION_GET_CONTENT
para solicitar archivos multimedia al usuario Un contenido multimedia en la nube
proveedor también puede brindar información sobre los álbumes, que se pueden explorar en la
Selector de fotos de Android
Antes de comenzar
Ten en cuenta los siguientes elementos antes de comenzar a crear proveedor de contenido multimedia.
Requisitos
Android está ejecutando un programa piloto para permitir que las apps nominadas para OEM se conviertan en la nube proveedores de medios. Solo las apps nominadas por OEM son aptas para participar en como proveedor de contenido multimedia en la nube para Android. Cada El OEM puede nominar hasta 3 apps. Una vez aprobadas, se podrá acceder a estas apps como proveedores de contenido multimedia en la nube en cualquier dispositivo Android con GMS en el que estén esté instalado.
Android mantiene una lista del servidor de todos los proveedores de servicios en la nube aptos. Cada OEM puedes elegir un proveedor de servicios en la nube predeterminado con una superposición configurable. Nominada Las apps deben cumplir con todos los requisitos técnicos y aprobar todas las pruebas de calidad. Para aprender más información sobre el proceso del programa piloto para proveedores de medios en la nube de OEM y completa el formulario de consultas.
Decide si necesitas crear un proveedor de contenido multimedia en la nube
Los proveedores de contenido multimedia en la nube son apps o servicios que actúan como fuente principal para crear copias de seguridad y recuperar fotos y videos de la nube. Si tu app tiene una biblioteca de contenido útil, pero no suele usarse como almacenamiento de fotos, deberías crear un proveedor de documentos en su lugar.
Un proveedor de servicios en la nube activo por perfil
Puede haber, como máximo, un proveedor activo de contenido multimedia en la nube a la vez para cada instancia de Android perfil. Los usuarios pueden quitar o cambiar su proveedor de contenido multimedia en la nube seleccionado en cualquier momento desde la configuración del selector de fotos.
De forma predeterminada, el selector de fotos de Android intentará elegir un proveedor de servicios en la nube. automáticamente.
- Si solo hay un proveedor de servicios en la nube apto en el dispositivo, se actualizará la app automáticamente como el proveedor actual.
Si hay más de un proveedor de servicios en la nube apto en el dispositivo y uno de coincidan con la configuración predeterminada del OEM y, luego, se seleccionará la app elegida por el OEM.
Si hay más de un proveedor de servicios en la nube apto en el dispositivo y ninguno de coinciden con la configuración predeterminada elegida por el OEM, no se seleccionará ninguna app.
Compila tu proveedor de contenido multimedia en la nube
En el siguiente diagrama, se ilustra la secuencia de eventos antes y durante
una sesión de selección de fotos entre la aplicación para Android, el selector de fotos de Android,
MediaProvider
del dispositivo local y CloudMediaProvider
.
- El sistema inicializa el proveedor de servicios en la nube preferido del usuario y, de manera periódica, Sincroniza los metadatos multimedia en el backend del selector de fotos de Android.
- Cuando una app para Android inicia el selector de fotos, antes de mostrar un local combinado o cuadrícula de elementos en la nube al usuario, el selector de fotos realiza una evaluación sincronización incremental con el proveedor de servicios en la nube para garantizar que los resultados estén lo más actualizados de la forma más eficaz posible. Después de recibir una respuesta o cuando se cumple el plazo, el la cuadrícula del selector de fotos ahora muestra todas las fotos accesibles, combinando las almacenadas localmente en tu dispositivo con los que están sincronizados desde la nube.
- Mientras el usuario se desplaza, el selector de fotos obtiene miniaturas de medios de la proveedor de contenido multimedia en la nube para mostrar en la IU.
- Cuando el usuario completa la sesión y los resultados incluyen un recurso multimedia en la nube el selector de fotos solicita descriptores de archivo para el contenido, genera un URI, y otorga acceso al archivo a la aplicación que realiza la llamada.
- La app ahora puede abrir el URI y tiene acceso de solo lectura al contenido multimedia contenidos. De forma predeterminada, los metadatos sensibles están ocultos. El selector de fotos aprovecha el sistema de archivos FUSE para coordinar el intercambio de datos entre La app para Android y el proveedor de contenido multimedia en la nube
Problemas comunes
Aquí encontrarás algunas consideraciones importantes para tener en cuenta al considerar tu implementación:
Cómo evitar archivos duplicados
Como el selector de fotos de Android no tiene forma de inspeccionar el estado del contenido multimedia de la nube,
CloudMediaProvider
debe proporcionar el MEDIA_STORE_URI
en el cursor
de cualquier archivo que exista en la nube y en el dispositivo local, o
los usuarios verán los archivos duplicados en el selector de fotos.
Cómo optimizar el tamaño de las imágenes para la vista previa
Es muy importante que el archivo que muestra onOpenPreview
no sea el archivo
resolución y que cumpla con el Size
que se solicita. Una imagen demasiado grande
generarán tiempos de carga en la IU, y una imagen demasiado pequeña puede estar pixelada o
desenfocado en función del tamaño de la pantalla del dispositivo.
Cómo controlar la orientación correcta
Si las miniaturas que se muestran en onOpenPreview
no contienen sus datos EXIF, tendrán
se deben devolver en la orientación correcta para evitar que las miniaturas se roten
incorrectamente en la cuadrícula de vista previa.
Evita el acceso no autorizado
Verifica el valor de MANAGE_CLOUD_MEDIA_PROVIDERS_PERMISSION
antes de devolver los datos a
el llamador del ContentProvider. Esto evitará que las apps no autorizadas
el acceso a los datos en la nube.
La clase CloudMediaProvider
Derivado de android.content.ContentProvider
, el CloudMediaProvider
incluye métodos como los que se muestran en el siguiente ejemplo:
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
}
Java
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);
}
La clase CloudMediaProviderContract
Además de la clase de implementación principal de CloudMediaProvider
, el elemento
El selector de fotos de Android incorpora una clase CloudMediaProviderContract
.
En esta clase, se describe la interoperabilidad entre el selector de fotos y la nube
proveedor de medios, que incluye aspectos como MediaCollectionInfo
para
operaciones de sincronización, columnas Cursor
anticipadas y Bundle
adicionales.
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"
}
}
Java
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
El sistema operativo usa el método onGetMediaCollectionInfo()
para
la validez de los elementos de medios
en la nube almacenados en caché y determinar
y la sincronización con el proveedor
de contenido multimedia en la nube. Debido a la posibilidad de que los usuarios
llamadas por el sistema operativo, se considera onGetMediaCollectionInfo()
es fundamental para el rendimiento; es crucial evitar operaciones de larga duración o procesos
que podrían afectar el rendimiento de forma negativa. El sistema operativo almacena en caché
respuestas anteriores de este método y las compara con las respuestas posteriores
para determinar las acciones adecuadas.
Kotlin
abstract fun onGetMediaCollectionInfo(extras: Bundle): Bundle
Java
@NonNull
public abstract Bundle onGetMediaCollectionInfo(@NonNull Bundle extras);
El paquete MediaCollectionInfo
que se muestra incluye las siguientes constantes:
onQueryMedia
El método onQueryMedia()
se usa para propagar la cuadrícula de fotos principal en
selector de fotos en una variedad de vistas. Estas llamadas pueden ser sensibles a la latencia y
Se puede llamar como parte de una sincronización proactiva en segundo plano o durante el selector de fotos.
sesiones cuando se requiere un estado de sincronización completo o incremental. El selector de fotos
de usuario no esperará indefinidamente una respuesta para mostrar los resultados
podría agotar el tiempo de espera de estas solicitudes para la interfaz de usuario. El cursor que se muestra
tratarán de ser procesados en la base de datos del selector de fotos para el futuro
sesiones.
Este método muestra un Cursor
que representa todos los elementos multimedia del contenido.
La colección se filtra de manera opcional por los extras proporcionados y se ordena de forma inversa.
orden cronológico de MediaColumns#DATE_TAKEN_MILLIS
(elementos más recientes
primero).
El paquete CloudMediaProviderContract
que se muestra incluye lo siguiente:
constantes:
EXTRA_ALBUM_ID
EXTRA_LOOPING_PLAYBACK_ENABLED
EXTRA_MEDIA_COLLECTION_ID
EXTRA_PAGE_SIZE
EXTRA_PAGE_TOKEN
EXTRA_PREVIEW_THUMBNAIL
EXTRA_SURFACE_CONTROLLER_AUDIO_MUTE_ENABLED
EXTRA_SYNC_GENERATION
MANAGE_CLOUD_MEDIA_PROVIDERS_PERMISSION
PROVIDER_INTERFACE
El proveedor de contenido multimedia en la nube debe establecer
CloudMediaProviderContract#EXTRA_MEDIA_COLLECTION_ID
como parte de la
Bundle
Si no se establece, se considerará un error y se invalidará el Cursor
que se muestra. Si
el proveedor de contenido multimedia en la nube maneja los filtros en los extras proporcionados, debe agregar
la clave a ContentResolver#EXTRA_HONORED_ARGS
como parte del
Cursor#setExtras
onQueryDeletedMedia
Se usa el método onQueryDeletedMedia()
para garantizar que se borren los elementos del
la cuenta de Cloud se quitaron correctamente de la interfaz de usuario del selector de fotos. Debido a
la sensibilidad de latencia potencial, estas llamadas pueden iniciarse como parte de lo siguiente:
- Sincronización proactiva en segundo plano
- Sesiones del selector de fotos (cuando se requiere un estado de sincronización completa o incremental)
La interfaz de usuario del selector de fotos prioriza una experiencia del usuario responsiva y
no esperarán indefinidamente una respuesta. Para mantener interacciones fluidas,
se pueden producir tiempos de espera. Se intentará procesar cualquier solicitud de Cursor
que se devuelva.
en la base de datos del selector de fotos para sesiones futuras.
Este método muestra un Cursor
que representa todos los elementos multimedia borrados en el
toda la colección de contenido multimedia dentro de la versión actual del proveedor como lo muestra
onGetMediaCollectionInfo()
Estos elementos se pueden filtrar de manera opcional por extras.
El proveedor de contenido multimedia en la nube debe establecer la
CloudMediaProviderContract#EXTRA_MEDIA_COLLECTION_ID
como parte de la
Cursor#setExtras
Si no configuras esto, se considerará un error y se invalidará Cursor
. Si
el proveedor manejó cualquier filtro en los extras proporcionados, debe agregar la clave al
ContentResolver#EXTRA_HONORED_ARGS
OnQueryAlbums
El método onQueryAlbums()
se usa para recuperar una lista de álbumes en la nube que
están disponibles en el proveedor de servicios en la nube y sus metadatos asociados. Consulta
CloudMediaProviderContract.AlbumColumns
para obtener más detalles.
Este método devuelve un Cursor
que representa todos los elementos del álbum en el contenido multimedia.
La colección se filtra de manera opcional por los extras proporcionados y se ordena de forma inversa.
orden cronológico de AlbumColumns#DATE_TAKEN_MILLIS
, elementos más recientes
antes de empezar. El proveedor de contenido multimedia en la nube debe establecer la
CloudMediaProviderContract#EXTRA_MEDIA_COLLECTION_ID
como parte de la
Cursor
Si no se establece, se considerará un error y se invalidará el Cursor
que se muestra. Si
el proveedor manejó cualquier filtro en los extras proporcionados, debe agregar la clave al
el ContentResolver#EXTRA_HONORED_ARGS
como parte del Cursor
que se muestra.
onOpenMedia
El método onOpenMedia()
debe mostrar el contenido multimedia de tamaño completo identificado por
el mediaId
proporcionado. Si este método se bloquea durante la descarga de contenido al
, debes verificar periódicamente la CancellationSignal
proporcionada para anular
las solicitudes abandonadas.
onOpenPreview
El método onOpenPreview()
debe mostrar una miniatura del elemento proporcionado.
size
para el elemento del mediaId proporcionado. La miniatura debe estar en
CloudMediaProviderContract.MediaColumns#MIME_TYPE
original y se espera que
ser de resolución mucho más baja que el elemento que muestra onOpenMedia
. Si este método
se bloquea al descargar contenido en el dispositivo, deberías
Verifica el CancellationSignal
proporcionado para anular las solicitudes abandonadas.
onCreateCloudMediaSurfaceController
El método onCreateCloudMediaSurfaceController()
debe mostrar un
CloudMediaSurfaceController
que se usa para renderizar la vista previa de elementos multimedia.
null
si no se admite la renderización de la vista previa
CloudMediaSurfaceController
administra la renderización de la vista previa de elementos multimedia.
en instancias determinadas de Surface
. Los métodos de esta clase deben ser
asíncronas y no debe bloquearse realizando ninguna operación pesada. Un solo
La instancia CloudMediaSurfaceController
es responsable de renderizar varias
elementos multimedia asociados con varias plataformas.
CloudMediaSurfaceController
es compatible con la siguiente lista de
devoluciones de llamada de ciclo de vida:
onConfigChange
onDestroy
onMediaPause
onMediaPlay
onMediaSeekTo
onPlayerCreate
onPlayerRelease
onSurfaceChanged
onSurfaceCreated
onSurfaceDestroyed