Android için bulut medya sağlayıcısı oluşturma

Bir bulut medya sağlayıcısı, Android fotoğraf seçiciye ek bulut medya içeriği sağlar. Bir uygulama, kullanıcıdan medya dosyası istemek için ACTION_PICK_IMAGES veya ACTION_GET_CONTENT kullandığında kullanıcılar bulut medya sağlayıcısı tarafından sağlanan fotoğraf veya videoları seçebilir. Ayrıca bir bulut medya sağlayıcısı, Android fotoğraf seçicide göz atılabilecek albümler hakkında bilgi de verebilir.

Başlamadan önce

Bulut medya sağlayıcınızı oluşturmaya başlamadan önce aşağıdaki noktaları göz önünde bulundurun.

Uygunluk

Android, OEM tarafından atanan uygulamaların bulut medya sağlayıcısı olmasına izin vermek için bir pilot program yürütmektedir. Şu anda yalnızca OEM'ler tarafından aday gösterilen uygulamalar bu programa katılarak Android için bulut medya sağlayıcısı olmaya uygundur. Her OEM en fazla 3 uygulamayı aday gösterebilir. Bu uygulamalar onaylandıktan sonra, yüklendikleri GMS Android destekli cihazlarda bulut medya sağlayıcısı olarak erişilebilir hale gelir.

Android, uygun tüm bulut sağlayıcılarının sunucu tarafında bir listesini tutar. Her OEM, yapılandırılabilir bir yer paylaşımı kullanarak varsayılan bir bulut sağlayıcısı seçebilir. Belirlenen uygulamalar tüm teknik gereksinimleri karşılamalı ve tüm kalite testlerini geçmelidir. OEM bulut medya sağlayıcısı pilot programının süreci ve şartları hakkında daha fazla bilgi edinmek için sorgu formunu doldurun.

Bir bulut medya sağlayıcısı oluşturmanız gerekip gerekmediğine karar verin

Bulut medya sağlayıcıları, buluttaki fotoğraf ve videoları yedeklemek ve almak için kullanıcıların birincil kaynağı olarak işlev gören uygulamalar veya hizmetlerdir. Uygulamanızın yararlı içeriklerden oluşan bir kitaplığı varsa ancak genellikle fotoğraf depolama çözümü olarak kullanılmıyorsa bunun yerine bir doküman sağlayıcı oluşturmayı düşünmelisiniz.

Profil başına bir etkin bulut sağlayıcısı

Her Android profili için aynı anda en fazla bir etkin bulut medya sağlayıcısı olabilir. Kullanıcılar, seçtikleri bulut medya sağlayıcısı uygulamasını diledikleri zaman fotoğraf seçici ayarlarından kaldırabilir veya değiştirebilir.

Android fotoğraf seçici, varsayılan olarak bir bulut sağlayıcısını otomatik olarak seçmeye çalışır.

  • Cihazda yalnızca tek bir uygun bulut sağlayıcı varsa bu uygulama otomatik olarak geçerli sağlayıcı olarak seçilir.
  • Cihazda birden fazla uygun bulut sağlayıcısı varsa ve bu sağlayıcılardan biri, OEM'nin seçtiği varsayılan ayarla eşleşiyorsa OEM tarafından seçilen uygulama seçilir.

  • Cihazda birden fazla uygun bulut sağlayıcısı varsa ve bunlardan hiçbiri OEM'nin seçtiği varsayılan ayarla eşleşmiyorsa herhangi bir uygulama seçilmez.

Bulut medya sağlayıcınızı oluşturun

Aşağıdaki şemada, Android uygulaması, Android fotoğraf seçici, yerel cihazın MediaProvider ve CloudMediaProvider arasında fotoğraf seçimi oturumu öncesinde ve sırasında gerçekleşen etkinliklerin sırası gösterilmektedir.

Fotoğraf seçiciden bulut medya sağlayıcısına giden akışı gösteren sıra şeması
Şekil 1: Fotoğraf seçimi oturumundaki etkinlik sırası şeması.
  1. Sistem, kullanıcının tercih ettiği bulut sağlayıcıyı başlatır ve medya meta verilerini düzenli aralıklarla Android fotoğraf seçici arka ucuyla senkronize eder.
  2. Bir Android uygulaması fotoğraf seçiciyi başlattığında kullanıcıya birleştirilmiş bir yerel öğe veya bulut öğe ızgarası göstermeden önce fotoğraf seçici, sonuçların mümkün olduğunca güncel olmasını sağlamak için bulut sağlayıcıyla gecikmeye duyarlı artımlı senkronizasyon gerçekleştirir. Yanıt aldıktan veya son tarihe ulaşıldığında fotoğraf seçici ızgarasında artık erişilebilir tüm fotoğraflar görüntülenerek cihazınızda yerel olarak depolanan fotoğraflar, buluttan senkronize edilen fotoğraflarla birleştirilir.
  3. Kullanıcı ekranı kaydırırken fotoğraf seçici, kullanıcı arayüzünde görüntülenmek üzere bulut medya sağlayıcısından medya küçük resimleri getirir.
  4. Kullanıcı oturumu tamamladığında ve sonuçlarda bir bulut medya öğesi bulunduğunda fotoğraf seçici, içerik için dosya tanımlayıcıları ister, URI oluşturur ve çağrı yapan uygulamaya dosyaya erişim izni verir.
  5. Uygulama artık URI'yı açabiliyor ve medya içeriklerine salt okuma erişimi sağlayabiliyor. Hassas meta veriler varsayılan olarak çıkartılır. Fotoğraf seçici, Android uygulaması ile bulut medya sağlayıcısı arasındaki veri alışverişini koordine etmek için FUSE dosya sistemini kullanır.

Sık Karşılaşılan Sorunlar

Uygulamanızı değerlendirirken aklınızda bulundurmanız gereken bazı önemli noktalar şunlardır:

Yinelenen dosyalardan kaçının

Android fotoğraf seçici, bulut medya durumunu hiçbir şekilde inceleyemediği için CloudMediaProvider ürününün hem bulutta hem de yerel cihazda bulunan herhangi bir dosyanın imleç satırındaki MEDIA_STORE_URI değerini sağlaması gerekir. Aksi takdirde kullanıcı, fotoğraf seçicide yinelenen dosyalar görür.

Resim boyutlarını önizleme görünümü için optimize edin

onOpenPreview adresinden döndürülen dosyanın tam çözünürlüklü resim olmaması ve istenen Size şartlarına uygun olması çok önemlidir. Çok büyük boyutlu bir resim, kullanıcı arayüzünde yükleme sürelerine neden olur ve çok küçük bir resim, cihazın ekran boyutuna bağlı olarak pikselleştirilebilir veya bulanık olabilir.

Doğru yönü tutma

onOpenPreview içinde döndürülen küçük resimler kendi EXIF verilerini içermiyorsa, küçük resimlerin önizleme ızgarasında yanlış bir şekilde döndürülmesini önlemek için doğru yönde döndürülmelidirler.

Yetkisiz erişimi engelleyin

ContentProvider'dan arayana veri döndürmeden önce MANAGE_CLOUD_MEDIA_PROVIDERS_PERMISSION olup olmadığını kontrol edin. Bu, yetkisiz uygulamaların bulut verilerine erişmesini önler.

CloudMediaProvider sınıfı

android.content.ContentProvider değişkeninden türetilen CloudMediaProvider sınıfı, aşağıdaki örnekte gösterilenlere benzer yöntemler içerir:

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);
}

CloudMediaProviderContract sınıfı

Android fotoğraf seçici, birincil CloudMediaProvider uygulama sınıfına ek olarak bir CloudMediaProviderContract sınıfı içerir. Bu sınıfta fotoğraf seçici ile bulut medya sağlayıcısı arasındaki birlikte çalışabilirlik özetlenmektedir. Bu esnada senkronizasyon işlemleri için MediaCollectionInfo, öngörülen Cursor sütunları ve Bundle ekstra özellikler ele alınmaktadır.

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

onGetMediaCollectionInfo() yöntemi, önbelleğe alınan bulut medya öğelerinin geçerliliğini değerlendirmek ve bulut medya sağlayıcısı ile gerekli senkronizasyonu belirlemek için işletim sistemi tarafından kullanılır. İşletim sisteminin sıklıkla çağrı yapma potansiyeli nedeniyle onGetMediaCollectionInfo(), performans açısından kritik öneme sahiptir. Uzun süren işlemlerden veya performansı olumsuz yönde etkileyebilecek yan etkilerden kaçınmak çok önemlidir. İşletim sistemi, bu yöntemden gelen önceki yanıtları önbelleğe alır ve uygun işlemleri belirlemek için bunları sonraki yanıtlarla karşılaştırır.

Kotlin

abstract fun onGetMediaCollectionInfo(extras: Bundle): Bundle

Java

@NonNull
public abstract Bundle onGetMediaCollectionInfo(@NonNull Bundle extras);

Döndürülen MediaCollectionInfo paketi aşağıdaki sabit değerleri içerir:

Sorgu Medyası

onQueryMedia() yöntemi, fotoğraf seçicideki ana fotoğraf ızgarasını çeşitli görünümlerde doldurmak için kullanılır. Bu çağrılar gecikmeye duyarlı olabilir ve arka planda proaktif senkronizasyonun bir parçası olarak ya da tam ya da artımlı senkronizasyon durumu gerekli olduğunda fotoğraf seçici oturumları sırasında çağrılabilir. Fotoğraf seçici kullanıcı arayüzü, sonuçların görüntülenmesi için süresiz olarak yanıt beklemez ve kullanıcı arayüzü amacıyla bu istekleri zaman aşımına uğratabilir. Döndürülen imleç, gelecekteki oturumlar için fotoğraf seçicinin veritabanına işlenmeye devam edecektir.

Bu yöntem, medya koleksiyonundaki tüm medya öğelerini temsil eden ve isteğe bağlı olarak sağlanan ekstralara göre filtrelenen ve MediaColumns#DATE_TAKEN_MILLIS öğesinin ters kronolojik düzeninde (en son öğe öncelikli) sıralanan bir Cursor döndürür.

Döndürülen CloudMediaProviderContract paketi aşağıdaki sabit değerleri içerir:

Bulut medya sağlayıcısı, döndürülen Bundle öğesinin bir parçası olarak CloudMediaProviderContract#EXTRA_MEDIA_COLLECTION_ID değerini ayarlamalıdır. Bunun ayarlanmaması bir hatadır ve döndürülen Cursor geçersiz kılınır. Bulut medya sağlayıcısı, sağlanan ekstralarda herhangi bir filtre işlediyse döndürülen Cursor#setExtras kapsamında anahtarı ContentResolver#EXTRA_HONORED_ARGS öğesine eklemelidir.

onQuerySilinmişMedya

Bulut hesabındaki silinmiş öğelerin, fotoğraf seçici kullanıcı arayüzünden doğru şekilde kaldırılmasını sağlamak için onQueryDeletedMedia() yöntemi kullanılır. Potansiyel gecikme hassasiyetlerinden dolayı bu çağrılar aşağıdakilerin kapsamında başlatılabilir:

  • Arka planda proaktif senkronizasyon
  • Fotoğraf seçici oturumları (tam veya artımlı senkronizasyon durumu gerekli olduğunda)

Fotoğraf seçicinin kullanıcı arayüzü, duyarlı kullanıcı deneyimine öncelik verir ve yanıt almak için süresiz olarak beklemez. Etkileşimlerin sorunsuz şekilde yürütülebilmesi için zaman aşımları yaşanabilir. Döndürülen tüm Cursor verileri, gelecekteki oturumlar için fotoğraf seçicinin veritabanında işlenmeye çalışılır.

Bu yöntem, onGetMediaCollectionInfo() tarafından döndürülen ve geçerli sağlayıcı sürümündeki medya koleksiyonunun tamamında yer alan silinmiş tüm medya öğelerini temsil eden bir Cursor döndürür. Bu öğeler isteğe bağlı olarak ekstralara göre filtrelenebilir. Bulut medya sağlayıcısı, CloudMediaProviderContract#EXTRA_MEDIA_COLLECTION_ID öğesini döndürülen Cursor#setExtras politikasının bir parçası olarak ayarlamalıdır. Bu ayar yapılmaması bir hatadır ve Cursor geçersiz kılınır. Sağlayıcı, sağlanan ekstralarda herhangi bir filtre gerçekleştirdiyse anahtarı ContentResolver#EXTRA_HONORED_ARGS öğesine eklemelidir.

onQueryAlbümler

onQueryAlbums() yöntemi, bulut sağlayıcıda bulunan Cloud albümlerinin listesini ve bunlarla ilişkili meta verileri getirmek için kullanılır. Ayrıntılı bilgi için CloudMediaProviderContract.AlbumColumns sayfasını inceleyin.

Bu yöntem , medya koleksiyonundaki tüm albüm öğelerini temsil eden ve isteğe bağlı olarak sağlanan ekstralara göre filtrelenen ve en son AlbumColumns#DATE_TAKEN_MILLIS olan öğeler arasında ters kronolojik sıralamada sıralanmış bir Cursor döndürür. Bulut medya sağlayıcısı, döndürülen Cursor öğesinin parçası olarak CloudMediaProviderContract#EXTRA_MEDIA_COLLECTION_ID özelliğini ayarlamalıdır. Bunun ayarlanmaması bir hatadır ve döndürülen Cursor geçersiz kılınır. Sağlayıcı, sağlanan ekstralarda herhangi bir filtreyi ele aldıysa anahtarı döndürülen Cursor öğesinin parçası olarak ContentResolver#EXTRA_HONORED_ARGS öğesine eklemelidir.

onOpenMedia

onOpenMedia() yöntemi, sağlanan mediaId tarafından tanımlanan tam boyutlu medyayı döndürmelidir. Bu yöntem cihaza içerik indirirken engelleme yaparsa iptal edilen istekleri iptal etmek için sağlanan CancellationSignal öğesini düzenli olarak kontrol etmeniz gerekir.

onOpenPreview

onOpenPreview() yöntemi, sağlanan mediaId öğesi için sağlanan size öğesinin küçük resmini döndürmelidir. Küçük resim orijinal CloudMediaProviderContract.MediaColumns#MIME_TYPE boyutunda olmalı ve onOpenMedia tarafından döndürülen öğeden çok daha düşük çözünürlüklü olması beklenmektedir. Cihaza içerik indirirken bu yöntem engellenirse vazgeçilen istekleri iptal etmek için sağlanan CancellationSignal değerini düzenli olarak kontrol etmeniz gerekir.

onCreateCloudMediaSurfaceController

onCreateCloudMediaSurfaceController() yöntemi, medya öğelerinin önizlemesini oluşturmak için kullanılan bir CloudMediaSurfaceController veya önizleme oluşturma desteklenmiyorsa null döndürmelidir.

CloudMediaSurfaceController, belirtilen Surface örneklerinde medya öğelerinin önizlemesini oluşturmayı yönetir. Bu sınıfın yöntemlerinin eşzamansız olmaları amaçlanmıştır ve yoğun bir işlem gerçekleştirerek engelleme yapmamalıdır. Tek bir CloudMediaSurfaceController örneği, birden çok yüzeyle ilişkilendirilmiş birden çok medya öğesini oluşturmaktan sorumludur.

CloudMediaSurfaceController aşağıdaki yaşam döngüsü geri çağırma listesini destekler: