Un provider di servizi multimediali cloud fornisce ulteriori contenuti multimediali cloud al cloud Android
selettore di foto. Gli utenti possono selezionare le foto o i video forniti
cloud media provider quando un'app usa ACTION_PICK_IMAGES
o
ACTION_GET_CONTENT
per richiedere file multimediali all'utente. Un elemento multimediale sul cloud
Il provider può anche fornire informazioni sugli album, che è possibile consultare nella
Selettore di foto Android.
Prima di iniziare
Considera i seguenti elementi prima di iniziare a creare il tuo cloud fornitore di contenuti multimediali.
Idoneità
Android sta conducendo un programma pilota per consentire alle app nominate dagli OEM di diventare cloud i fornitori di contenuti multimediali. Sono idonee a partecipare solo le app nominate dagli OEM. questo programma per diventare un fornitore di contenuti multimediali cloud per Android in questo momento. Ciascuna L'OEM può nominare fino a 3 app. Una volta approvate, queste app diventano accessibili tramite i provider di contenuti multimediali cloud su qualsiasi dispositivo GMS con Android su cui vengono installato.
Android gestisce un elenco lato server di tutti i cloud provider idonei. Ogni OEM puoi scegliere un cloud provider predefinito utilizzando un overlay configurabile. Candidatura Le app devono soddisfare tutti i requisiti tecnici e superare tutti i test di qualità. Per ulteriori informazioni di più sul processo del programma pilota per i fornitori di contenuti multimediali cloud OEM e requisiti, compila il modulo di richiesta.
Decidi se devi creare un cloud media provider
I provider di contenuti multimediali cloud sono app o servizi che agiscono come fonte principale per il backup e il recupero di foto e video dal cloud. Se la tua app dispone di una raccolta di contenuti utili, ma in genere non viene utilizzata come soluzione di archiviazione di foto, dovresti valutare la creazione di un fornitore di documenti .
Un cloud provider attivo per profilo
Può esserci al massimo un cloud media provider attivo alla volta per ogni Android profilo. Gli utenti possono rimuovere o modificare il provider di contenuti multimediali cloud selezionato dell'app in qualsiasi momento dalle impostazioni del selettore di foto.
Per impostazione predefinita, il selettore di foto Android tenterà di scegliere un provider cloud automaticamente.
- Se è presente un solo cloud provider idoneo sul dispositivo, quell'app verrà selezionato automaticamente come fornitore attuale.
Se sul dispositivo sono presenti più cloud provider idonei e uno dei corrispondono all'impostazione predefinita scelta dall'OEM, verrà selezionata l'app scelta dall'OEM.
Se sul dispositivo sono presenti più cloud provider idonei e nessuno di questi corrispondono all'OEM scelto come predefinito, quindi non verrà selezionata nessuna app.
Crea il tuo cloud media provider
Il seguente diagramma illustra la sequenza degli eventi prima e durante
una sessione di selezione di foto tra l'app per Android, il selettore di foto Android,
MediaProvider
del dispositivo locale e un CloudMediaProvider
.
- Il sistema inizializza il cloud provider preferito dall'utente e periodicamente sincronizza i metadati multimediali nel backend del selettore di foto Android.
- Quando un'app Android avvia il selettore di foto, prima di mostrare un'immagine locale unita o la griglia degli elementi cloud all'utente, il selettore di foto esegue un'operazione una sincronizzazione incrementale con il cloud provider per garantire che i risultati siano sempre aggiornati. il più possibile. Dopo aver ricevuto una risposta, o una volta raggiunta la scadenza, il La griglia del selettore di foto ora mostra tutte le foto accessibili, unendo quelle archiviate localmente sul tuo dispositivo con quelle sincronizzate dal cloud.
- Mentre l'utente scorre, il selettore di foto recupera le miniature dei contenuti multimediali dal da visualizzare nella UI.
- Quando l'utente completa la sessione e i risultati includono un file multimediale sul cloud elemento, il selettore di foto richiede i descrittori dei file per i contenuti, genera un URI e concede l'accesso al file all'applicazione chiamante.
- L'app è ora in grado di aprire l'URI e ha accesso di sola lettura ai contenuti multimediali contenuti. Per impostazione predefinita, i metadati sensibili sono oscurati. Selettore di foto utilizza il file system FUSE per coordinare lo scambio di dati tra l'app per Android e il fornitore di contenuti multimediali cloud.
Problemi comuni
Di seguito sono riportate alcune importanti considerazioni da tenere presenti quando si considera implementazione:
Evita i file duplicati
Poiché il selettore di foto Android non ha modo di esaminare
lo stato dei contenuti multimediali sul cloud,
CloudMediaProvider
deve fornire il MEDIA_STORE_URI
nel cursore
riga di qualsiasi file esistente sia nel cloud che sul dispositivo locale oppure la riga
l'utente vedrà i file duplicati nel selettore di foto.
Ottimizza le dimensioni delle immagini per la visualizzazione dell'anteprima
È molto importante che il file restituito da onOpenPreview
non sia la
di immagine con risoluzione ed è conforme alle Size
richieste. Immagine troppo grande
comporta tempi di caricamento nell'interfaccia utente e un'immagine troppo piccola potrebbe essere sgranata o
sfocato in base alle dimensioni dello schermo del dispositivo.
Gestisci l'orientamento corretto
Se le miniature restituite in onOpenPreview
non contengono i relativi dati EXIF,
devono essere restituiti con l'orientamento corretto per evitare che le miniature vengano ruotate
in modo errato nella griglia di anteprima.
Impedire l'accesso non autorizzato
Controlla MANAGE_CLOUD_MEDIA_PROVIDERS_PERMISSION
prima di restituire i dati a
e il chiamante da ContentProvider. In questo modo le app non autorizzate
che accedono ai dati cloud.
La classe CloudMediaProvider
Ricavato da android.content.ContentProvider
, CloudMediaProvider
include metodi come quelli mostrati nell'esempio seguente:
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 classe CloudMediaProviderContract
Oltre alla classe di implementazione CloudMediaProvider
principale,
Il selettore di foto Android incorpora una classe CloudMediaProviderContract
.
Questa classe illustra l'interoperabilità tra il selettore di foto e il cloud
fornitore di media, che comprende aspetti quali MediaCollectionInfo
per
operazioni di sincronizzazione, Cursor
colonne previste e Bundle
extra.
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
Il metodo onGetMediaCollectionInfo()
viene utilizzato dal sistema operativo per
valutare la validità degli elementi multimediali cloud memorizzati nella cache e determinare le
la sincronizzazione con il cloud media provider. Considerato il rischio di frequenti
chiamate dal sistema operativo, onGetMediaCollectionInfo()
è considerata
critiche per le prestazioni; è fondamentale evitare operazioni a lunga esecuzione
che potrebbero influire negativamente sul rendimento. Il sistema operativo memorizza nella cache
risposte precedenti di questo metodo e le confronta con le risposte successive
per determinare le azioni appropriate.
Kotlin
abstract fun onGetMediaCollectionInfo(extras: Bundle): Bundle
Java
@NonNull
public abstract Bundle onGetMediaCollectionInfo(@NonNull Bundle extras);
Il bundle MediaCollectionInfo
restituito include le seguenti costanti:
suQueryMedia
Il metodo onQueryMedia()
viene utilizzato per compilare la griglia principale delle foto in
selettore di foto in diverse visualizzazioni. Queste chiamate potrebbero essere sensibili alla latenza
può essere chiamato come parte di una sincronizzazione proattiva in background o durante il selettore di foto
quando è richiesto uno stato di sincronizzazione completa o incrementale. Selettore di foto
l'interfaccia utente non attenderà a tempo indeterminato una risposta per mostrare i risultati e
potrebbero scadere queste richieste per l'interfaccia utente. Il cursore restituito
tenterà comunque di essere elaborato nel database del selettore di foto per il futuro
sessioni.
Questo metodo restituisce un Cursor
che rappresenta tutti gli elementi multimediali nell'elemento multimediale
raccolta facoltativamente filtrata in base agli extra forniti e ordinata invertita
ordine cronologico di MediaColumns#DATE_TAKEN_MILLIS
(elementi più recenti
).
Il bundle CloudMediaProviderContract
restituito include quanto segue
costanti:
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
Il fornitore di contenuti multimediali deve impostare
CloudMediaProviderContract#EXTRA_MEDIA_COLLECTION_ID
nell'ambito della spesa restituita
Bundle
. La mancata impostazione è un errore e invalida il valore Cursor
restituito. Se
il provider di servizi multimediali cloud gestisce i filtri negli extra forniti, deve aggiungere
la chiave per ContentResolver#EXTRA_HONORED_ARGS
come parte dell'istruzione
Cursor#setExtras
.
onQuerydeletedMedia
Viene utilizzato il metodo onQueryDeletedMedia()
per garantire che gli elementi eliminati nel
l'account cloud vengano rimossi correttamente dall'interfaccia utente del selettore di foto. A causa di
la loro potenziale sensibilità alla latenza, queste chiamate potrebbero essere avviate come parte di:
- Sincronizzazione proattiva in background
- Sessioni del selettore di foto (quando è necessario uno stato di sincronizzazione completa o incrementale)
L'interfaccia utente del selettore di foto dà la priorità a un'esperienza utente reattiva e
non attenderà a tempo indeterminato una risposta. Per mantenere le interazioni senza problemi,
potrebbero verificarsi timeout. Gli elementi Cursor
restituiti tenteranno comunque di essere elaborati
nel database del selettore di foto per le sessioni future.
Questo metodo restituisce un Cursor
che rappresenta tutti gli elementi multimediali eliminati in
l'intera raccolta multimediale all'interno della versione attuale del provider, così come viene restituita
onGetMediaCollectionInfo()
. Questi articoli possono essere facoltativamente filtrati in base agli extra.
Il fornitore di contenuti multimediali deve impostare
CloudMediaProviderContract#EXTRA_MEDIA_COLLECTION_ID
nell'ambito della spesa restituita
Cursor#setExtras
Nessuna impostazione. Questo è un errore e invalida il Cursor
. Se
il provider gestisce eventuali filtri negli extra forniti, deve aggiungere la chiave
ContentResolver#EXTRA_HONORED_ARGS
.
onQueryAlbums
Il metodo onQueryAlbums()
viene utilizzato per recuperare un elenco di album Cloud che
disponibili nel cloud provider e nei metadati associati. Consulta
CloudMediaProviderContract.AlbumColumns
per ulteriori dettagli.
Questo metodo restituisce un Cursor
che rappresenta tutti gli elementi dell'album nei contenuti multimediali
raccolta facoltativamente filtrata in base agli extra forniti e ordinata invertita
ordine cronologico di AlbumColumns#DATE_TAKEN_MILLIS
, elementi più recenti
per prima cosa. Il fornitore di contenuti multimediali deve impostare
CloudMediaProviderContract#EXTRA_MEDIA_COLLECTION_ID
nell'ambito della spesa restituita
Cursor
. La mancata impostazione è un errore e invalida il valore Cursor
restituito. Se
il provider gestisce eventuali filtri negli extra forniti, deve aggiungere la chiave
ContentResolver#EXTRA_HONORED_ARGS
nell'ambito di Cursor
restituiti.
suOpenMedia
Il metodo onOpenMedia()
deve restituire i contenuti multimediali a grandezza originale identificati da
il mediaId
fornito. Se questo metodo blocca il download di contenuti nell'
dispositivo, devi controllare periodicamente il CancellationSignal
fornito per interrompere
richieste abbandonate.
suOpenAnteprima
Il metodo onOpenPreview()
dovrebbe restituire una miniatura del contenuto fornito
size
per l'elemento del mediaId fornito. La miniatura deve essere nel
originale CloudMediaProviderContract.MediaColumns#MIME_TYPE
e dovrebbe rispettare
avere una risoluzione molto più bassa rispetto a quella dell'articolo restituito da onOpenMedia
. Se questo metodo
viene bloccato durante il download di contenuti sul dispositivo, ti consigliamo di
controlla il valore CancellationSignal
fornito per interrompere le richieste abbandonate.
onCreateCloudMediaSurfaceController
Il metodo onCreateCloudMediaSurfaceController()
dovrebbe restituire un
CloudMediaSurfaceController
utilizzato per eseguire il rendering dell'anteprima degli elementi multimediali oppure
null
se il rendering dell'anteprima non è supportato.
L'CloudMediaSurfaceController
gestisce il rendering dell'anteprima degli elementi multimediali
su determinate istanze di Surface
. I metodi di questa classe sono pensati per
è asincrono e non deve bloccarlo eseguendo operazioni complicate. Un singolo
L'istanza CloudMediaSurfaceController
è responsabile del rendering di
elementi multimediali
associati a più piattaforme.
CloudMediaSurfaceController
supporta il seguente elenco di
del ciclo di vita:
onConfigChange
onDestroy
onMediaPause
onMediaPlay
onMediaSeekTo
onPlayerCreate
onPlayerRelease
onSurfaceChanged
onSurfaceCreated
onSurfaceDestroyed