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

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

בורר התמונות

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

חנות מדיה

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

Kotlin

val projection = arrayOf(media-database-columns-to-retrieve)
val selection = sql-where-clause-with-placeholder-variables
val selectionArgs = values-of-placeholder-variables
val sortOrder = sql-order-by-clause

applicationContext.contentResolver.query(
    MediaStore.media-type.Media.EXTERNAL_CONTENT_URI,
    projection,
    selection,
    selectionArgs,
    sortOrder
)?.use { cursor ->
    while (cursor.moveToNext()) {
        // Use an ID column from the projection to get
        // a URI representing the media item itself.
    }
}

Java

String[] projection = new String[] {
        media-database-columns-to-retrieve
};
String selection = sql-where-clause-with-placeholder-variables;
String[] selectionArgs = new String[] {
        values-of-placeholder-variables
};
String sortOrder = sql-order-by-clause;

Cursor cursor = getApplicationContext().getContentResolver().query(
    MediaStore.media-type.Media.EXTERNAL_CONTENT_URI,
    projection,
    selection,
    selectionArgs,
    sortOrder
);

while (cursor.moveToNext()) {
    // Use an ID column from the projection to get
    // a URI representing the media item itself.
}

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

  • תמונות, כולל תצלומים וצילומי מסך, שמאוחסנות בספריות DCIM/ ו-Pictures/. המערכת מוסיפה את הקבצים האלה לטבלה MediaStore.Images.
  • סרטונים, שמאוחסנים בספריות DCIM/, Movies/ ו-Pictures/. המערכת מוסיפה את הקבצים האלה לטבלה MediaStore.Video.
  • קובצי אודיו, שמאוחסנים בספריות Alarms/, Audiobooks/, Music/, Notifications/, Podcasts/ ו-Ringtones/. בנוסף, המערכת מזהה פלייליסטים של אודיו שנמצאים בספריות Music/ או Movies/, וגם הקלטות קול שנמצאות בספרייה Recordings/. המערכת מוסיפה את הקבצים האלה לטבלה MediaStore.Audio. הספרייה Recordings/ לא זמינה ב-Android 11 (רמת API‏ 30) ובגרסאות נמוכות יותר.
  • קבצים שהורדו,שמאוחסנים בספרייה Download/. במכשירים עם Android 10 (API ברמה 29) ואילך, הקבצים האלה מאוחסנים בטבלה MediaStore.Downloads. הטבלה הזו לא זמינה ב-Android מגרסה 9 (רמת API‏ 28) ומטה.

חנות המדיה כוללת גם אוסף בשם MediaStore.Files. התוכן שלו תלוי אם האפליקציה משתמשת באחסון עם היקף מוגבל, שזמין באפליקציות שמיועדות ל-Android מגרסה 10 ואילך.

  • אם האחסון המוגבל מופעל, באוסף מוצגים רק התמונות, הסרטונים וקובצי האודיו שהאפליקציה יצרה. רוב המפתחים לא צריכים להשתמש ב-MediaStore.Files כדי להציג קובצי מדיה מאפליקציות אחרות, אבל אם יש לכם דרישה ספציפית לעשות זאת, אתם יכולים להצהיר על ההרשאה READ_EXTERNAL_STORAGE. עם זאת, מומלץ להשתמש בממשקי ה-API כדי לפתוח קבצים שהאפליקציה לא יצרה.MediaStore
  • אם האחסון המוגבל לא זמין או לא נמצא בשימוש, באוסף מוצגים כל סוגי קובצי המדיה.

בקשת ההרשאות הנדרשות

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

הרשאות אחסון

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

גישה לקובצי מדיה משלכם

במכשירים עם Android 10 ואילך, לא צריך הרשאות שקשורות לאחסון כדי לגשת לקובצי מדיה שהאפליקציה שלכם היא הבעלים שלהם ולשנות אותם, כולל קבצים באוסף MediaStore.Downloads. לדוגמה, אם אתם מפתחים אפליקציית מצלמה, אתם לא צריכים לבקש הרשאות שקשורות לאחסון כדי לגשת לתמונות שהאפליקציה מצלמת, כי האפליקציה שלכם היא הבעלים של התמונות שאתם כותבים למאגר המדיה.

גישה לקובצי מדיה של אפליקציות אחרות

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

כל עוד אפשר לראות קובץ באמצעות השאילתות MediaStore.Images, MediaStore.Video או MediaStore.Audio, אפשר לראות אותו גם באמצעות השאילתה MediaStore.Files.

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

<!-- Required only if your app needs to access images or photos
     that other apps created. -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />

<!-- Required only if your app needs to access videos
     that other apps created. -->
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />

<!-- Required only if your app needs to access audio files
     that other apps created. -->
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
                 android:maxSdkVersion="29" />

הרשאות נוספות שנדרשות לאפליקציות שפועלות במכשירים מדור קודם

אם האפליקציה שלכם מופעלת במכשיר עם Android בגרסה 9 ומטה, או אם ביטלתם באופן זמני את ההסכמה לשימוש באחסון בהיקף מוגבל באפליקציה, אתם צריכים לבקש את ההרשאה READ_EXTERNAL_STORAGE כדי לגשת לקובצי מדיה. אם רוצים לשנות קובצי מדיה, צריך לבקש גם את ההרשאה WRITE_EXTERNAL_STORAGE.

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

אם האפליקציה שלכם רוצה לגשת לקובץ בMediaStore.Downloadsאוסף שהאפליקציה לא יצרה, אתם צריכים להשתמש ב-Storage Access Framework (מסגרת לגישה לאחסון). מידע נוסף על השימוש ב-framework הזה זמין במאמר גישה למסמכים ולקבצים אחרים מאחסון משותף.

הרשאת גישה למיקום במדיה

אם האפליקציה שלך מטרגטת Android 10 (רמת API‏ 29) ומעלה, ועליה לאחזר מטא נתונים של EXIF לא מצונזרים מתמונות, צריך להצהיר על ההרשאה ACCESS_MEDIA_LOCATION בקובץ המניפסט של האפליקציה, ואז לבקש את ההרשאה הזו בזמן הריצה.

חיפוש עדכונים בחנות המדיה

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

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

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

שאילתה של אוסף מדיה

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

Kotlin

// Need the READ_EXTERNAL_STORAGE permission if accessing video files that your
// app didn't create.

// Container for information about each video.
data class Video(val uri: Uri,
    val name: String,
    val duration: Int,
    val size: Int
)
val videoList = mutableListOf<Video>()

val collection =
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        MediaStore.Video.Media.getContentUri(
            MediaStore.VOLUME_EXTERNAL
        )
    } else {
        MediaStore.Video.Media.EXTERNAL_CONTENT_URI
    }

val projection = arrayOf(
    MediaStore.Video.Media._ID,
    MediaStore.Video.Media.DISPLAY_NAME,
    MediaStore.Video.Media.DURATION,
    MediaStore.Video.Media.SIZE
)

// Show only videos that are at least 5 minutes in duration.
val selection = "${MediaStore.Video.Media.DURATION} >= ?"
val selectionArgs = arrayOf(
    TimeUnit.MILLISECONDS.convert(5, TimeUnit.MINUTES).toString()
)

// Display videos in alphabetical order based on their display name.
val sortOrder = "${MediaStore.Video.Media.DISPLAY_NAME} ASC"

val query = ContentResolver.query(
    collection,
    projection,
    selection,
    selectionArgs,
    sortOrder
)
query?.use { cursor ->
    // Cache column indices.
    val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID)
    val nameColumn =
            cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DISPLAY_NAME)
    val durationColumn =
            cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATION)
    val sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.SIZE)

    while (cursor.moveToNext()) {
        // Get values of columns for a given video.
        val id = cursor.getLong(idColumn)
        val name = cursor.getString(nameColumn)
        val duration = cursor.getInt(durationColumn)
        val size = cursor.getInt(sizeColumn)

        val contentUri: Uri = ContentUris.withAppendedId(
            MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
            id
        )

        // Stores column values and the contentUri in a local object
        // that represents the media file.
        videoList += Video(contentUri, name, duration, size)
    }
}

Java

// Need the READ_EXTERNAL_STORAGE permission if accessing video files that your
// app didn't create.

// Container for information about each video.
class Video {
    private final Uri uri;
    private final String name;
    private final int duration;
    private final int size;

    public Video(Uri uri, String name, int duration, int size) {
        this.uri = uri;
        this.name = name;
        this.duration = duration;
        this.size = size;
    }
}
List<Video> videoList = new ArrayList<Video>();

Uri collection;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
    collection = MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL);
} else {
    collection = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
}

String[] projection = new String[] {
    MediaStore.Video.Media._ID,
    MediaStore.Video.Media.DISPLAY_NAME,
    MediaStore.Video.Media.DURATION,
    MediaStore.Video.Media.SIZE
};
String selection = MediaStore.Video.Media.DURATION +
        " >= ?";
String[] selectionArgs = new String[] {
    String.valueOf(TimeUnit.MILLISECONDS.convert(5, TimeUnit.MINUTES));
};
String sortOrder = MediaStore.Video.Media.DISPLAY_NAME + " ASC";

try (Cursor cursor = getApplicationContext().getContentResolver().query(
    collection,
    projection,
    selection,
    selectionArgs,
    sortOrder
)) {
    // Cache column indices.
    int idColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID);
    int nameColumn =
            cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DISPLAY_NAME);
    int durationColumn =
            cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATION);
    int sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.SIZE);

    while (cursor.moveToNext()) {
        // Get values of columns for a given video.
        long id = cursor.getLong(idColumn);
        String name = cursor.getString(nameColumn);
        int duration = cursor.getInt(durationColumn);
        int size = cursor.getInt(sizeColumn);

        Uri contentUri = ContentUris.withAppendedId(
                MediaStore.Video.Media.EXTERNAL_CONTENT_URI, id);

        // Stores column values and the contentUri in a local object
        // that represents the media file.
        videoList.add(new Video(contentUri, name, duration, size));
    }
}

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

  • מפעילים את השיטה query() בשרשור worker.
  • כדאי לשמור במטמון את אינדקס העמודות כדי שלא תצטרכו להפעיל את הפונקציה getColumnIndexOrThrow() בכל פעם שאתם מעבדים שורה מתוצאת השאילתה.
  • מוסיפים את המזהה ל-URI של התוכן כמו בדוגמה הזו.
  • במכשירים עם Android מגרסה 10 ומעלה נדרשים שמות עמודות שמוגדרים ב-MediaStore API. אם ספרייה תלויה באפליקציה שלכם מצפה לשם עמודה שלא מוגדר ב-API, כמו "MimeType", צריך להשתמש ב-CursorWrapper כדי לתרגם באופן דינמי את שם העמודה בתהליך של האפליקציה.

טעינת תמונות ממוזערות של קבצים

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

כדי לטעון את התמונה הממוזערת של קובץ מדיה מסוים, משתמשים ב-loadThumbnail() ומעבירים את הגודל של התמונה הממוזערת שרוצים לטעון, כמו שמוצג בקטע הקוד הבא:

Kotlin

// Load thumbnail of a specific media item.
val thumbnail: Bitmap =
        applicationContext.contentResolver.loadThumbnail(
        content-uri, Size(640, 480), null)

Java

// Load thumbnail of a specific media item.
Bitmap thumbnail =
        getApplicationContext().getContentResolver().loadThumbnail(
        content-uri, new Size(640, 480), null);

פתיחת קובץ מדיה

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

תיאור קובץ

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

Kotlin

// Open a specific media item using ParcelFileDescriptor.
val resolver = applicationContext.contentResolver

// "rw" for read-and-write.
// "rwt" for truncating or overwriting existing file contents.
val readOnlyMode = "r"
resolver.openFileDescriptor(content-uri, readOnlyMode).use { pfd ->
    // Perform operations on "pfd".
}

Java

// Open a specific media item using ParcelFileDescriptor.
ContentResolver resolver = getApplicationContext()
        .getContentResolver();

// "rw" for read-and-write.
// "rwt" for truncating or overwriting existing file contents.
String readOnlyMode = "r";
try (ParcelFileDescriptor pfd =
        resolver.openFileDescriptor(content-uri, readOnlyMode)) {
    // Perform operations on "pfd".
} catch (IOException e) {
    e.printStackTrace();
}

העברה של קבצים בסטרימינג

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

Kotlin

// Open a specific media item using InputStream.
val resolver = applicationContext.contentResolver
resolver.openInputStream(content-uri).use { stream ->
    // Perform operations on "stream".
}

Java

// Open a specific media item using InputStream.
ContentResolver resolver = getApplicationContext()
        .getContentResolver();
try (InputStream stream = resolver.openInputStream(content-uri)) {
    // Perform operations on "stream".
}

נתיבי קבצים ישירים

כדי שהאפליקציה שלכם תפעל בצורה חלקה יותר עם ספריות מדיה של צד שלישי, ב-Android 11 (רמת API 30) ומעלה אפשר להשתמש בממשקי API אחרים מלבד MediaStore API כדי לגשת לקובצי מדיה מאחסון משותף. במקום זאת, אפשר לגשת לקובצי מדיה ישירות באמצעות אחד מהממשקי ה-API הבאים:

  • ‫API‏ File
  • ספריות מקוריות, כמו fopen()

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

אם האפליקציה מנסה לגשת לקובץ באמצעות File API ואין לה את ההרשאות הנדרשות, מתרחשת FileNotFoundException.

כדי לגשת לקבצים אחרים באחסון משותף במכשיר עם Android 10 (רמת API‏ 29), מומלץ להשבית באופן זמני את האחסון המוגבל על ידי הגדרת requestLegacyExternalStorage ל-true בקובץ המניפסט של האפליקציה. כדי לגשת לקובצי מדיה באמצעות שיטות קבצים מקוריות ב-Android 10, צריך גם לבקש את ההרשאה READ_EXTERNAL_STORAGE.

שיקולים כשניגשים לתוכן מדיה

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

נתונים בקובץ שמור

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

ביצועים

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

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

עמודה DATA

כשניגשים לקובץ מדיה קיים, אפשר להשתמש בערך של העמודה DATA בלוגיקה. הסיבה לכך היא שהערך הזה כולל נתיב קובץ תקין. עם זאת, אל תניחו שהקובץ תמיד זמין. צריך להיות מוכנים לטפל בשגיאות קלט/פלט (I/O) שמתרחשות בקבצים.

לעומת זאת, כדי ליצור או לעדכן קובץ מדיה, לא משתמשים בערך של העמודה DATA. במקום זאת, צריך להשתמש בערכים של העמודות DISPLAY_NAME ו-RELATIVE_PATH.

נפחי אחסון

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

חשוב לזכור את הכמויות הבאות:

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

אפשר למצוא נפחים אחרים באמצעות קריאה ל-MediaStore.getExternalVolumeNames():

Kotlin

val volumeNames: Set<String> = MediaStore.getExternalVolumeNames(context)
val firstVolumeName = volumeNames.iterator().next()

Java

Set<String> volumeNames = MediaStore.getExternalVolumeNames(context);
String firstVolumeName = volumeNames.iterator().next();

המיקום שבו צולמו התמונות או הסרטונים

חלק מהתמונות והסרטונים מכילים פרטי מיקום במטא-נתונים שלהם. פרטי המיקום מראים את המקום שבו צולמה התמונה או שבו צולם הסרטון.

הגישה לפרטי המיקום באפליקציה תלויה בסוג המידע שאתם רוצים לגשת אליו: מידע על תמונה או על סרטון.

תצלומים

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

  1. מבקשים את ההרשאה ACCESS_MEDIA_LOCATION במניפסט של האפליקציה.
  2. מהאובייקט MediaStore, מקבלים את הבייטים המדויקים של התמונה על ידי קריאה ל-setRequireOriginal() והעברת ה-URI של התמונה, כמו שמוצג בקטע הקוד הבא:

    Kotlin

    val photoUri: Uri = Uri.withAppendedPath(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            cursor.getString(idColumnIndex)
    )
    
    // Get location data using the Exifinterface library.
    // Exception occurs if ACCESS_MEDIA_LOCATION permission isn't granted.
    photoUri = MediaStore.setRequireOriginal(photoUri)
    contentResolver.openInputStream(photoUri)?.use { stream ->
        ExifInterface(stream).run {
            // If lat/long is null, fall back to the coordinates (0, 0).
            val latLong = latLong ?: doubleArrayOf(0.0, 0.0)
        }
    }

    Java

    Uri photoUri = Uri.withAppendedPath(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            cursor.getString(idColumnIndex));
    
    final double[] latLong;
    
    // Get location data using the Exifinterface library.
    // Exception occurs if ACCESS_MEDIA_LOCATION permission isn't granted.
    photoUri = MediaStore.setRequireOriginal(photoUri);
    InputStream stream = getContentResolver().openInputStream(photoUri);
    if (stream != null) {
        ExifInterface exifInterface = new ExifInterface(stream);
        double[] returnedLatLong = exifInterface.getLatLong();
    
        // If lat/long is null, fall back to the coordinates (0, 0).
        latLong = returnedLatLong != null ? returnedLatLong : new double[2];
    
        // Don't reuse the stream associated with
        // the instance of "ExifInterface".
        stream.close();
    } else {
        // Failed to load the stream, so return the coordinates (0, 0).
        latLong = new double[2];
    }

סרטונים

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

Kotlin

val retriever = MediaMetadataRetriever()
val context = applicationContext

// Find the videos that are stored on a device by querying the video collection.
val query = ContentResolver.query(
    collection,
    projection,
    selection,
    selectionArgs,
    sortOrder
)
query?.use { cursor ->
    val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID)
    while (cursor.moveToNext()) {
        val id = cursor.getLong(idColumn)
        val videoUri: Uri = ContentUris.withAppendedId(
            MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
            id
        )
        extractVideoLocationInfo(videoUri)
    }
}

private fun extractVideoLocationInfo(videoUri: Uri) {
    try {
        retriever.setDataSource(context, videoUri)
    } catch (e: RuntimeException) {
        Log.e(APP_TAG, "Cannot retrieve video file", e)
    }
    // Metadata uses a standardized format.
    val locationMetadata: String? =
            retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION)
}

Java

MediaMetadataRetriever retriever = new MediaMetadataRetriever();
Context context = getApplicationContext();

// Find the videos that are stored on a device by querying the video collection.
try (Cursor cursor = context.getContentResolver().query(
    collection,
    projection,
    selection,
    selectionArgs,
    sortOrder
)) {
    int idColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID);
    while (cursor.moveToNext()) {
        long id = cursor.getLong(idColumn);
        Uri videoUri = ContentUris.withAppendedId(
                MediaStore.Video.Media.EXTERNAL_CONTENT_URI, id);
        extractVideoLocationInfo(videoUri);
    }
}

private void extractVideoLocationInfo(Uri videoUri) {
    try {
        retriever.setDataSource(context, videoUri);
    } catch (RuntimeException e) {
        Log.e(APP_TAG, "Cannot retrieve video file", e);
    }
    // Metadata uses a standardized format.
    String locationMetadata = retriever.extractMetadata(
            MediaMetadataRetriever.METADATA_KEY_LOCATION);
}

שיתוף

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

כדי לשתף קובצי מדיה, צריך להשתמש ב-URI מסוג content://, כמו שמומלץ במדריך ליצירת ספק תוכן.

שיוך של קובצי מדיה לאפליקציה

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

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

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

הוספת פריט

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

Kotlin

// Add a specific media item.
val resolver = applicationContext.contentResolver

// Find all audio files on the primary external storage device.
val audioCollection =
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        MediaStore.Audio.Media.getContentUri(
            MediaStore.VOLUME_EXTERNAL_PRIMARY
        )
    } else {
        MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
    }

// Publish a new song.
val newSongDetails = ContentValues().apply {
    put(MediaStore.Audio.Media.DISPLAY_NAME, "My Song.mp3")
}

// Keep a handle to the new song's URI in case you need to modify it
// later.
val myFavoriteSongUri = resolver
        .insert(audioCollection, newSongDetails)

Java

// Add a specific media item.
ContentResolver resolver = getApplicationContext()
        .getContentResolver();

// Find all audio files on the primary external storage device.
Uri audioCollection;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
    audioCollection = MediaStore.Audio.Media
            .getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
} else {
    audioCollection = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}

// Publish a new song.
ContentValues newSongDetails = new ContentValues();
newSongDetails.put(MediaStore.Audio.Media.DISPLAY_NAME,
        "My Song.mp3");

// Keep a handle to the new song's URI in case you need to modify it
// later.
Uri myFavoriteSongUri = resolver
        .insert(audioCollection, newSongDetails);

החלפת סטטוס ההמתנה של קובצי מדיה

אם האפליקציה מבצעת פעולות שעלולות לקחת זמן, כמו כתיבה לקובצי מדיה, כדאי שתהיה לה גישה בלעדית לקובץ בזמן העיבוד. במכשירים עם Android מגרסה 10 ואילך, האפליקציה יכולה לקבל גישה בלעדית כזו על ידי הגדרת הערך של הדגל IS_PENDING ל-1. רק האפליקציה שלכם יכולה לראות את הקובץ עד שהיא משנה את הערך של IS_PENDING בחזרה ל-0.

קטע הקוד הבא מבוסס על קטע הקוד הקודם. בדוגמה הבאה אפשר לראות איך משתמשים בדגל IS_PENDING כשמאחסנים שיר ארוך בספרייה שמתאימה לאוסף MediaStore.Audio:

Kotlin

// Add a media item that other apps don't see until the item is
// fully written to the media store.
val resolver = applicationContext.contentResolver

// Find all audio files on the primary external storage device.
val audioCollection =
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        MediaStore.Audio.Media.getContentUri(
            MediaStore.VOLUME_EXTERNAL_PRIMARY
        )
    } else {
        MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
    }

val songDetails = ContentValues().apply {
    put(MediaStore.Audio.Media.DISPLAY_NAME, "My Workout Playlist.mp3")
    put(MediaStore.Audio.Media.IS_PENDING, 1)
}

val songContentUri = resolver.insert(audioCollection, songDetails)

// "w" for write.
resolver.openFileDescriptor(songContentUri, "w", null).use { pfd ->
    // Write data into the pending audio file.
}

// Now that you're finished, release the "pending" status and let other apps
// play the audio track.
songDetails.clear()
songDetails.put(MediaStore.Audio.Media.IS_PENDING, 0)
resolver.update(songContentUri, songDetails, null, null)

Java

// Add a media item that other apps don't see until the item is
// fully written to the media store.
ContentResolver resolver = getApplicationContext()
        .getContentResolver();

// Find all audio files on the primary external storage device.
Uri audioCollection;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
    audioCollection = MediaStore.Audio.Media
            .getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
} else {
    audioCollection = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}

ContentValues songDetails = new ContentValues();
songDetails.put(MediaStore.Audio.Media.DISPLAY_NAME,
        "My Workout Playlist.mp3");
songDetails.put(MediaStore.Audio.Media.IS_PENDING, 1);

Uri songContentUri = resolver
        .insert(audioCollection, songDetails);

// "w" for write.
try (ParcelFileDescriptor pfd =
        resolver.openFileDescriptor(songContentUri, "w", null)) {
    // Write data into the pending audio file.
}

// Now that you're finished, release the "pending" status and let other apps
// play the audio track.
songDetails.clear();
songDetails.put(MediaStore.Audio.Media.IS_PENDING, 0);
resolver.update(songContentUri, songDetails, null, null);

הוספת רמז למיקום הקובץ

כשאפליקציה מאחסנת מדיה במכשיר עם Android 10, כברירת מחדל המדיה מאורגנת לפי הסוג שלה. לדוגמה, כברירת מחדל, קובצי תמונות חדשים ממוקמים בספרייה Environment.DIRECTORY_PICTURES, שמתאימה לאוסף MediaStore.Images.

אם האפליקציה שלכם מודעת למיקום ספציפי שבו אפשר לאחסן קבצים, כמו אלבום תמונות בשם Pictures/MyVacationPictures, אתם יכולים להגדיר את MediaColumns.RELATIVE_PATH כדי לספק למערכת רמז לגבי המיקום שבו כדאי לאחסן את הקבצים החדשים שנכתבו.

עדכון פריט

כדי לעדכן קובץ מדיה שהאפליקציה שלכם היא הבעלים שלו, משתמשים בקוד שדומה לקוד הבא:

Kotlin

// Updates an existing media item.
val mediaId = // MediaStore.Audio.Media._ID of item to update.
val resolver = applicationContext.contentResolver

// When performing a single item update, prefer using the ID.
val selection = "${MediaStore.Audio.Media._ID} = ?"

// By using selection + args you protect against improper escaping of // values.
val selectionArgs = arrayOf(mediaId.toString())

// Update an existing song.
val updatedSongDetails = ContentValues().apply {
    put(MediaStore.Audio.Media.DISPLAY_NAME, "My Favorite Song.mp3")
}

// Use the individual song's URI to represent the collection that's
// updated.
val numSongsUpdated = resolver.update(
        myFavoriteSongUri,
        updatedSongDetails,
        selection,
        selectionArgs)

Java

// Updates an existing media item.
long mediaId = // MediaStore.Audio.Media._ID of item to update.
ContentResolver resolver = getApplicationContext()
        .getContentResolver();

// When performing a single item update, prefer using the ID.
String selection = MediaStore.Audio.Media._ID + " = ?";

// By using selection + args you protect against improper escaping of
// values. Here, "song" is an in-memory object that caches the song's
// information.
String[] selectionArgs = new String[] { getId().toString() };

// Update an existing song.
ContentValues updatedSongDetails = new ContentValues();
updatedSongDetails.put(MediaStore.Audio.Media.DISPLAY_NAME,
        "My Favorite Song.mp3");

// Use the individual song's URI to represent the collection that's
// updated.
int numSongsUpdated = resolver.update(
        myFavoriteSongUri,
        updatedSongDetails,
        selection,
        selectionArgs);

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

עדכון בקוד מקורי

אם אתם צריכים לכתוב קובצי מדיה באמצעות ספריות מקומיות, צריך להעביר את מתאר הקובץ המשויך מקוד מבוסס Java או מקוד מבוסס Kotlin אל הקוד המקומי.

קטע הקוד הבא מראה איך להעביר את מתאר הקובץ של אובייקט מדיה לקוד המקורי של האפליקציה:

Kotlin

val contentUri: Uri = ContentUris.withAppendedId(
        MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
        cursor.getLong(BaseColumns._ID))
val fileOpenMode = "r"
val parcelFd = resolver.openFileDescriptor(contentUri, fileOpenMode)
val fd = parcelFd?.detachFd()
// Pass the integer value "fd" into your native code. Remember to call
// close(2) on the file descriptor when you're done using it.

Java

Uri contentUri = ContentUris.withAppendedId(
        MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
        cursor.getLong(Integer.parseInt(BaseColumns._ID)));
String fileOpenMode = "r";
ParcelFileDescriptor parcelFd =
        resolver.openFileDescriptor(contentUri, fileOpenMode);
if (parcelFd != null) {
    int fd = parcelFd.detachFd();
    // Pass the integer value "fd" into your native code. Remember to call
    // close(2) on the file descriptor when you're done using it.
}

עדכון קובצי מדיה של אפליקציות אחרות

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

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

Kotlin

// Apply a grayscale filter to the image at the given content URI.
try {
    // "w" for write.
    contentResolver.openFileDescriptor(image-content-uri, "w")?.use {
        setGrayscaleFilter(it)
    }
} catch (securityException: SecurityException) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        val recoverableSecurityException = securityException as?
            RecoverableSecurityException ?:
            throw RuntimeException(securityException.message, securityException)

        val intentSender =
            recoverableSecurityException.userAction.actionIntent.intentSender
        intentSender?.let {
            startIntentSenderForResult(intentSender, image-request-code,
                    null, 0, 0, 0, null)
        }
    } else {
        throw RuntimeException(securityException.message, securityException)
    }
}

Java

try {
    // "w" for write.
    ParcelFileDescriptor imageFd = getContentResolver()
            .openFileDescriptor(image-content-uri, "w");
    setGrayscaleFilter(imageFd);
} catch (SecurityException securityException) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        RecoverableSecurityException recoverableSecurityException;
        if (securityException instanceof RecoverableSecurityException) {
            recoverableSecurityException =
                    (RecoverableSecurityException)securityException;
        } else {
            throw new RuntimeException(
                    securityException.getMessage(), securityException);
        }
        IntentSender intentSender =recoverableSecurityException.getUserAction()
                .getActionIntent().getIntentSender();
        startIntentSenderForResult(intentSender, image-request-code,
                null, 0, 0, 0, null);
    } else {
        throw new RuntimeException(
                securityException.getMessage(), securityException);
    }
}

צריך להשלים את התהליך הזה בכל פעם שהאפליקציה צריכה לשנות קובץ מדיה שהיא לא יצרה.

לחלופין, אם האפליקציה שלכם פועלת ב-Android 11 ואילך, אתם יכולים לאפשר למשתמשים להעניק לאפליקציה שלכם גישת כתיבה לקבוצה של קובצי מדיה. משתמשים בשיטה createWriteRequest(), כמו שמתואר בקטע על ניהול קבוצות של קובצי מדיה.

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

הסרת פריט

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

Kotlin

// Remove a specific media item.
val resolver = applicationContext.contentResolver

// URI of the image to remove.
val imageUri = "..."

// WHERE clause.
val selection = "..."
val selectionArgs = "..."

// Perform the actual removal.
val numImagesRemoved = resolver.delete(
        imageUri,
        selection,
        selectionArgs)

Java

// Remove a specific media item.
ContentResolver resolver = getApplicationContext()
        getContentResolver();

// URI of the image to remove.
Uri imageUri = "...";

// WHERE clause.
String selection = "...";
String[] selectionArgs = "...";

// Perform the actual removal.
int numImagesRemoved = resolver.delete(
        imageUri,
        selection,
        selectionArgs);

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

אם האפליקציה שלכם פועלת ב-Android 11 ומעלה, אתם יכולים לאפשר למשתמשים לבחור קבוצה של קובצי מדיה להסרה. משתמשים בשיטה createTrashRequest() או בשיטה createDeleteRequest(), כמו שמתואר בקטע על ניהול קבוצות של קובצי מדיה.

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

זיהוי עדכונים לקובצי מדיה

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

בפרט, getGeneration() הוא אמין יותר מהתאריכים בעמודות המדיה, כמו DATE_ADDED ו-DATE_MODIFIED. הסיבה לכך היא שהערכים בעמודות המדיה האלה יכולים להשתנות כשמפעילים את הפונקציה setLastModified() באפליקציה או כשהמשתמש משנה את השעה במערכת.

ניהול קבוצות של קובצי מדיה

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

השיטות שמאפשרות את הפונקציונליות של 'עדכון קבוצתי' כוללות את האפשרויות הבאות:

createWriteRequest()
בקשה מהמשתמש להעניק לאפליקציה גישת כתיבה לקבוצה שצוינה של קובצי מדיה.
createFavoriteRequest()
בקשה מהמשתמש לסמן את קובצי המדיה שצוינו כחלק מקובצי המדיה ה'מועדפים' שלו במכשיר. כל אפליקציה שיש לה גישת קריאה לקובץ הזה יכולה לראות שהמשתמש סימן את הקובץ כ'מועדף'.
createTrashRequest()

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

createDeleteRequest()

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

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

לדוגמה, כך יוצרים מבנה של קריאה ל-createWriteRequest():

Kotlin

val urisToModify = /* A collection of content URIs to modify. */
val editPendingIntent = MediaStore.createWriteRequest(contentResolver,
        urisToModify)

// Launch a system prompt requesting user permission for the operation.
startIntentSenderForResult(editPendingIntent.intentSender, EDIT_REQUEST_CODE,
    null, 0, 0, 0)

Java

List<Uri> urisToModify = /* A collection of content URIs to modify. */
PendingIntent editPendingIntent = MediaStore.createWriteRequest(contentResolver,
                  urisToModify);

// Launch a system prompt requesting user permission for the operation.
startIntentSenderForResult(editPendingIntent.getIntentSender(),
    EDIT_REQUEST_CODE, null, 0, 0, 0);

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

Kotlin

override fun onActivityResult(requestCode: Int, resultCode: Int,
                 data: Intent?) {
    ...
    when (requestCode) {
        EDIT_REQUEST_CODE ->
            if (resultCode == Activity.RESULT_OK) {
                /* Edit request granted; proceed. */
            } else {
                /* Edit request not granted; explain to the user. */
            }
    }
}

Java

@Override
protected void onActivityResult(int requestCode, int resultCode,
                   @Nullable Intent data) {
    ...
    if (requestCode == EDIT_REQUEST_CODE) {
        if (resultCode == Activity.RESULT_OK) {
            /* Edit request granted; proceed. */
        } else {
            /* Edit request not granted; explain to the user. */
        }
    }
}

אפשר להשתמש באותו דפוס כללי עם createFavoriteRequest(),‏ createTrashRequest() ו-createDeleteRequest().

הרשאה לניהול מדיה

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

אם האפליקציה שלכם מטרגטת ל-Android 12 (רמת API 31) ומעלה, אתם יכולים לבקש מהמשתמשים להעניק לאפליקציה את ההרשאה המיוחדת ניהול מדיה. ההרשאה הזו מאפשרת לאפליקציה לבצע כל אחת מהפעולות הבאות בלי לבקש מהמשתמש אישור לכל פעולה על קובץ:

כדי לעשות זאת, פועלים לפי השלבים הבאים:

  1. צריך להצהיר על ההרשאה MANAGE_MEDIA ועל ההרשאה READ_EXTERNAL_STORAGE בקובץ המניפסט של האפליקציה.

    כדי להתקשר אל createWriteRequest() בלי להציג תיבת דו-שיח לאישור, צריך להצהיר גם על ההרשאה ACCESS_MEDIA_LOCATION.

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

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

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

אם האפליקציה שלכם מבצעת בעיקר אחד מהתפקידים הבאים, כדאי לשקול חלופה לממשקי ה-API של MediaStore.

עבודה עם סוגים אחרים של קבצים

אם האפליקציה שלכם פועלת עם מסמכים וקבצים שלא מכילים רק תוכן מדיה, כמו קבצים עם הסיומת EPUB או PDF, צריך להשתמש בפעולת intent‏ ACTION_OPEN_DOCUMENT, כמו שמתואר במדריך בנושא אחסון של מסמכים וקבצים אחרים וגישה אליהם.

שיתוף קבצים באפליקציות נלוות

במקרים שבהם אתם מספקים חבילה של אפליקציות נלוות, כמו אפליקציית הודעות ואפליקציית פרופיל, מגדירים שיתוף קבצים באמצעות מזהי URI של content://. אנחנו ממליצים על תהליך העבודה הזה גם כשיטה מומלצת לאבטחה.

מקורות מידע נוספים

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

טעימות

סרטונים