Um provedor de mídia em nuvem fornece conteúdo de mídia em nuvem adicional para o Android
seletor de fotos. Os usuários podem selecionar fotos ou vídeos fornecidos pelo
provedor de mídia em nuvem quando um app usa ACTION_PICK_IMAGES
ou
ACTION_GET_CONTENT
para solicitar arquivos de mídia do usuário. Uma instância de mídia em nuvem
provedor também pode dar informações sobre álbuns, que podem ser procurados no
Seletor de fotos do Android.
Antes de começar
Considere os seguintes itens antes de começar a criar sua nuvem provedor de mídia.
Qualificação
O Android está executando um programa piloto para permitir que apps indicados pelo OEM se tornem a nuvem provedores de mídia. Somente apps indicados por OEMs estão qualificados para participar. este programa para se tornar um provedor de mídia em nuvem para Android neste momento. Cada O OEM pode indicar até três apps. Depois de aprovados, esses apps ficam acessíveis como provedores de mídia em nuvem em qualquer dispositivo com tecnologia Android que use GMS nos quais estejam instalado.
O Android mantém uma lista do lado do servidor de todos os provedores de nuvem qualificados. Cada OEM É possível escolher um provedor de nuvem padrão usando uma sobreposição configurável. Indicados os apps precisam atender a todos os requisitos técnicos e passar em todos os testes de qualidade. Para saber mais sobre o processo do programa piloto do provedor de mídia em nuvem OEM e requisitos, preencha o formulário de consulta.
Decida se você precisa criar um provedor de mídia em nuvem
Os provedores de mídia em nuvem são aplicativos ou serviços que atuam como um fonte principal para fazer backup e recuperar fotos e vídeos da nuvem. Caso seu app tenha uma biblioteca de conteúdo útil, mas ela normalmente não é usada como de armazenamento de fotos, considere criar um provedor de documentos como alternativa.
Um provedor de nuvem ativo por perfil
Pode haver no máximo um provedor de mídia em nuvem ativo por vez para cada serviço Android de usuário. Os usuários podem remover ou mudar o provedor de mídia em nuvem selecionado aplicativo a qualquer momento nas configurações do seletor de fotos.
Por padrão, o seletor de fotos do Android tenta escolher um provedor de nuvem automaticamente.
- Se houver apenas um provedor de nuvem qualificado no dispositivo, esse app como o provedor atual automaticamente.
Se houver mais de um provedor de nuvem qualificado no dispositivo e um dos eles corresponderem ao padrão escolhido pelo OEM, o app escolhido pelo OEM será selecionado.
Se houver mais de um provedor de nuvem qualificado no dispositivo e nenhum dos eles corresponderem ao padrão escolhido pelo OEM, nenhum app será selecionado.
Crie seu provedor de mídia em nuvem
O diagrama a seguir ilustra a sequência de eventos antes e durante
uma sessão de seleção de fotos entre o app Android, o seletor de fotos do Android, a
o MediaProvider
do dispositivo local e um CloudMediaProvider
.
- O sistema inicializa o provedor de nuvem preferido do usuário e periodicamente sincroniza metadados de mídia com o back-end do seletor de fotos do Android.
- Quando um app Android inicia o seletor de fotos antes de mostrar um local mesclado. ou grade de itens da nuvem para o usuário, o seletor de fotos faz uma verificação sincronização incremental com o provedor de nuvem para garantir que os resultados sejam atualizados possível. Depois de receber uma resposta ou quando o prazo for atingido, o a grade do seletor de fotos agora mostra todas as fotos acessíveis, combinando as armazenadas localmente em seu dispositivo com aqueles sincronizados a partir da nuvem.
- Enquanto o usuário rola, o seletor de fotos busca miniaturas de mídia do provedor de mídia em nuvem seja exibido na interface.
- Quando o usuário conclui a sessão e os resultados incluem uma mídia da nuvem item, o seletor de fotos solicita descritores de arquivo para o conteúdo, gera uma URI e concede acesso ao arquivo para o aplicativo de chamada.
- O app agora pode abrir o URI e tem acesso somente leitura à mídia conteúdo. Por padrão, os metadados confidenciais são editados. O seletor de fotos aproveita o sistema de arquivos FUSE para coordenar a troca de dados entre os app Android e o provedor de mídia em nuvem.
Problemas comuns
Aqui estão algumas considerações importantes para considerar ao implementação:
Evitar arquivos duplicados
Como o seletor de fotos do Android não tem como inspecionar o estado da mídia na nuvem,
o CloudMediaProvider
precisa fornecer o MEDIA_STORE_URI
no cursor.
de qualquer arquivo que exista na nuvem e no dispositivo local, ou a
o usuário vai encontrar arquivos duplicados no seletor de fotos.
Otimizar tamanhos de imagem para exibição de visualização
É muito importante que o arquivo retornado de onOpenPreview
não seja o
imagem com resolução melhor e segue a Size
solicitada. Imagem muito grande
levará a tempos de carregamento na interface, e uma imagem muito pequena poderá ficar pixelada
desfocadas com base no tamanho da tela do dispositivo.
Processar a orientação correta
Se as miniaturas retornadas no onOpenPreview
não tiverem os dados EXIF, elas
devem ser retornados na orientação correta para evitar que as miniaturas sejam giradas
incorretamente na grade de visualização.
Impedir acessos não autorizados
Verifique o MANAGE_CLOUD_MEDIA_PROVIDERS_PERMISSION
antes de retornar os dados ao
o autor da chamada do ContentProvider. Isso vai impedir que apps não autorizados
e acessar dados na nuvem.
A classe CloudMediaProvider
Derivado de android.content.ContentProvider
, o CloudMediaProvider
inclui métodos como os mostrados no exemplo abaixo:
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);
}
A classe CloudMediaProviderProvider
Além da classe de implementação principal CloudMediaProvider
, a
O seletor de fotos do Android incorpora uma classe CloudMediaProviderContract
.
Esta aula mostra a interoperabilidade entre o seletor de fotos e a nuvem
provedor de mídia, abrangendo aspectos como MediaCollectionInfo
para
operações de sincronização, colunas Cursor
antecipadas e extras 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"
}
}
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
O método onGetMediaCollectionInfo()
é usado pelo sistema operacional para
avaliar a validade dos itens de mídia na nuvem armazenados em cache e determinar
a sincronização com o provedor de mídia em nuvem. Devido ao potencial de erros frequentes
chamadas pelo sistema operacional, onGetMediaCollectionInfo()
é considerada
essencial para o desempenho, é crucial evitar operações de longa duração ou lado
que podem afetar negativamente o desempenho. O sistema operacional armazena em cache
respostas anteriores desse método e as compara com as respostas subsequentes
para determinar as ações apropriadas.
Kotlin
abstract fun onGetMediaCollectionInfo(extras: Bundle): Bundle
Java
@NonNull
public abstract Bundle onGetMediaCollectionInfo(@NonNull Bundle extras);
O pacote MediaCollectionInfo
retornado inclui as seguintes constantes:
onQueryMedia
O método onQueryMedia()
é usado para preencher a grade de fotos principal em
o seletor de fotos em diversas visualizações. Essas chamadas podem ser sensíveis à latência e
pode ser chamado como parte de uma sincronização proativa em segundo plano ou durante o seletor de fotos
sessões quando um estado de sincronização completo ou incremental é necessário. O seletor de fotos
interface do usuário não espera indefinidamente por uma resposta exibir resultados e
pode definir o tempo limite dessas solicitações
para fins de interface do usuário. O cursor retornado
ainda tentará ser processada no banco de dados do seletor de fotos para
de conteúdo.
Esse método retorna um Cursor
que representa todos os itens de mídia na mídia
coleção opcionalmente filtrada pelos extras fornecidos e ordenada ao contrário
ordem cronológica de MediaColumns#DATE_TAKEN_MILLIS
(itens mais recentes)
primeiro).
O pacote CloudMediaProviderContract
retornado inclui o seguinte:
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
O provedor de mídia em nuvem precisa definir
CloudMediaProviderContract#EXTRA_MEDIA_COLLECTION_ID
como parte da
Bundle
. Não definir isso é um erro e invalida o Cursor
retornado. Se
o provedor de mídia em nuvem manipulou todos os filtros nos extras fornecidos, deve adicionar
a chave para o ContentResolver#EXTRA_HONORED_ARGS
como parte da
Cursor#setExtras
.
onQueryExcluídoMedia
O método onQueryDeletedMedia()
é usado para garantir que os itens excluídos na
Google Cloud são removidas corretamente da interface do usuário do seletor de fotos. Devido a
a potencial sensibilidade à latência, essas chamadas podem ser iniciadas como parte de:
- Sincronização proativa em segundo plano
- Sessões do seletor de fotos (quando um estado de sincronização completo ou incremental é necessário)
A interface do usuário do seletor de fotos prioriza uma experiência do usuário responsiva e
não esperará indefinidamente por uma resposta. Para manter interações suaves,
de tempo limite podem ocorrer. O Cursor
retornado ainda vai tentar ser processado
no banco de dados do seletor de fotos para sessões futuras.
Esse método retorna um Cursor
que representa todos os itens de mídia excluídos da
toda a coleção de mídia na versão atual do provedor, conforme retornado por
onGetMediaCollectionInfo()
. Esses itens podem ser filtrados opcionalmente por extras.
O provedor de mídia em nuvem precisa definir
CloudMediaProviderContract#EXTRA_MEDIA_COLLECTION_ID
como parte da
Cursor#setExtras
Não definir isso é um erro e invalida o Cursor
. Se
o provedor manipulou todos os filtros nos extras fornecidos, deverá adicionar a chave ao
o ContentResolver#EXTRA_HONORED_ARGS
.
onQueryAlbums
O método onQueryAlbums()
é usado para buscar uma lista de álbuns do Cloud que
disponíveis no provedor de nuvem e os metadados associados. Consulte
CloudMediaProviderContract.AlbumColumns
para mais detalhes.
Esse método retorna um Cursor
que representa todos os itens do álbum na mídia
coleção opcionalmente filtrada pelos extras fornecidos e ordenada ao contrário
ordem cronológica de AlbumColumns#DATE_TAKEN_MILLIS
, itens mais recentes
primeiro. O provedor de mídia em nuvem precisa definir
CloudMediaProviderContract#EXTRA_MEDIA_COLLECTION_ID
como parte da
Cursor
. Não definir isso é um erro e invalida o Cursor
retornado. Se
o provedor manipulou todos os filtros nos extras fornecidos, deverá adicionar a chave ao
o ContentResolver#EXTRA_HONORED_ARGS
como parte do Cursor
retornado.
onOpenMedia
O método onOpenMedia()
deve retornar a mídia em tamanho original identificada por
o mediaId
fornecido. Se esse método bloquear o download de conteúdo para o
dispositivo, verifique periodicamente o CancellationSignal
fornecido para cancelar
abandonados.
onOpenPreview
O método onOpenPreview()
precisa retornar uma miniatura do
size
para o item do mediaId fornecido. A miniatura deve estar no
CloudMediaProviderContract.MediaColumns#MIME_TYPE
original e espera-se que
ter uma resolução muito menor do que o item retornado por onOpenMedia
. Se esse método
está bloqueada durante o download de conteúdo para o dispositivo, verifique periodicamente
verifica o CancellationSignal
fornecido para cancelar solicitações abandonadas.
onCreateCloudMediaSurfaceController
O método onCreateCloudMediaSurfaceController()
retorna uma
CloudMediaSurfaceController
usado para renderizar a visualização de itens de mídia ou
null
se a renderização da visualização não for compatível.
O CloudMediaSurfaceController
gerencia a renderização da visualização dos itens de mídia
em determinadas instâncias de Surface
. Os métodos dessa classe precisam ser
assíncronas e não devem bloquear por meio de operações pesadas. Um único
A instância CloudMediaSurfaceController
é responsável por renderizar vários
itens de mídia associados a várias plataformas.
O CloudMediaSurfaceController
é compatível com a seguinte lista de
callbacks do ciclo de vida:
onConfigChange
onDestroy
onMediaPause
onMediaPlay
onMediaSeekTo
onPlayerCreate
onPlayerRelease
onSurfaceChanged
onSurfaceCreated
onSurfaceDestroyed