איך נותנים גישה חלקית לתמונות ולסרטונים

ב-Android 14 נוספה האפשרות 'גישה לתמונות נבחרות', שמאפשרת למשתמשים להעניק לאפליקציות גישה לתמונות ולסרטונים ספציפיים בספרייה שלהם, במקום להעניק גישה לכל קובצי המדיה מסוג נתון.

השינוי הזה מופעל רק אם האפליקציה מטרגטת ל-Android 14 (רמת API 34) ומעלה. אם עדיין לא השתמשתם בבורר התמונות, מומלץ להטמיע אותו באפליקציה כדי לספק חוויה עקבית של בחירת תמונות וסרטונים, תוך שמירה על פרטיות המשתמשים, בלי שתצטרכו לבקש הרשאות אחסון.

אם אתם שומרים על בורר גלריה משלכם באמצעות הרשאות אחסון, ואתם צריכים לשמור על שליטה מלאה בהטמעה, עליכם לשנות את ההטמעה כך שתשתמש בהרשאה החדשה READ_MEDIA_VISUAL_USER_SELECTED. אם האפליקציה לא משתמשת בהרשאה החדשה, המערכת תפעיל אותה במצב תאימות.

גרסת יעד של SDK בוצעה הצהרה על READ_MEDIA_VISUAL_USER_SELECTED הגישה ל-Photos נבחרה והופעלה התנהגות UX
SDK 33 לא לא לא רלוונטי
כן כן האפליקציה קובעת את הרשאות הגישה
SDK 34 לא כן נשלט על ידי המערכת (התנהגות תואמת)
כן כן האפליקציה קובעת את הרשאות הגישה

כדי ליצור בורר גלריה משלכם, אתם צריכים לבצע פיתוח ותחזוקה נרחבים, והאפליקציה שלכם צריכה לבקש הרשאות אחסון כדי לקבל הסכמה מפורשת מהמשתמשים. המשתמשים יכולים לדחות את הבקשות האלה, או אם האפליקציה פועלת במכשיר עם Android 14 והאפליקציה מטרגטת את Android 14 (רמת API 34) ואילך, להגביל את הגישה למדיה שנבחרה. בתמונה הבאה מוצגת דוגמה לבקשת הרשאות ולבחירת מדיה באמצעות האפשרויות החדשות.

הסיומת
איור 1. בתיבת הדו-שיח החדשה, המשתמשים יכולים לבחור תמונות וסרטונים ספציפיים שהם רוצים להפוך לזמינים לאפליקציה, בנוסף לאפשרויות הרגילות של מתן גישה מלאה או דחיית כל הגישה.

בקטע הזה נדגים את הגישה המומלצת ליצירת בורר משלכם בגלריה באמצעות MediaStore. אם כבר יש לכם כלי לבחירת גלריה לאפליקציה, ואתם צריכים שליטה מלאה, תוכלו להשתמש בדוגמאות האלה כדי להתאים את ההטמעה. אם לא תעדכנו את ההטמעה כדי לטפל בגישה לתמונות נבחרות, המערכת מריצה את האפליקציה שלכם במצב תאימות.

בקשת הרשאות

קודם צריך לבקש את הרשאות האחסון המתאימות במניפסט של Android, בהתאם לגרסה של מערכת ההפעלה:

<!-- Devices running Android 12L (API level 32) or lower  -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />

<!-- Devices running Android 13 (API level 33) or higher -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />

<!-- To handle the reselection within the app on devices running Android 14
     or higher if your app targets Android 14 (API level 34) or higher.  -->
<uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />

לאחר מכן, מבקשים את הרשאות זמן הריצה המתאימות, בהתאם גם לגרסת מערכת ההפעלה:

// Register ActivityResult handler
val requestPermissions = registerForActivityResult(RequestMultiplePermissions()) { results ->
    // Handle permission requests results
    // See the permission example in the Android platform samples: https://github.com/android/platform-samples
}

// Permission request logic
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
    requestPermissions.launch(arrayOf(READ_MEDIA_IMAGES, READ_MEDIA_VIDEO, READ_MEDIA_VISUAL_USER_SELECTED))
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
    requestPermissions.launch(arrayOf(READ_MEDIA_IMAGES, READ_MEDIA_VIDEO))
} else {
    requestPermissions.launch(arrayOf(READ_EXTERNAL_STORAGE))
}

לאפליקציות מסוימות אין צורך בהרשאות

החל מגרסה 10 של Android‏ (רמה 29 של API), לאפליקציות כבר לא נדרשות הרשאות אחסון כדי להוסיף קבצים לאחסון המשותף. כלומר, אפליקציות יכולות להוסיף תמונות לגלריה, לצלם סרטונים ולשמור אותם באחסון המשותף או להוריד חשבוניות בפורמט PDF בלי לבקש הרשאות אחסון. אם האפליקציה שלכם רק מוסיפה קבצים לאחסון המשותף ולא שולחת שאילתות לגבי תמונות או סרטונים, כדאי להפסיק לבקש הרשאות אחסון ולהגדיר maxSdkVersion של API 28 ב-AndroidManifest.xml:

<!-- No permission is needed to add files to shared storage on Android 10 (API level 29) or higher  -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="28" />

טיפול בבחירה מחדש של מדיה

באמצעות התכונה 'גישה ל-Photos' ב-Android 14, האפליקציה צריכה לקבל את ההרשאה החדשה READ_MEDIA_VISUAL_USER_SELECTED כדי לשלוט בבחירה מחדש של מדיה, ולעדכן את ממשק האפליקציה כדי לאפשר למשתמשים להעניק לאפליקציה גישה לקבוצה שונה של תמונות וסרטונים. בתמונה הבאה מוצגת דוגמה לבקשת הרשאות ולבחירה מחדש של מדיה:

הסיומת
איור 2. בתיבת הדו-שיח החדשה, המשתמשים יכולים גם לבחור מחדש את התמונות והסרטונים שהם רוצים להעניק גישה אליהם לאפליקציה.

כשפותחים את תיבת הדו-שיח לבחירה, מוצגות תמונות, סרטונים או שניהם, בהתאם להרשאות שנדרשו. לדוגמה, אם מבקשים את ההרשאה READ_MEDIA_VIDEO בלי ההרשאה READ_MEDIA_IMAGES, רק סרטונים יופיעו בממשק המשתמש כדי שהמשתמשים יוכלו לבחור קבצים.

// Allow the user to select only videos
requestPermissions.launch(arrayOf(READ_MEDIA_VIDEO, READ_MEDIA_VISUAL_USER_SELECTED))

אתם יכולים לבדוק אם לאפליקציה יש גישה מלאה, חלקית או חסומה לספריית התמונות של המכשיר, ולעדכן את הממשק בהתאם. כדאי לבקש את ההרשאות האלה כשהאפליקציה צריכה גישה לאחסון, במקום בזמן ההפעלה. חשוב לזכור שאפשר לשנות את הרשאת ההענקה בין הקריאות החוזרות ל-lifecycle של האפליקציה onStart ו-onResume, כי המשתמש יכול לשנות את הגישה בהגדרות בלי לסגור את האפליקציה.

if (
    Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
    (
        ContextCompat.checkSelfPermission(context, READ_MEDIA_IMAGES) == PERMISSION_GRANTED ||
        ContextCompat.checkSelfPermission(context, READ_MEDIA_VIDEO) == PERMISSION_GRANTED
    )
) {
    // Full access on Android 13 (API level 33) or higher
} else if (
    Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE &&
    ContextCompat.checkSelfPermission(context, READ_MEDIA_VISUAL_USER_SELECTED) == PERMISSION_GRANTED
) {
    // Partial access on Android 14 (API level 34) or higher
}  else if (ContextCompat.checkSelfPermission(context, READ_EXTERNAL_STORAGE) == PERMISSION_GRANTED) {
    // Full access up to Android 12 (API level 32)
} else {
    // Access denied
}

שליחת שאילתה לספריית המכשירים

אחרי שתבדקו שיש לכם גישה להרשאות האחסון המתאימות, תוכלו לבצע פעולות ב-MediaStore כדי לשלוח שאילתה לספרייה של המכשיר (הגישה יכולה להיות חלקית או מלאה, והגישה עובדת באותה דרך בשני המקרים):

data class Media(
    val uri: Uri,
    val name: String,
    val size: Long,
    val mimeType: String,
)

// Run the querying logic in a coroutine outside of the main thread to keep the app responsive.
// Keep in mind that this code snippet is querying only images of the shared storage.
suspend fun getImages(contentResolver: ContentResolver): List<Media> = withContext(Dispatchers.IO) {
    val projection = arrayOf(
        Images.Media._ID,
        Images.Media.DISPLAY_NAME,
        Images.Media.SIZE,
        Images.Media.MIME_TYPE,
    )

    val collectionUri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        // Query all the device storage volumes instead of the primary only
        Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL)
    } else {
        Images.Media.EXTERNAL_CONTENT_URI
    }

    val images = mutableListOf<Media>()

    contentResolver.query(
        collectionUri,
        projection,
        null,
        null,
        "${Images.Media.DATE_ADDED} DESC"
    )?.use { cursor ->
        val idColumn = cursor.getColumnIndexOrThrow(Images.Media._ID)
        val displayNameColumn = cursor.getColumnIndexOrThrow(Images.Media.DISPLAY_NAME)
        val sizeColumn = cursor.getColumnIndexOrThrow(Images.Media.SIZE)
        val mimeTypeColumn = cursor.getColumnIndexOrThrow(Images.Media.MIME_TYPE)

        while (cursor.moveToNext()) {
            val uri = ContentUris.withAppendedId(collectionUri, cursor.getLong(idColumn))
            val name = cursor.getString(displayNameColumn)
            val size = cursor.getLong(sizeColumn)
            val mimeType = cursor.getString(mimeTypeColumn)

            val image = Media(uri, name, size, mimeType)
            images.add(image)
        }
    }

    return@withContext images
}

קטע הקוד הזה פשוט יותר כדי להמחיש איך מתקשרים עם MediaStore. באפליקציה שמיועדת לסביבת הייצור, כדאי להשתמש בפונקציית חלוקה לדפים באמצעות ספריית חלוקה לדפים כדי לשפר את הביצועים.

שליחת שאילתה לגבי הבחירה האחרונה

אפליקציות בגרסאות Android 15 ואילך ובגרסה Android 14 עם תמיכה בעדכוני מערכת של Google Play יכולות לבצע שאילתה לגבי התמונות והסרטונים האחרונים שהמשתמש בחר כשהיה לו גישה חלקית, על ידי הפעלת ההרשאה QUERY_ARG_LATEST_SELECTION_ONLY:

if (getExtensionVersion(Build.VERSION_CODES.U) >= 12) {
    val queryArgs = bundleOf(
        QUERY_ARG_SQL_SORT_ORDER to "${Images.Media.DATE_ADDED} DESC"
        QUERY_ARG_LATEST_SELECTION_ONLY to true
    )

    contentResolver.query(collectionUri, projection, queryArgs, null)
}

הגישה לתמונות ולסרטונים נשמרת כשמשדרגים את המכשיר

במקרים שבהם האפליקציה שלכם מותקנת במכשיר ששודרג מגרסה קודמת של Android ל-Android 14, למערכת נשארת גישה מלאה לתמונות ולסרטונים של המשתמש, והיא מעניקה לאפליקציה הרשאות מסוימות באופן אוטומטי. ההתנהגות המדויקת תלויה בקבוצת ההרשאות שהוקצו לאפליקציה לפני השדרוג של המכשיר ל-Android 14.

הרשאות מ-Android 13

כדאי להביא בחשבון את המצב הבא:

  1. האפליקציה מותקנת במכשיר עם Android 13.
  2. המשתמש העניק לאפליקציה את ההרשאה READ_MEDIA_IMAGES ואת ההרשאה READ_MEDIA_VIDEO.
  3. לאחר מכן, המכשיר משודרג ל-Android 14 בזמן שהאפליקציה עדיין מותקנת.
  4. האפליקציה שלכם תתחיל לטרגט ל-Android 14 (רמת API ‏34) ואילך.

במקרה כזה, לאפליקציה עדיין תהיה גישה מלאה לתמונות ולסרטונים של המשתמש. המערכת גם שומרת באופן אוטומטי את ההרשאות READ_MEDIA_IMAGES ו-READ_MEDIA_VIDEO שהוענקו לאפליקציה שלכם.

הרשאות מ-Android מגרסה 12 ומטה

נניח את המצב הבא:

  1. האפליקציה מותקנת במכשיר עם Android 13.
  2. המשתמש העניק לאפליקציה את ההרשאה READ_EXTERNAL_STORAGE או את ההרשאה WRITE_EXTERNAL_STORAGE.
  3. לאחר מכן, המכשיר משודרג ל-Android 14 בזמן שהאפליקציה עדיין מותקנת.
  4. האפליקציה שלכם תתחיל לטרגט ל-Android 14 (רמת API ‏34) ואילך.

במקרה כזה, לאפליקציה עדיין תהיה גישה מלאה לתמונות ולסרטונים של המשתמש. המערכת גם מעניקה לאפליקציה את ההרשאה READ_MEDIA_IMAGES ואת ההרשאה READ_MEDIA_VIDEO באופן אוטומטי.

שיטות מומלצות

הקטע הזה כולל כמה שיטות מומלצות לשימוש בהרשאה READ_MEDIA_VISUAL_USER_SELECTED. מידע נוסף זמין במאמר שיטות מומלצות לשימוש בהרשאות.

לא לשמור את מצב ההרשאה באופן סופי

לא לשמור את מצב ההרשאה באופן קבוע, כולל SharedPreferences או DataStore. יכול להיות שהמצב המאוחסן לא מסונכרן עם המצב בפועל. מצב ההרשאה יכול להשתנות אחרי איפוס ההרשאות, מצב תרדמת האפליקציה, שינוי שהמשתמש ביצע בהגדרות האפליקציה או כשהאפליקציה עוברת לרקע. במקום זאת, צריך לבדוק את הרשאות האחסון באמצעות ContextCompat.checkSelfPermission().

אל תניחו שיש לכם גישה מלאה לתמונות ולסרטונים

בעקבות השינויים שהוצגו ב-Android 14, יכול להיות שלאפליקציה תהיה גישה חלקית בלבד לספריית התמונות במכשיר. אם האפליקציה שומרת במטמון נתוני MediaStore כשנשלחת שאילתה באמצעות ContentResolver, יכול להיות שהמטמון לא עדכני.

  • תמיד צריך לשלוח שאילתה ל-MediaStore באמצעות ContentResolver, במקום להסתמך על מטמון שנשמר.
  • לשמור את התוצאות בזיכרון בזמן שהאפליקציה פועלת בחזית.
  • כדאי לרענן את התוצאות כשהאפליקציה עוברת את מחזור החיים של onResume, כי יכול להיות שהמשתמש ישנה את הגישה מלאה לגישה חלקית דרך הגדרות ההרשאות.

התייחסות לגישה ל-URI כזמנית

אם המשתמש יבחר באפשרות בחירת תמונות וסרטונים בתיבת הדו-שיח של הרשאות המערכת, תוקף הגישה של האפליקציה לתמונות ולסרטונים שנבחרו יפוג בסופו של דבר. האפליקציה צריכה תמיד לטפל במקרים של אין גישה ל-Uri כלשהו, בלי קשר לסמכות שלהם.

סינון סוג המדיה שניתן לבחור לפי הרשאה

תיבת הדו-שיח לבחירה מותאמת לסוג ההרשאה המבוקש:

  • אם מבקשים רק את READ_MEDIA_IMAGES, יוצגו רק תמונות שאפשר לבחור.
  • אם מבקשים רק את READ_MEDIA_VIDEO, אפשר לבחור רק סרטון.
  • אם מבקשים גם את READ_MEDIA_IMAGES וגם את READ_MEDIA_VIDEO, אפשר לבחור מכל ספריית התמונות.

בהתאם לתרחישי השימוש של האפליקציה, חשוב לבקש את ההרשאות המתאימות כדי למנוע חוויית משתמש גרועה. אם התכונה מצפה לבחירת סרטונים בלבד, חשוב לבקש רק את READ_MEDIA_VIDEO.

בקשת הרשאות בפעולה אחת

כדי למנוע מהמשתמשים לראות כמה תיבות דו-שיח של זמן ריצה של המערכת, צריך לבקש את ההרשאות READ_MEDIA_VISUAL_USER_SELECTED,‏ ACCESS_MEDIA_LOCATION ו'קריאת מדיה' (READ_MEDIA_IMAGES,‏ READ_MEDIA_VIDEO או את שתיהן) בפעולה אחת.

מאפשרים למשתמשים לנהל את הבחירה שלהם

כשהמשתמש בוחר במצב גישה חלקית, האפליקציה לא אמורה להניח שספריית התמונות של המכשיר ריקה, וצריכה לאפשר לו להעניק קבצים נוספים.

המשתמש יכול להחליט לעבור מגישה מלאה לגישה חלקית דרך הגדרות ההרשאות בלי להעניק גישה לחלק מקובצי המדיה החזותית.

מצב תאימות

אם יש לכם כלי לבחירת גלריה משלכם באמצעות הרשאות אחסון אבל לא התאמתם את האפליקציה לשימוש בהרשאה החדשה READ_MEDIA_VISUAL_USER_SELECTED, המערכת תפעיל את האפליקציה במצב תאימות בכל פעם שהמשתמשים צריכים לבחור מדיה או לבחור בה מחדש.

ההתנהגות במהלך בחירת המדיה הראשונית

במהלך הבחירה הראשונית, אם משתמש בוחר באפשרות 'בחירת תמונות וסרטונים' (איור 1), ההרשאות READ_MEDIA_IMAGES ו-READ_MEDIA_VIDEO ניתנות במהלך הסשן של האפליקציה, כדי לתת הרשאת הרשאה זמנית וגישה זמנית לתמונות ולסרטונים שהם בחרו. כשהאפליקציה עוברת לרקע או כשהמשתמש משבית אותה באופן פעיל, המערכת תסרב בסופו של דבר להעניק את ההרשאות האלה. ההתנהגות הזו זהה להרשאות חד-פעמיות אחרות.

ההתנהגות במהלך בחירת מדיה מחדש

אם האפליקציה תצטרך גישה לתמונות ולסרטונים נוספים בשלב מאוחר יותר, תצטרכו לבקש שוב את ההרשאה READ_MEDIA_IMAGES או את ההרשאה READ_MEDIA_VIDEO באופן ידני. המערכת פועלת לפי אותו תהליך כמו בבקשת ההרשאה הראשונית, ומבקשת מהמשתמשים לבחור תמונות וסרטונים (ראו איור 2).

אם האפליקציה פועלת בהתאם לשיטות המומלצות בנושא הרשאות, השינוי הזה לא אמור לגרום לתקלה באפליקציה, במיוחד אם האפליקציה לא מניחה שגישה ל-URI נשמרת, מאחסנת את מצב הרשאת המערכת או מעדכנת את קבוצת התמונות המוצגות לאחר שינוי ההרשאה. עם זאת, יכול להיות שההתנהגות הזו לא תהיה אידיאלית, בהתאם לתרחיש לדוגמה של האפליקציה שלכם. כדי לספק למשתמשים את חוויית השימוש הטובה ביותר, מומלץ להטמיע בורר תמונות או לשנות את בורר התמונות בגלריה של האפליקציה כדי לטפל בהתנהגות הזו ישירות באמצעות ההרשאה READ_MEDIA_VISUAL_USER_SELECTED.