Penyedia media cloud menyediakan konten media cloud tambahan untuk perangkat
pemilih foto. Pengguna dapat memilih foto atau video yang disediakan oleh
penyedia media cloud saat aplikasi menggunakan ACTION_PICK_IMAGES
atau
ACTION_GET_CONTENT
untuk meminta file media dari pengguna. Media cloud
penyedia juga dapat memberikan informasi tentang album, yang dapat dijelajahi di
Pemilih foto Android.
Sebelum memulai
Pertimbangkan item berikut sebelum Anda mulai membangun solusi cloud penyedia media tersebut.
Kelayakan
Android menjalankan program uji coba untuk memungkinkan aplikasi yang dinominasikan OEM menjadi cloud penyedia media. Hanya aplikasi yang dinominasikan oleh OEM yang memenuhi syarat untuk berpartisipasi program ini untuk menjadi penyedia media cloud untuk Android pada saat ini. Masing-masing OEM dapat menominasikan hingga 3 aplikasi. Setelah disetujui, aplikasi ini dapat diakses sebagai penyedia media cloud di perangkat mana pun yang didukung Android GMS tempat mereka berada terinstal.
Android mengelola daftar sisi server dari semua penyedia cloud yang memenuhi syarat. Setiap OEM dapat memilih penyedia cloud default menggunakan overlay yang dapat dikonfigurasi. Dinominasikan aplikasi harus memenuhi semua persyaratan teknis dan lulus semua uji kualitas. Untuk mempelajari lebih lanjut tentang proses program uji coba penyedia media cloud OEM persyaratan, lengkapi formulir pertanyaan.
Menentukan apakah Anda perlu membuat penyedia media cloud
Penyedia media cloud dimaksudkan sebagai aplikasi atau layanan yang bertindak sebagai sumber utama untuk mencadangkan dan mengambil foto serta video dari {i>cloud<i}. Jika aplikasi Anda memiliki pustaka konten yang berguna, tetapi biasanya tidak digunakan sebagai solusi penyimpanan foto, sebaiknya pertimbangkan untuk membuat penyedia dokumen sebagai gantinya.
Satu penyedia cloud aktif per profil
Hanya boleh ada maksimal satu penyedia media cloud yang aktif pada satu waktu untuk setiap perangkat profil Anda. Pengguna mungkin menghapus atau mengubah penyedia media cloud yang mereka pilih kapan saja dari setelan pemilih foto.
Secara default, pemilih foto Android akan mencoba memilih penyedia cloud secara otomatis.
- Jika hanya ada satu penyedia {i>cloud <i} yang memenuhi syarat di perangkat, aplikasi tersebut akan dipilih sebagai penyedia saat ini secara otomatis.
Jika ada lebih dari satu penyedia {i>cloud <i} yang memenuhi syarat di perangkat dan salah satu cocok dengan default pilihan OEM, lalu aplikasi yang dipilih OEM akan dipilih.
Jika ada lebih dari satu penyedia {i>cloud <i} yang memenuhi syarat di perangkat, dan tidak ada cocok dengan OEM yang dipilih default, tidak ada aplikasi yang akan dipilih.
Membangun penyedia media cloud Anda
Diagram berikut mengilustrasikan urutan peristiwa baik sebelum dan selama
sesi pemilihan foto antara aplikasi Android, pemilih foto Android,
MediaProvider
perangkat lokal, dan CloudMediaProvider
.
- Sistem akan melakukan inisialisasi penyedia cloud pilihan pengguna dan secara berkala menyinkronkan metadata media ke backend pemilih foto Android.
- Saat aplikasi Android meluncurkan pemilih foto, sebelum menampilkan foto lokal yang digabungkan atau petak item cloud kepada pengguna, pemilih foto menjalankan fungsi yang sensitif sinkronisasi inkremental dengan penyedia cloud untuk memastikan hasilnya aktual mungkin. Setelah menerima respons, atau ketika batas waktu tercapai, petak pemilih foto kini menampilkan semua foto yang dapat diakses, yang menggabungkan foto yang disimpan secara lokal di perangkat Anda dengan data yang disinkronkan dari {i>cloud<i}.
- Selagi pengguna men-scroll, pemilih foto mengambil thumbnail media dari penyedia media cloud untuk ditampilkan di UI.
- Saat pengguna menyelesaikan sesi dan hasilnya mencakup media cloud item, pemilih foto akan meminta deskriptor file untuk konten, menghasilkan URI, dan memberikan akses ke file untuk aplikasi panggilan.
- Aplikasi kini dapat membuka URI dan memiliki akses hanya baca ke media konten. Secara default, metadata sensitif disamarkan. Pemilih foto memanfaatkan sistem file FUSE untuk mengkoordinasikan pertukaran data antara aplikasi Android dan penyedia media cloud.
Masalah Umum
Berikut ini beberapa pertimbangan penting yang harus diperhatikan saat mempertimbangkan penerapan:
Menghindari file duplikat
Karena pemilih foto Android tidak
dapat memeriksa status media cloud,
CloudMediaProvider
harus menyediakan MEDIA_STORE_URI
di kursor
baris file apa pun yang ada di {i>cloud<i} dan
di perangkat lokal, atau di
akan melihat file duplikat di pemilih foto.
Mengoptimalkan ukuran gambar untuk tampilan pratinjau
Sangat penting bahwa file yang ditampilkan dari onOpenPreview
tidak penuh
resolusi gambar, dan mematuhi Size
yang diminta. Gambar terlalu besar
akan menimbulkan waktu pemuatan di UI, dan terlalu kecil gambar mungkin akan mengalami pikselasi atau
buram berdasarkan ukuran layar perangkat.
Menangani orientasi yang benar
Jika thumbnail yang ditampilkan di onOpenPreview
tidak berisi data EXIF, thumbnail tersebut akan
harus dikembalikan dalam orientasi yang benar agar thumbnail tidak diputar
di petak pratinjau.
Mencegah akses yang tidak sah
Periksa MANAGE_CLOUD_MEDIA_PROVIDERS_PERMISSION
sebelum mengembalikan data ke
pemanggil dari ContentProvider. Tindakan ini akan mencegah aplikasi yang tidak diizinkan
yang mengakses data cloud.
Class CloudMediaProvider
Berasal dari android.content.ContentProvider
, CloudMediaProvider
menyertakan metode seperti yang ditunjukkan dalam contoh berikut:
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);
}
Class CloudMediaProviderContract
Selain class implementasi CloudMediaProvider
utama, class
Pemilih foto Android menggabungkan class CloudMediaProviderContract
.
Kelas ini menjelaskan interoperabilitas antara pemilih foto dan cloud
penyedia media, yang mencakup aspek seperti MediaCollectionInfo
untuk
operasi sinkronisasi, kolom Cursor
yang diharapkan, dan tambahan 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
Metode onGetMediaCollectionInfo()
digunakan oleh sistem operasi untuk
menilai validitas item media cloud yang di-cache dan menentukan
sinkronisasi dengan penyedia media {i>cloud<i}. Karena potensi terjadinya
oleh sistem operasi, onGetMediaCollectionInfo()
dianggap
performa yang penting; sangat penting untuk menghindari
operasi yang berjalan lama atau
yang dapat berdampak negatif terhadap performa. Sistem operasi menyimpan {i>cache<i}
respons sebelumnya dari metode ini dan membandingkannya dengan respons berikutnya
untuk menentukan tindakan yang tepat.
Kotlin
abstract fun onGetMediaCollectionInfo(extras: Bundle): Bundle
Java
@NonNull
public abstract Bundle onGetMediaCollectionInfo(@NonNull Bundle extras);
Paket MediaCollectionInfo
yang ditampilkan menyertakan konstanta berikut:
onQueryMedia
Metode onQueryMedia()
digunakan untuk mengisi petak foto utama di
pemilih foto dalam berbagai tampilan. Panggilan ini mungkin sensitif terhadap latensi, dan
dapat dipanggil sebagai bagian dari sinkronisasi proaktif latar belakang, atau selama pemilih foto
sesi saat status sinkronisasi penuh atau inkremental diperlukan. Pemilih foto
antarmuka pengguna tidak akan menunggu
waktu yang ditentukan untuk menampilkan hasil, dan
mungkin kehabisan waktu permintaan
ini untuk tujuan antarmuka pengguna. Kursor yang dikembalikan
masih akan mencoba diproses ke dalam database pemilih foto untuk masa mendatang
sesi.
Metode ini menampilkan Cursor
yang mewakili semua item media pada media
koleksi secara opsional difilter berdasarkan tambahan yang disediakan dan diurutkan secara terbalik
urutan kronologis MediaColumns#DATE_TAKEN_MILLIS
(item terbaru
terlebih dahulu).
Paket CloudMediaProviderContract
yang ditampilkan mencakup hal berikut
konstanta:
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
Penyedia media cloud harus menetapkan
CloudMediaProviderContract#EXTRA_MEDIA_COLLECTION_ID
sebagai bagian dari item baris yang ditampilkan
Bundle
. Tidak menetapkan nilai ini adalah error dan membuat Cursor
yang ditampilkan menjadi tidak valid. Jika
penyedia media {i>cloud <i}menangani semua filter
dalam tambahan yang disediakan, penyedia itu harus
kunci ke ContentResolver#EXTRA_HONORED_ARGS
sebagai bagian dari nilai yang ditampilkan
Cursor#setExtras
.
onQueryDeletedMedia
Metode onQueryDeletedMedia()
digunakan untuk memastikan item yang dihapus di
akun cloud dihapus dengan benar dari antarmuka pengguna pemilih foto. Karena
sensitivitas latensi potensialnya, panggilan ini dapat dimulai sebagai bagian dari:
- Sinkronisasi proaktif latar belakang
- Sesi pemilih foto (jika status sinkronisasi penuh atau inkremental diperlukan)
Antarmuka pengguna pemilih foto memprioritaskan
pengalaman pengguna yang responsif dan
tidak akan menunggu respons
untuk waktu yang tak terbatas. Untuk mempertahankan interaksi yang lancar,
mungkin terjadi waktu tunggu. Setiap Cursor
yang dikembalikan akan tetap berupaya diproses
ke dalam {i>database <i}pemilih foto untuk sesi-sesi selanjutnya.
Metode ini menampilkan Cursor
yang mewakili semua item media yang dihapus dalam
seluruh koleksi media dalam versi penyedia saat ini seperti yang ditampilkan oleh
onGetMediaCollectionInfo()
. Item ini dapat difilter secara opsional menurut tambahan.
Penyedia media cloud harus menetapkan
CloudMediaProviderContract#EXTRA_MEDIA_COLLECTION_ID
sebagai bagian dari item baris yang ditampilkan
Cursor#setExtras
Tidak menetapkan ini adalah error dan membuat Cursor
menjadi tidak valid. Jika
penyedia menangani setiap filter dalam tambahan
yang disediakan, penyedia itu harus menambahkan kunci
ContentResolver#EXTRA_HONORED_ARGS
.
onQueryAlbums
Metode onQueryAlbums()
digunakan untuk mengambil daftar album Cloud yang
tersedia di penyedia {i>cloud<i},
dan {i>metadata<i} yang terkait. Lihat
CloudMediaProviderContract.AlbumColumns
untuk detail tambahan.
Metode ini menampilkan Cursor
yang mewakili semua item album di media
koleksi secara opsional difilter berdasarkan tambahan yang disediakan dan diurutkan secara terbalik
urutan kronologis AlbumColumns#DATE_TAKEN_MILLIS
, item terbaru
terlebih dahulu. Penyedia media cloud harus menetapkan
CloudMediaProviderContract#EXTRA_MEDIA_COLLECTION_ID
sebagai bagian dari item baris yang ditampilkan
Cursor
. Tidak menetapkan nilai ini adalah error dan membuat Cursor
yang ditampilkan menjadi tidak valid. Jika
penyedia menangani setiap filter dalam tambahan
yang disediakan, penyedia itu harus menambahkan kunci
ContentResolver#EXTRA_HONORED_ARGS
sebagai bagian dari Cursor
yang ditampilkan.
onOpenMedia
Metode onOpenMedia()
harus menampilkan media ukuran penuh yang diidentifikasi oleh
mediaId
yang disediakan. Jika metode ini melakukan pemblokiran saat mendownload konten ke
perangkat Anda, secara berkala Anda harus memeriksa CancellationSignal
yang disediakan untuk membatalkan
permintaan yang diabaikan.
onOpenPreview
Metode onOpenPreview()
harus menampilkan thumbnail yang disediakan
size
untuk item mediaId yang disediakan. Thumbnail harus berada di
CloudMediaProviderContract.MediaColumns#MIME_TYPE
asli dan diharapkan
memiliki resolusi yang jauh lebih rendah daripada item yang ditampilkan oleh onOpenMedia
. Jika metode ini
diblokir saat mengunduh konten ke perangkat, Anda harus secara berkala
periksa CancellationSignal
yang disediakan untuk membatalkan permintaan yang diabaikan.
onCreateCloudMediaSurfaceController
Metode onCreateCloudMediaSurfaceController()
harus menampilkan
CloudMediaSurfaceController
digunakan untuk merender pratinjau item media, atau
null
jika rendering pratinjau tidak didukung.
CloudMediaSurfaceController
mengelola rendering pratinjau item media
pada instance Surface
tertentu. Metode class ini dimaksudkan untuk
asinkron, dan tidak boleh memblokir dengan melakukan operasi berat apa pun. Satu
Instance CloudMediaSurfaceController
bertanggung jawab untuk merender beberapa
item media yang terkait dengan beberapa platform.
CloudMediaSurfaceController
memiliki dukungan untuk daftar
callback siklus proses:
onConfigChange
onDestroy
onMediaPause
onMediaPlay
onMediaSeekTo
onPlayerCreate
onPlayerRelease
onSurfaceChanged
onSurfaceCreated
onSurfaceDestroyed