Ein Cloud-Medienanbieter stellt zusätzliche Cloud-Medieninhalte für die Android-
Bildauswahl. Nutzer können Fotos oder Videos auswählen, die vom
Cloud-Medienanbieter, wenn eine Anwendung ACTION_PICK_IMAGES
oder
ACTION_GET_CONTENT
, um Mediendateien vom Nutzer anzufordern. Cloudmedien
Anbieter kann auch Informationen zu Alben bereitstellen, die Sie in der
Android-Bildauswahl
Hinweis
Berücksichtigen Sie die folgenden Punkte, bevor Sie mit dem Erstellen Ihrer Cloud beginnen Medienanbieter.
Voraussetzungen
Android führt ein Pilotprogramm durch, damit OEM-nominierte Apps in die Cloud umgewandelt werden können. Medienanbieter. Nur von OEMs nominierte Apps sind zur Teilnahme an um ein Cloud-Medienanbieter für Android zu werden. Jedes Der OEM kann bis zu drei Apps nominieren. Nach der Genehmigung sind diese Apps verfügbar als Cloud-Medienanbieter auf allen Android-GMD-Geräten, auf denen sie installiert haben.
Android pflegt eine serverseitige Liste aller berechtigten Cloud-Anbieter. Jeder OEM kann mithilfe eines konfigurierbaren Overlays einen Standard-Cloud-Anbieter auswählen. Nominiert Apps müssen alle technischen Anforderungen erfüllen und alle Qualitätstests bestehen. Weitere Informationen mehr über das Pilotprogramm für OEMs Cloud-Medienanbieter und füllen Sie das Antragsformular aus.
Entscheiden, ob Sie einen Cloud-Medienanbieter erstellen müssen
Cloud-Medienanbieter sind Apps oder Dienste, primäre Quelle zum Sichern und Abrufen von Fotos und Videos aus der Cloud Wenn Ihre App über eine Bibliothek nützlicher Inhalte verfügt, diese normalerweise aber nicht als Fotospeicherlösung sollten Sie einen Dokumentanbieter erstellen. .
Ein aktiver Cloud-Anbieter pro Profil
Für jeden Android-Server kann es jeweils nur einen aktiven Cloud-Medienanbieter geben Profil. Nutzer können ihren ausgewählten Cloud-Medienanbieter entfernen oder ändern App jederzeit über die Einstellungen für die Bildauswahl.
Standardmäßig versucht die Android-Bildauswahl, einen Cloud-Anbieter auszuwählen automatisch.
- Wenn es nur einen zulässigen Cloud-Anbieter auf dem Gerät gibt, wird diese App automatisch als aktueller Anbieter ausgewählt.
Wenn es auf dem Gerät mehr als einen geeigneten Cloud-Anbieter gibt und einer der mit der Standardeinstellung des OEMs übereinstimmt, wird die vom OEM ausgewählte App ausgewählt.
Wenn es auf dem Gerät mehr als einen geeigneten Cloud-Anbieter gibt und keiner der mit den Standardeinstellungen des OEMs übereinstimmen, wird keine App ausgewählt.
Cloud-Medienanbieter erstellen
Das folgende Diagramm veranschaulicht die Abfolge der Ereignisse vor und während
eine Fotoauswahlsitzung zwischen der Android-App, der Android-Bildauswahl, der
MediaProvider
auf dem lokalen Gerät und einen CloudMediaProvider
.
- Das System initialisiert den bevorzugten Cloud-Anbieter des Nutzers und synchronisiert Medienmetadaten mit dem Android-Back-End für die Bildauswahl.
- Wenn eine Android-App die Bildauswahl startet, bevor eine zusammengeführte lokale oder Wolkenelementraster hinzufügen, führt die Bildauswahl eine latenzempfindliche inkrementelle Synchronisierung mit dem Cloud-Anbieter, um sicherzustellen, dass die Ergebnisse auf dem neuesten Stand sind wie möglich. Nach Erhalt einer Antwort oder nach Ablauf der Frist Das Raster für die Bildauswahl zeigt jetzt alle verfügbaren Fotos an und fasst die gespeicherten lokal auf Ihrem Gerät mit denen aus der Cloud synchronisiert.
- Während der Nutzer scrollt, ruft die Bildauswahl Medien-Thumbnails aus der Cloud-Medienanbieter, der in der UI angezeigt werden soll.
- Wenn der Nutzer die Sitzung beendet und die Ergebnisse ein Cloud-Medienmedium enthalten fordert die Bildauswahl Dateideskriptoren für den Inhalt an, generiert ein URI und gewährt der aufrufenden Anwendung Zugriff auf die Datei.
- Die App kann jetzt den URI öffnen und hat Lesezugriff auf die Medien Inhalte. Sensible Metadaten werden standardmäßig entfernt. Bildauswahl nutzt das Dateisystem FUSE zur Koordinierung des Datenaustauschs zwischen den Android-App und Cloud-Medienanbieter
Häufige Fragen und Probleme
Hier sind einige wichtige Überlegungen, die Sie bei der Implementierung:
Doppelte Dateien vermeiden
Da es mit der Android-Bildauswahl nicht möglich ist, den Medienstatus der Cloud zu prüfen,
Der CloudMediaProvider
muss den MEDIA_STORE_URI
im Cursor enthalten.
Zeile für eine beliebige Datei, die sowohl in der Cloud als auch auf dem lokalen Gerät vorhanden ist, oder
werden in der Bildauswahl doppelte Dateien angezeigt.
Bildgrößen für die Vorschauanzeige optimieren
Es ist sehr wichtig, dass die von onOpenPreview
zurückgegebene Datei nicht die vollständige
Bildauflösung und entspricht der angeforderten Size
. Bild zu groß
führen zu Ladezeiten in der Benutzeroberfläche und ein zu kleines Bild kann verpixelt
je nach Bildschirmgröße des Geräts verschwommen ist.
Korrekte Ausrichtung behandeln
Wenn die in onOpenPreview
zurückgegebenen Miniaturansichten keine EXIF-Daten enthalten, werden sie
sollten in der richtigen Ausrichtung zurückgegeben werden, um zu verhindern, dass Miniaturansichten gedreht werden.
im Vorschauraster nicht korrekt angezeigt wird.
Unbefugten Zugriff verhindern
Überprüfen Sie den MANAGE_CLOUD_MEDIA_PROVIDERS_PERMISSION
, bevor Sie Daten zurückgeben an
den Aufrufer aus dem ContentProvider. So wird verhindert, dass nicht autorisierte Apps
auf Cloud-Daten zugreifen.
Klasse "CloudMediaProvider"
Abgeleitet von android.content.ContentProvider
, dem CloudMediaProvider
enthält Methoden wie die im folgenden Beispiel gezeigten:
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);
}
Klasse "CloudMediaProviderContract"
Zusätzlich zur primären Implementierungsklasse CloudMediaProvider
enthält der
Die Android-Bildauswahl enthält eine CloudMediaProviderContract
-Klasse.
In diesem Kurs wird die Interoperabilität zwischen der Bildauswahl und der Cloud erläutert.
Medienanbieter, einschließlich Aspekten wie MediaCollectionInfo
für
Synchronisierungsvorgänge, erwartete Cursor
Spalten und Bundle
Extras.
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
Die Methode onGetMediaCollectionInfo()
wird vom Betriebssystem verwendet, um
die Gültigkeit der im Cache gespeicherten Cloud-Medienelemente bewerten und ermitteln,
mit dem Cloud-Medienanbieter synchronisieren. Da es häufig zu häufigen
vom Betriebssystem aufgerufen, wird onGetMediaCollectionInfo()
als
leistungskritisch; ist es wichtig, Vorgänge mit langer Ausführungszeit oder
die sich negativ auf die Leistung auswirken könnten. Das Betriebssystem speichert
vorherigen Antworten dieser Methode und vergleicht sie mit nachfolgenden Antworten
um geeignete Maßnahmen zu ermitteln.
Kotlin
abstract fun onGetMediaCollectionInfo(extras: Bundle): Bundle
Java
@NonNull
public abstract Bundle onGetMediaCollectionInfo(@NonNull Bundle extras);
Das zurückgegebene MediaCollectionInfo
-Bundle enthält die folgenden Konstanten:
onQueryMedia
Mit der Methode onQueryMedia()
wird das Hauptfotoraster in
Fotoauswahl in verschiedenen Ansichten öffnen. Diese Aufrufe können latenzempfindlich sein
kann im Rahmen einer proaktiven Hintergrundsynchronisierung oder während der Bildauswahl aufgerufen werden
Sitzungen, bei denen eine vollständige oder inkrementelle Synchronisierung erforderlich ist. Bildauswahl
Die Benutzeroberfläche wartet nicht unbegrenzt auf eine Antwort, um Ergebnisse anzuzeigen, und
kann es aufgrund der Benutzeroberfläche zu einer Zeitüberschreitung kommen. Den zurückgegebenen Cursor
versucht weiterhin, in die Datenbank der Bildauswahl aufgenommen zu werden.
Sitzungen.
Diese Methode gibt ein Cursor
zurück, das alle Medienelemente in den Medien darstellt.
Sammlung kann optional nach den bereitgestellten Extras gefiltert und umgekehrt sortiert werden
chronologische Reihenfolge der MediaColumns#DATE_TAKEN_MILLIS
(neueste Elemente)
.
Das zurückgegebene CloudMediaProviderContract
-Bundle enthält Folgendes
Konstanten:
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
Der Cloud-Medienanbieter muss
CloudMediaProviderContract#EXTRA_MEDIA_COLLECTION_ID
als Teil des zurückgegebenen
Bundle
. Wenn dies nicht festgelegt wird, handelt es sich um einen Fehler, der die zurückgegebene Cursor
ungültig macht. Wenn
hat der Cloud-Medienanbieter alle Filter in den bereitgestellten Extras bearbeitet, muss er
den Schlüssel für ContentResolver#EXTRA_HONORED_ARGS
als Teil der zurückgegebenen
Cursor#setExtras
.
onQueryDeletedMedia
Mit der Methode onQueryDeletedMedia()
wird sichergestellt, dass gelöschte Elemente im
Cloud-Konto korrekt aus der Benutzeroberfläche der Bildauswahl entfernt wurden. Aufgrund von
ihrer potenziellen Latenzempfindlichkeit können diese Aufrufe als Teil von:
- Proaktive Hintergrundsynchronisierung
- Bildauswahlsitzungen (wenn ein vollständiger oder inkrementeller Synchronisierungsstatus erforderlich ist)
Die Benutzeroberfläche der Bildauswahl priorisiert eine responsive Nutzererfahrung und
nicht unbegrenzt auf eine Antwort warten. Um reibungslose Interaktionen aufrechtzuerhalten,
kann es zu Zeitüberschreitungen kommen. Alle zurückgegebenen Cursor
werden weiterhin verarbeitet.
für zukünftige Sitzungen
in die Datenbank der Bildauswahl speichern.
Diese Methode gibt ein Cursor
zurück, das alle gelöschten Mediaelemente im
gesamte Mediensammlung innerhalb der aktuellen Anbieterversion, wie sie von
onGetMediaCollectionInfo()
. Diese Artikel können optional nach Extras gefiltert werden.
Der Cloud-Medienanbieter muss die
CloudMediaProviderContract#EXTRA_MEDIA_COLLECTION_ID
als Teil des zurückgegebenen
Cursor#setExtras
Wenn Sie dies nicht festlegen, handelt es sich um einen Fehler, der Cursor
ungültig macht. Wenn
ob der Anbieter alle Filter in den bereitgestellten Extras bearbeitet hat, muss der Schlüssel
ContentResolver#EXTRA_HONORED_ARGS
.
onQueryAlbums
Mit der Methode onQueryAlbums()
wird eine Liste von Cloud-Alben abgerufen,
die beim Cloud-Anbieter verfügbar sind, sowie in den zugehörigen Metadaten. Weitere Informationen finden Sie unter
CloudMediaProviderContract.AlbumColumns
.
Diese Methode gibt ein Cursor
zurück, das alle Albumelemente in den Medien darstellt.
Sammlung kann optional nach den bereitgestellten Extras gefiltert und umgekehrt sortiert werden
chronologische Reihenfolge der AlbumColumns#DATE_TAKEN_MILLIS
, neueste Elemente
. Der Cloud-Medienanbieter muss die
CloudMediaProviderContract#EXTRA_MEDIA_COLLECTION_ID
als Teil des zurückgegebenen
Cursor
. Wenn dies nicht festgelegt wird, handelt es sich um einen Fehler, der die zurückgegebene Cursor
ungültig macht. Wenn
ob der Anbieter alle Filter in den bereitgestellten Extras bearbeitet hat, muss der Schlüssel
das ContentResolver#EXTRA_HONORED_ARGS
als Teil des zurückgegebenen Cursor
.
onOpenMedia
Die Methode onOpenMedia()
sollte das Medium in voller Größe zurückgeben, das durch
Die angegebene mediaId
. Wenn diese Methode beim Herunterladen von Inhalten auf den
Gerät, solltest du regelmäßig die CancellationSignal
prüfen, um den Vorgang abzubrechen
oder abgebrochene Anfragen.
onOpenPreview
Die Methode onOpenPreview()
sollte eine Miniaturansicht des bereitgestellten
size
für das Element der angegebenen mediaId. Die Miniaturansicht sollte im
Original-CloudMediaProviderContract.MediaColumns#MIME_TYPE
und voraussichtlich
ist viel geringer als das von onOpenMedia
zurückgegebene Element. Wenn diese Methode
beim Herunterladen von Inhalten auf das Gerät blockiert wird, sollten Sie sich in regelmäßigen Abständen
Prüfen Sie die bereitgestellte CancellationSignal
, um abgebrochene Anfragen abzubrechen.
onCreateCloudMediaSurfaceController
Die Methode onCreateCloudMediaSurfaceController()
sollte eine
CloudMediaSurfaceController
, das zum Rendern der Vorschau von Medienelementen verwendet wird, oder
null
, wenn das Vorschau-Rendering nicht unterstützt wird.
Der CloudMediaSurfaceController
verwaltet das Rendern der Vorschau von Medienelementen
für bestimmte Instanzen von Surface
. Die Methoden dieser Klasse
asynchron und sollten nicht durch schwere Vorgänge blockiert werden. Eine einzelne
CloudMediaSurfaceController
-Instanz ist für das Rendern mehrerer
Medienelemente, die mit mehreren Oberflächen verknüpft sind.
Das CloudMediaSurfaceController
unterstützt die folgende Liste von
Lebenszyklus-Callbacks:
onConfigChange
onDestroy
onMediaPause
onMediaPlay
onMediaSeekTo
onPlayerCreate
onPlayerRelease
onSurfaceChanged
onSurfaceCreated
onSurfaceDestroyed