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

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

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

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

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

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

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

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

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

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

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

إذا كان تطبيقك يستهدف Android 10 (المستوى 29 من واجهة برمجة التطبيقات) أو إصدارًا أحدث ويحتاج إلى استرداد بيانات EXIF الوصفية غير المنقّحة من الصور، عليك توضيح إذن ACCESS_MEDIA_LOCATION في ملف بيان تطبيقك، ثم طلب هذا الإذن في وقت التشغيل.

البحث عن تحديثات في متجر الوسائط

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

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

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

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

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

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

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

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

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

الأداء

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

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

عمود البيانات

عند الوصول إلى ملف وسائط حالي، يمكنك استخدام قيمة العمود 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:// على النحو المقترَح في دليل إنشاء موفّر محتوى.

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

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

ومع ذلك، إذا ألغى المستخدم تثبيت تطبيقك وأعاد تثبيته، عليك طلب 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);

تبديل حالة "معلّق" لملفات الوسائط

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

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

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

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

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

مصادر إضافية

لمزيد من المعلومات عن كيفية تخزين الوسائط والوصول إليها، يُرجى الاطّلاع على المراجع التالية.

العيّنات

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