ספק מדיה בענן מספק ל-Android תוכן נוסף של מדיה בענן
לבחירת תמונות. המשתמשים יכולים לבחור תמונות או סרטונים שמסופקים על ידי
ספק מדיה בענן כשאפליקציה משתמשת ב-ACTION_PICK_IMAGES
או
ACTION_GET_CONTENT
כדי לבקש קובצי מדיה מהמשתמש. מדיה בענן
הספק יכול גם לספק מידע על אלבומים, שניתן לעיין בו
הכלי לבחירת תמונות ב-Android.
לפני שמתחילים
לפני שמתחילים לבנות את הענן, כדאי לחשוב על הנושאים הבאים ספק מדיה.
מי רשאי להשתמש בפיצ'ר
ב-Android יש תוכנית פיילוט שמאפשרת להפוך אפליקציות שקשורות ל-OEM (יצרן ציוד מקורי) לענן ספקי מדיה שונים. רק אפליקציות שיצרני ציוד מקורי (OEM) זכאים להשתתף בהן התוכנית הזו להפוך לספק מדיה בענן ל-Android כרגע. כל אחד ה-OEM (יצרן הציוד המקורי) יכול להציע עד 3 אפליקציות. לאחר האישור, האפליקציות האלה יהיו נגישות בתור ספקי מדיה בענן בכל מכשיר מבוסס-Android של GMS שבו הם מותקנת.
ב-Android מוצגת רשימה בצד השרת של כל ספקי שירותי הענן שעומדים בדרישות. כל OEM אתם יכולים לבחור ספק ברירת מחדל של שירותי ענן באמצעות שכבת-על שניתן להגדיר. מועמד שהאפליקציות צריכות לעמוד בכל הדרישות הטכניות ולעבור את כל בדיקות האיכות. למידה מידע נוסף על תהליך תוכנית הפיילוט של ספק מדיה בענן ל-OEM (יצרן ציוד מקורי) הדרישות הרלוונטיות, מלאו את טופס הבקשה.
איך להחליט אם אתם צריכים ליצור ספק מדיה בענן
ספקי מדיה בענן מיועדים להיות אפליקציות או שירותים שפועלים כמשתמשים המקור העיקרי לגיבוי ולאחזור תמונות וסרטונים מהענן. אם לאפליקציה יש ספרייה של תוכן שימושי, אבל בדרך כלל היא לא משמשת פתרון לאחסון תמונות, כדאי ליצור ספק מסמכים במקום זאת.
ספק שירותי ענן פעיל אחד לכל פרופיל
יכול להיות לכל היותר ספק פעיל אחד של מדיה בענן בכל רגע נתון לכל Android פרופיל. משתמשים יכולים להסיר או לשנות את ספק המדיה בענן שנבחר בכל שלב דרך ההגדרות של בורר התמונות.
כברירת מחדל, הכלי לבחירת תמונות ב-Android ינסה לבחור ספק שירותי ענן באופן אוטומטי.
- אם יש במכשיר רק ספק שירותי ענן אחד שעומד בדרישות, האפליקציה הזו הספק הנוכחי נבחר באופן אוטומטי.
אם יש במכשיר יותר מספק אחד של שירותי ענן שעומד בדרישות, וגם הם תואמים לברירת המחדל שנבחרה על ידי ה-OEM, האפליקציה שנבחרה על ידי ה-OEM תיבחר.
אם יש במכשיר יותר מספק אחד של שירותי ענן שעומד בדרישות, ואף אחד הם תואמים לברירת המחדל של ה-OEM (יצרן הציוד המקורי), לא תיבחר אף אפליקציה.
יצירת ספק מדיה בענן
התרשים הבא מדגים את רצף האירועים גם לפני וגם במהלך
סשן בחירת תמונות בין האפליקציה ל-Android, בורר התמונות ב-Android,
MediaProvider
של המכשיר המקומי, וגם CloudMediaProvider
.
- המערכת מפעילה את ספק הענן המועדף על המשתמש ומדי פעם מסנכרן מטא-נתונים של מדיה עם הקצה העורפי של הכלי לבחירת תמונות ב-Android.
- כשאפליקציה ל-Android מפעילה את בורר התמונות, לפני הצגה של תמונה מקומית ממוזגת או רשת פריטים בענן למשתמש, הכלי לבחירת תמונות מבצע סנכרון מצטבר עם ספק שירותי הענן כדי להבטיח שהתוצאות עדכניות ככל האפשר. לאחר קבלת תשובה, או כשמגיע המועד האחרון, ברשת של הכלי לבחירת תמונות מוצגות עכשיו כל התמונות שאפשר לגשת אליהן, תוך שילוב של התמונות ששמורות באופן מקומי במכשיר שלך, עם אלו שסונכרנו מהענן.
- בזמן שהמשתמש גולל, הכלי לבחירת תמונות מאחזר תמונות ממוזערות של מדיה של ספק המדיה בענן להצגה בממשק המשתמש.
- כשהמשתמש משלים את הסשן, והתוצאות כוללות מדיה בענן הפריט, בוחר התמונות מבקש מתארי קבצים עבור התוכן, יוצר URI, ומעניק גישה לקובץ לאפליקציה שמפעילה את הקריאה.
- האפליקציה יכולה עכשיו לפתוח את ה-URI ויש לה גישת קריאה בלבד למדיה תוכן. כברירת מחדל, המטא-נתונים הרגישים מצונזרים. הכלי לבחירת תמונות משתמשת במערכת הקבצים של FUSE כדי לתאם את חילופי הנתונים בין אפליקציית Android וספק המדיה בענן.
בעיות נפוצות
הנה כמה שיקולים חשובים שכדאי לזכור כשמביאים בחשבון הטמעה:
נמנעים מכפילויות של קבצים
כי בכלי לבחירת תמונות ב-Android אין דרך לבדוק את מצב המדיה בענן,
הפונקציה CloudMediaProvider
צריכה לספק את הערך MEDIA_STORE_URI
בסמן
של כל קובץ שקיים גם בענן וגם במכשיר המקומי,
המשתמש יראה קבצים כפולים בכלי לבחירת תמונות.
אופטימיזציה של גודל התמונה לתצוגה המקדימה
חשוב מאוד שהקובץ שמוחזר מ-onOpenPreview
אינו מלא
ברזולוציה הזו, שעומדת בדרישות של Size
. התמונה גדולה מדי
יהיו זמני טעינה בממשק המשתמש, והתמונה קטנה מדי עלולה להיות מפוקסלת
מטושטשות בהתאם לגודל המסך של המכשיר.
נקודת אחיזה בכיוון הנכון
אם התמונות הממוזערות שמוחזרות בonOpenPreview
לא מכילות את נתוני ה-EXIF שלהן,
צריך להחזיר את התמונה בכיוון הנכון כדי למנוע סיבוב של התמונות הממוזערות.
בתצוגת התצוגה המקדימה.
מניעת גישה לא מורשית
כדאי לבדוק את MANAGE_CLOUD_MEDIA_PROVIDERS_PERMISSION
לפני החזרת הנתונים אל
מבצע הקריאה החוזרת מה-ContentProvider. הפעולה הזו תמנע מאפליקציות לא מורשות
גישה לנתוני ענן.
המחלקה CloudMediaProvider
נגזר מ-android.content.ContentProvider
, CloudMediaProvider
class כולל שיטות כמו אלה שמוצגות בדוגמה הבאה:
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
בנוסף לסיווג ההטמעה הראשי CloudMediaProvider
,
הכלי לבחירת תמונות של Android משלב כיתה ב-CloudMediaProviderContract
.
בשיעור הזה מתוארת יכולת הפעולה ההדדית בין הכלי לבחירת תמונות לבין הענן
ספק מדיה, שכולל היבטים כמו MediaCollectionInfo
עבור
פעולות סנכרון, Cursor
עמודות צפויות ו-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";
}
ב-GetMediaCollectionInfo
מערכת ההפעלה משתמשת בשיטה onGetMediaCollectionInfo()
כדי
להעריך את התוקף של פריטי המדיה בענן שנשמרו במטמון ולקבוע אם
סנכרון עם ספק המדיה בענן. בגלל פוטנציאל הדיווחים על תדירות
שיחות על ידי מערכת ההפעלה, onGetMediaCollectionInfo()
נחשב
קריטי לביצועים, חשוב להימנע מפעולות ממושכות או
השפעות שעלולות להשפיע לרעה על הביצועים. המטמון של מערכת ההפעלה
תשובות קודמות בשיטה הזו והשוואה שלהן לתגובות הבאות
כדי לקבוע את הפעולות המתאימות.
Kotlin
abstract fun onGetMediaCollectionInfo(extras: Bundle): Bundle
Java
@NonNull
public abstract Bundle onGetMediaCollectionInfo(@NonNull Bundle extras);
החבילה MediaCollectionInfo
שמוחזרת כוללת את הקבועים הבאים:
onQueryMedia
השיטה onQueryMedia()
משמשת לאכלוס תצוגת התמונות הראשית.
בחירה של תמונות במגוון תצוגות. השיחות האלה עשויות להיות רגישות לזמן אחזור,
יכולות להיות חלק מסנכרון יזום ברקע, או במהלך הכלי לבחירת תמונות
סשנים כאשר נדרש מצב סנכרון מלא או מצטבר. הכלי לבחירת תמונות
ממשק המשתמש לא ימתין ללא הגבלת זמן לתגובה להצגת תוצאות, וגם
עשוי לגרום לזמן הקצוב לבקשות האלה למטרות ממשק משתמש. הסמן שמוחזר
עדיין ינסה לעבד אותו במסד הנתונים של בוחר התמונות בעתיד
סשנים.
השיטה הזו מחזירה את הערך Cursor
שמייצג את כל פריטי המדיה במדיה
אופציונלי: סינון לפי התוספות שסופקו ומיון בסדר הפוך
סדר כרונולוגי של MediaColumns#DATE_TAKEN_MILLIS
(רוב הפריטים האחרונים
ראשונה).
החבילה CloudMediaProviderContract
שמוחזרת כוללת את הפריטים הבאים
קבועים:
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
ספק המדיה בענן צריך להגדיר
CloudMediaProviderContract#EXTRA_MEDIA_COLLECTION_ID
כחלק מההחזרה
Bundle
. אם לא מגדירים את הפעולה הזו, זו שגיאה ומבטלת את התוקף של Cursor
שהוחזר. אם המיקום
ספק המדיה בענן טיפל במסננים בכל התוספות שסופקו, הוא חייב להוסיף
את המפתח אל ContentResolver#EXTRA_HONORED_ARGS
כחלק
Cursor#setExtras
.
onQuerydeleteMedia
השיטה onQueryDeletedMedia()
משמשת כדי לוודא שפריטים נמחקו
יוסרו כמו שצריך מממשק המשתמש של הכלי לבחירת תמונות. בעקבות
את הרגישות הפוטנציאלית לזמן האחזור, יכול להיות שהקריאות האלה יופעלו כחלק מהגורמים הבאים:
- סנכרון יזום ברקע
- סשנים בכלי לבחירת תמונות (כשנדרש מצב סנכרון מלא או מצטבר)
לממשק המשתמש של הכלי לבחירת תמונות יש עדיפות לחוויית משתמש רספונסיבית,
לא יחכו לתגובה במשך זמן רב. כדי לשמור על אינטראקציות חלקות,
עשויים להיות זמנים קצובים לתפוגה. עדיין יתבצע ניסיון לעבד את כל Cursor
שהוחזר
למסד הנתונים של הכלי לבחירת תמונות בסשנים עתידיים.
השיטה הזו מחזירה את הערך Cursor
שמייצג את כל פריטי המדיה שנמחקו
את כל אוסף המדיה בגרסת הספק הנוכחית כפי שהוחזר על ידי
onGetMediaCollectionInfo()
. אפשר לסנן את הפריטים האלה לפי תוספות.
ספק המדיה בענן צריך להגדיר את
CloudMediaProviderContract#EXTRA_MEDIA_COLLECTION_ID
כחלק מההחזרה
Cursor#setExtras
אם לא מגדירים את האפשרות הזו, זו שגיאה ומבטלים את התוקף של Cursor
. אם המיקום
הספק טיפל במסננים בכל התוספות שסופקו, עליו להוסיף את המפתח כדי
ContentResolver#EXTRA_HONORED_ARGS
.
onQueryAlbums
השיטה onQueryAlbums()
משמשת לאחזור רשימה של אלבומי ענן
זמינים בספק שירותי הענן, ובמטא-נתונים שמשויכים אליהם. צפייה
CloudMediaProviderContract.AlbumColumns
אפשר למצוא פרטים נוספים.
השיטה הזו מחזירה את הערך Cursor
שמייצג את כל הפריטים באלבום במדיה
אופציונלי: סינון לפי התוספות שסופקו ומיון בסדר הפוך
סדר כרונולוגי של AlbumColumns#DATE_TAKEN_MILLIS
, הפריטים העדכניים ביותר
קודם. ספק המדיה בענן צריך להגדיר את
CloudMediaProviderContract#EXTRA_MEDIA_COLLECTION_ID
כחלק מההחזרה
Cursor
. אם לא מגדירים את הפעולה הזו, זו שגיאה ומבטלת את התוקף של Cursor
שהוחזר. אם המיקום
הספק טיפל במסננים בכל התוספות שסופקו, עליו להוסיף את המפתח כדי
ContentResolver#EXTRA_HONORED_ARGS
כחלק מה-Cursor
שהוחזר.
ב-OpenMedia
השיטה onOpenMedia()
אמורה להחזיר את המדיה בגודל מלא שזוהה על ידי
mediaId
שצוין. אם השיטה הזו חוסמת במהלך הורדת תוכן
במכשיר, עליך לבדוק מדי פעם את ה-CancellationSignal
שסופק כדי לבטל
בקשות שננטשו.
ב-OpenPreview
השיטה onOpenPreview()
אמורה להחזיר תמונה ממוזערת של
size
לפריט של מזהה המדיה שצוין. התמונה הממוזערת צריכה להיות
הגרסה המקורית של CloudMediaProviderContract.MediaColumns#MIME_TYPE
וצפויה
תהיה ברזולוציה נמוכה הרבה יותר מאשר הפריט שהוחזר על ידי onOpenMedia
. אם השיטה הזו
חסום בזמן הורדת תוכן למכשיר, רצוי מדי פעם
לבדוק את CancellationSignal
שסופקה כדי לבטל בקשות שננטשו.
onCreateCloudMediaSurfaceController
השיטה onCreateCloudMediaSurfaceController()
אמורה להחזיר
CloudMediaSurfaceController
משמש לעיבוד התצוגה המקדימה של פריטי מדיה, או
null
אם אין תמיכה ברינדור של תצוגה מקדימה.
השדה CloudMediaSurfaceController
מנהל את הרינדור של התצוגה המקדימה של פריטי מדיה
במופעים נתונים של Surface
. השיטות בכיתה הזו נועדו
אסינכרונית, ולא תיחסם על ידי ביצוע פעולה עמוסה. סינגל
מכונה אחת (CloudMediaSurfaceController
) אחראית לעיבוד של מספר מודעות
פריטי מדיה שמשויכים למספר פלטפורמות.
CloudMediaSurfaceController
תומך ברשימה הבאה של
קריאות חוזרות (callback) במחזור החיים:
onConfigChange
onDestroy
onMediaPause
onMediaPlay
onMediaSeekTo
onPlayerCreate
onPlayerRelease
onSurfaceChanged
onSurfaceCreated
onSurfaceDestroyed