Cloud-Medienanbieter für Android erstellen

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.

<ph type="x-smartling-placeholder">
</ph> Sequenzdiagramm, das den Ablauf von der Bildauswahl zu einem Cloud-Medienanbieter zeigt
Abbildung 1: Ereignisabfolgediagramm während einer Fotoauswahlsitzung
  1. Das System initialisiert den bevorzugten Cloud-Anbieter des Nutzers und synchronisiert Medienmetadaten mit dem Android-Back-End für die Bildauswahl.
  2. 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.
  3. Während der Nutzer scrollt, ruft die Bildauswahl Medien-Thumbnails aus der Cloud-Medienanbieter, der in der UI angezeigt werden soll.
  4. 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.
  5. 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:

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: