الوصول إلى ملفات الوسائط من مساحة التخزين المشتركة

لتقديم تجربة مستخدم أكثر إثراء، تتيح العديد من التطبيقات للمستخدمين المساهمة والوصول إلى الوسائط المتوفرة على وحدة تخزين خارجية. ويوفّر إطار العمل فهرسًا محسّنًا في مجموعات الوسائط يُطلق عليها اسم متجر الوسائط، ما يتيح للمستخدمين استرداد ملفات الوسائط هذه وتعديلها بسهولة أكبر. وحتى بعد إلغاء تثبيت تطبيقك، تظل هذه الملفات على جهاز المستخدم.

أداة اختيار الصور

بدلاً من استخدام متجر الوسائط، توفّر "أداة اختيار الصور" من 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 (المستوى 30 لواجهة برمجة التطبيقات) والإصدارات الأقدم.
  • الملفات التي تم تنزيلها، والتي يتم تخزينها في الدليل Download/. على الأجهزة التي تعمل بالإصدار 10 من نظام التشغيل Android 10 (المستوى 29 لواجهة برمجة التطبيقات) والإصدارات الأحدث، يتم تخزين هذه الملفات في جدول MediaStore.Downloads. لا يتوفّر هذا الجدول على نظام التشغيل Android 9 (المستوى 28 لواجهة برمجة التطبيقات) والإصدارات الأقدم.

يتضمن متجر الوسائط أيضًا مجموعة باسم MediaStore.Files. ويعتمد محتواها على ما إذا كان تطبيقك يستخدم مساحة تخزين محددة والمتاحة في التطبيقات التي تستهدف الإصدار 10 من نظام Android أو الإصدارات الأحدث.

  • إذا كانت ميزة "التخزين على نطاق واسع" مفعّلة، لا تعرض المجموعة سوى الصور والفيديوهات والملفات الصوتية التي أنشأها تطبيقك. ولا يحتاج معظم المطوّرين إلى استخدام MediaStore.Files لعرض ملفات الوسائط من تطبيقات أخرى، ولكن إذا كان لديك شرط معيّن لإجراء ذلك، يمكنك تقديم بيان عن الإذن READ_EXTERNAL_STORAGE. ومع ذلك، ننصحك باستخدام MediaStore واجهات برمجة التطبيقات لفتح الملفات التي لم ينشئها تطبيقك.
  • في حال عدم توفّر مساحة تخزين ذات نطاق واسع أو عدم استخدامها، ستعرض المجموعة جميع أنواع ملفات الوسائط.

طلب الأذونات اللازمة

قبل إجراء العمليات على ملفات الوسائط، تأكَّد من أنّ تطبيقك قد أعلن عن الأذونات التي يحتاجها للوصول إلى هذه الملفات. ومع ذلك، يُرجى عدم الإفصاح عن الأذونات التي لا يحتاجها تطبيقك أو لا يستخدمها.

أذونات مساحة التخزين

تعتمد حاجة التطبيق إلى أذونات الوصول إلى مساحة التخزين على ما إذا كان بإمكانه الوصول إلى ملفات الوسائط الخاصة به فقط أو الملفات التي أنشأتها تطبيقات أخرى.

الوصول إلى ملفات الوسائط

على الأجهزة التي تعمل بالإصدار 10 من نظام التشغيل Android أو إصدار أحدث، لا تحتاج إلى أذونات متعلّقة بمساحة التخزين للوصول إلى ملفات الوسائط التي يملكها تطبيقك وتعديلها، بما في ذلك الملفات في مجموعة 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" />

أذونات إضافية مطلوبة للتطبيقات التي تعمل على الأجهزة القديمة

إذا تم استخدام تطبيقك على جهاز يعمل بالإصدار 9 من نظام التشغيل Android أو بإصدار أقدم أو إذا تم إيقاف تطبيقك مؤقتًا مؤقتًا، يجب طلب إذن READ_EXTERNAL_STORAGE للوصول إلى أي ملف وسائط. إذا أردت تعديل ملفات الوسائط، عليك طلب إذن WRITE_EXTERNAL_STORAGE أيضًا.

يجب تفعيل "إطار عمل الوصول إلى مساحة التخزين" للوصول إلى المحتوى الذي تم تنزيله من التطبيقات الأخرى

إذا كان تطبيقك يريد الوصول إلى ملف ضمن مجموعة MediaStore.Downloads لم ينشئه تطبيقك، يجب استخدام "إطار عمل الوصول إلى مساحة التخزين". لمزيد من المعلومات حول كيفية استخدام إطار العمل هذا، يُرجى الاطّلاع على الوصول إلى المستندات والملفات الأخرى من مساحة التخزين المشتركة.

إذن تحديد الموقع الجغرافي للوسائط

إذا كان تطبيقك يستهدف نظام Android 10 (المستوى 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() في سلسلة محادثات عاملة.
  • يمكنك تخزين فهارس الأعمدة مؤقتًا بحيث لا تحتاج إلى استدعاء getColumnIndexOrThrow() في كل مرة تعالج فيها صفًا من نتيجة طلب البحث.
  • إلحاق المعرّف بمعرّف الموارد المنتظم (URI) للمحتوى كما هو موضح في هذا المثال.
  • تتطلّب الأجهزة التي تعمل بالإصدار 10 من نظام التشغيل Android أو الإصدارات الأحدث أسماء أعمدة معرَّفة في واجهة برمجة تطبيقات MediaStore. إذا كانت المكتبة التابعة داخل تطبيقك تتوقع اسم عمود غير محدّد في واجهة برمجة التطبيقات مثل "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 (المستوى 30 لواجهة برمجة التطبيقات) والإصدارات الأحدث استخدام واجهات برمجة تطبيقات أخرى غير واجهة برمجة التطبيقات MediaStore للوصول إلى ملفات الوسائط من مساحة التخزين المشتركة. يمكنك بدلاً من ذلك الوصول إلى ملفات الوسائط مباشرةً باستخدام أي من واجهات برمجة التطبيقات التالية:

  • واجهة برمجة التطبيقات File
  • المكتبات الأصلية، مثل fopen()

إذا لم يكن لديك أي أذونات متعلّقة بمساحة التخزين، يمكنك الوصول إلى الملفات في الدليل الخاص بالتطبيق بالإضافة إلى ملفات الوسائط المنسوبة إلى تطبيقك باستخدام واجهة برمجة التطبيقات File API.

إذا حاول تطبيقك الوصول إلى ملف باستخدام واجهة برمجة التطبيقات File ولم يكن لديه الأذونات اللازمة، يتم تنفيذ FileNotFoundException.

للوصول إلى الملفات الأخرى في مساحة التخزين المشتركة على جهاز يعمل بنظام التشغيل Android 10 (المستوى 29 من واجهة برمجة التطبيقات)، ننصحك بإيقاف مساحة التخزين على النطاق مؤقتًا من خلال ضبط requestLegacyExternalStorage على true في ملف بيان تطبيقك. للوصول إلى ملفات الوسائط باستخدام طُرق الملفات الأصلية على Android 10، عليك أيضًا طلب إذن READ_EXTERNAL_STORAGE.

الاعتبارات الواجب مراعاتها عند الوصول إلى محتوى الوسائط

عند الوصول إلى محتوى الوسائط، يُرجى مراعاة الاعتبارات التي تمت مناقشتها في الأقسام التالية.

البيانات المؤقتة

إذا كان تطبيقك يخزّن معرّفات الموارد المنتظمة (URI) أو البيانات من متجر الوسائط في ذاكرة التخزين المؤقت، ابحث بشكل دوري عن تحديثات متجر الوسائط. تتيح عملية التحقق هذه مزامنة البيانات المخزنة مؤقتًا من جانب التطبيق مع بيانات موفّر الخدمة من جانب النظام.

عروض أداء

عند تنفيذ عمليات قراءة متسلسلة لملفات الوسائط باستخدام مسارات الملفات المباشرة، يكون أداء هذه الملفات مشابهًا لأداء واجهة برمجة التطبيقات MediaStore.

ومع ذلك، عند إجراء عمليات قراءة وكتابة عشوائية لملفات الوسائط باستخدام مسارات الملفات المباشرة، يمكن أن تصل سرعة العملية إلى الضعف. في هذه الحالات، ننصحك باستخدام واجهة برمجة تطبيقات MediaStore بدلاً من ذلك.

عمود DATA

عند الوصول إلى ملف وسائط حالي، يمكنك استخدام قيمة العمود DATA وفقًا للمنطق. هذا لأن هذه القيمة لها مسار ملف صالح. ومع ذلك، لا تفترض أن الملف متاح دائمًا. كن مستعدًا للتعامل مع أي أخطاء تحدث في I/O قائمة على الملفات.

ولإنشاء ملف وسائط أو تعديله، لا تستخدِم قيمة العمود DATA. بدلاً من ذلك، استخدِم قيم عمودَي DISPLAY_NAME وRELATIVE_PATH.

أحجام التخزين

يمكن للتطبيقات التي تستهدف الإصدار 10 من نظام التشغيل Android أو الإصدارات الأحدث الوصول إلى الاسم الفريد الذي يعيّنه النظام لكل وحدة تخزين خارجية. يساعدك نظام التسمية هذا في تنظيم المحتوى وفهرسته بكفاءة، ويمنحك التحكم في مكان تخزين ملفات الوسائط الجديدة.

يُرجى أخذ الأجزاء التالية في الاعتبار على وجه التحديد:

  • يوفّر مستوى الصوت 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://، على النحو الموصى به في دليل إنشاء موفّر محتوى.

إحالة التطبيقات إلى ملفات الوسائط

عند تفعيل مساحة التخزين على نطاق واسع لتطبيق يستهدف الإصدار 10 من نظام التشغيل Android أو الإصدارات الأحدث، ينسب النظام تطبيقًا إلى كل ملف وسائط، ما يحدد الملفات التي يمكن لتطبيقك الوصول إليها عندما لا يطلب أي أذونات لمساحة التخزين. يمكن نسب كل ملف إلى تطبيق واحد فقط. لذلك، إذا أنشأ التطبيق ملف وسائط تم تخزينه في مجموعة وسائط الصور أو الفيديوهات أو الملفات الصوتية، يمكن للتطبيق الوصول إلى الملف.

ولكن إذا ألغى المستخدم تثبيت تطبيقك ثم أعاد تثبيته، عليك طلب READ_EXTERNAL_STORAGE للوصول إلى الملفات التي أنشأها تطبيقك في الأصل. إنّ طلب الإذن هذا مطلوب لأنّ النظام يعتبر أنّ الملف منسوب إلى الإصدار المثبَّت سابقًا من التطبيق، بدلاً من الإصدار المثبّت حديثًا.

إضافة سؤال

لإضافة عنصر وسائط إلى مجموعة حالية، يمكنك استخدام رمز مشابه لما يلي. يمكن لمقتطف الرمز هذا الوصول إلى مستوى صوت 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);

تبديل حالة "الانتظار" لملفات الوسائط

إذا كان تطبيقك يؤدي عمليات من المحتمل أن تستغرق وقتًا طويلاً، مثل الكتابة في ملفات الوسائط، من المفيد أن يكون لديك إذن وصول حصري إلى الملف أثناء معالجته. على الأجهزة التي تعمل بالإصدار 10 من نظام التشغيل Android أو الإصدارات الأحدث، يمكن لتطبيقك الحصول على إذن الوصول الحصري من خلال ضبط قيمة علامة 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().

إذن إدارة الوسائط

قد يثق المستخدمون في تطبيق معيّن لإدارة الوسائط، مثل إجراء تعديلات متكرّرة على ملفات الوسائط. إذا كان تطبيقك يستهدف الإصدار 11 من نظام التشغيل Android أو الإصدارات الأحدث ولم يكن تطبيق معرض الصور التلقائي على الجهاز، يجب عرض مربّع حوار تأكيد للمستخدم في كل مرة يحاول فيها تطبيقك تعديل ملف أو حذفه.

إذا كان تطبيقك يستهدف نظام التشغيل Android 12 (المستوى 31 لواجهة برمجة التطبيقات) أو الإصدارات الأحدث، يمكنك أن تطلب من المستخدمين منح تطبيقك إذن الوصول إلى إذن خاص من إدارة الوسائط. ويتيح هذا الإذن لتطبيقك تنفيذ كلّ من الإجراءات التالية بدون الحاجة إلى مطالبة المستخدم بكل عملية ملفّ:

لإجراء ذلك، يجب إكمال الخطوات التالية:

  1. يُرجى تقديم بيان لإذن MANAGE_MEDIA وإذن READ_EXTERNAL_STORAGE في ملف البيان الخاص بتطبيقك.

    لطلب الإذن createWriteRequest() بدون عرض مربّع حوار تأكيد، عليك الإشارة أيضًا إلى إذن ACCESS_MEDIA_LOCATION.

  2. في تطبيقك، يمكنك عرض واجهة مستخدم لتوضيح سبب رغبته في منح إذن إدارة الوسائط لتطبيقك.

  3. استدعِ إجراء الهدف ACTION_REQUEST_MANAGE_MEDIA. ينقل ذلك المستخدمين إلى شاشة تطبيقات إدارة الوسائط في إعدادات النظام. من هنا، يمكن للمستخدمين منح إذن الوصول الخاص إلى التطبيق.

حالات الاستخدام التي تتطلب بديلاً لمتجر الوسائط

إذا كان تطبيقك يؤدي أحد الأدوار التالية بشكل أساسي، يمكنك التفكير في بديل لواجهات برمجة تطبيقات MediaStore.

العمل مع أنواع أخرى من الملفات

إذا كان تطبيقك يعمل مع مستندات وملفات لا تحتوي فقط على محتوى وسائط، مثل الملفات التي تستخدم امتداد ملف EPUB أو PDF، استخدِم الإجراء ACTION_OPEN_DOCUMENT الغرض، كما هو موضّح في دليل تخزين المستندات والملفات الأخرى والوصول إليها.

مشاركة الملفات في التطبيقات المصاحبة

في الحالات التي تقدّم فيها مجموعة من التطبيقات المصاحبة، مثل تطبيق مراسلة وتطبيق للملف الشخصي، يمكنك إعداد مشاركة الملفات باستخدام معرّفات الموارد المنتظمة (URI) content://. ننصحك أيضًا باعتماد سير العمل هذا كأفضل ممارسات الأمان.

مراجع إضافية

لمزيد من المعلومات حول كيفية تخزين الوسائط والوصول إليها، راجع الموارد التالية:

العيّنات

الفيديوهات الطويلة