لتوفير تجربة مستخدم أكثر ثراءً، تتيح العديد من التطبيقات للمستخدمين المساهمة في الوسائط المتوفرة على وحدة تخزين خارجية والوصول إليها. يوفّر إطار العمل فهرسًا محسّنًا لمجموعات الوسائط، يُطلق عليه مستودع الوسائط، يتيح للمستخدمين استرداد ملفات الوسائط هذه وتعديلها بسهولة أكبر. وحتى بعد إلغاء تثبيت تطبيقك، تظل هذه الملفات على جهاز المستخدم.
أداة اختيار الصور
كبديل لاستخدام "مستودع الوسائط"، توفّر أداة "اختيار الصور" على 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/
على الإصدار 11 من نظام التشغيل Android (المستوى 30 لواجهة برمجة التطبيقات) والإصدارات الأقدم. - الملفات التي تم تنزيلها، والتي يتم تخزينها في الدليل
Download/
على الأجهزة التي تعمل بالإصدار 10 (المستوى 29 من واجهة برمجة التطبيقات) والإصدارات الأحدث من نظام التشغيل Android، يتم تخزين هذه الملفات في جدولMediaStore.Downloads
. لا يتوفّر هذا الجدول على الإصدار 9 من نظام التشغيل Android (المستوى 28 لواجهة برمجة التطبيقات) والإصدارات الأقدم.
يتضمّن متجر الوسائط أيضًا مجموعة باسم
MediaStore.Files
. تعتمد محتوياته على ما إذا كان تطبيقك يستخدم مساحة التخزين المحصورة المتاحة للتطبيقات التي تستهدف الإصدار Android 10 أو الإصدارات الأحدث.
- في حال تفعيل ميزة "مساحة التخزين المحصورة"، لا تعرض المجموعة سوى الصور والفيديوهات والملفات الصوتية التي أنشأها تطبيقك. لا يحتاج معظم المطوّرين إلى استخدام
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
.
يجب استخدام Storage Access Framework للوصول إلى عمليات التنزيل التي تجريها التطبيقات الأخرى
إذا كان تطبيقك يريد الوصول إلى ملف ضمن مجموعة MediaStore.Downloads
لم ينشئها تطبيقك، عليك استخدام Storage Access Framework. لمزيد من المعلومات حول كيفية استخدام إطار العمل هذا، يُرجى الاطّلاع على الوصول إلى المستندات والملفات الأخرى من مساحة التخزين المشترَكة.
إذن تحديد الموقع الجغرافي للوسائط
إذا كان تطبيقك يستهدف الإصدار 10 من نظام التشغيل Android (المستوى 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 الخاص بالمحتوى كما هو موضّح في هذا المثال.
- تتطلّب الأجهزة التي تعمل بنظام التشغيل 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
للوصول إلى ملفات الوسائط من وحدة التخزين المشتركة. يمكنك بدلاً من ذلك الوصول إلى ملفات الوسائط مباشرةً
باستخدام أي من واجهات برمجة التطبيقات التالية:
- واجهة برمجة التطبيقات
File
- المكتبات الأصلية، مثل
fopen()
إذا لم يكن لديك أي أذونات متعلقة بمساحة التخزين، يمكنك الوصول إلى الملفات في الدليل الخاص بتطبيقك بالإضافة إلى ملفات الوسائط المنسوبة إلى تطبيقك باستخدام واجهة برمجة التطبيقات File
.
إذا حاول تطبيقك الوصول إلى ملف باستخدام واجهة برمجة التطبيقات File
ولم يكن لديه الأذونات اللازمة، سيحدث خطأ FileNotFoundException
.
للوصول إلى ملفات أخرى في مساحة التخزين المشتركة على جهاز يعمل بالإصدار 10 من نظام التشغيل Android (المستوى 29 من واجهة برمجة التطبيقات)، ننصحك بإيقاف ميزة "مساحة التخزين المحصورة النطاق" مؤقتًا من خلال ضبط
requestLegacyExternalStorage
على true
في ملف بيان تطبيقك. للوصول إلى ملفات الوسائط باستخدام طرق الملفات الأصلية على نظام التشغيل Android 10، يجب أيضًا طلب الإذن READ_EXTERNAL_STORAGE
.
اعتبارات عند الوصول إلى محتوى الوسائط
عند الوصول إلى محتوى الوسائط، يُرجى مراعاة الاعتبارات الموضّحة في الأقسام التالية.
البيانات المؤقتة
إذا كان تطبيقك يخزّن عناوين URI أو بيانات من مكتبة الوسائط مؤقتًا، عليك التحقّق بشكل دوري من توفّر تحديثات لمكتبة الوسائط. يسمح هذا الإجراء بأن تظل البيانات المخزّنة مؤقتًا على مستوى التطبيق متزامنة مع بيانات مقدّم الخدمة على مستوى النظام.
الأداء
عند إجراء عمليات قراءة تسلسلية لملفات الوسائط باستخدام مسارات الملفات المباشرة، يكون الأداء مشابهًا لأداء واجهة برمجة التطبيقات MediaStore
.
عند إجراء عمليات قراءة وكتابة عشوائية لملفات الوسائط باستخدام مسارات الملفات المباشرة، قد تكون العملية أبطأ بمقدار الضعف. في هذه الحالات، ننصحك باستخدام واجهة برمجة التطبيقات MediaStore
بدلاً من ذلك.
عمود DATA
عند الوصول إلى ملف وسائط حالي، يمكنك استخدام قيمة العمود DATA
في منطقك. ويرجع ذلك إلى أنّ هذه القيمة تتضمّن مسار ملف صالحًا. ومع ذلك، لا تفترض أنّ الملف متاح دائمًا. استعد للتعامل مع أي أخطاء في الإدخال/الإخراج المستند إلى الملفات.
لإنشاء ملف وسائط أو تعديله، لا تستخدِم قيمة العمود 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();
الموقع الجغرافي الذي تم فيه التقاط الوسائط
تحتوي بعض الصور والفيديوهات على معلومات الموقع الجغرافي في البيانات الوصفية الخاصة بها، والتي تعرض المكان الذي تم فيه التقاط صورة أو تسجيل فيديو.
تعتمد طريقة الوصول إلى معلومات الموقع الجغرافي هذه في تطبيقك على ما إذا كنت بحاجة إلى الوصول إلى معلومات الموقع الجغرافي لصورة أو فيديو.
صور فوتوغرافية
إذا كان تطبيقك يستخدم مساحة التخزين المحصورة، سيخفي النظام معلومات الموقع الجغرافي تلقائيًا. للوصول إلى هذه المعلومات، أكمل الخطوات التالية:
- يجب طلب الإذن
ACCESS_MEDIA_LOCATION
في ملف بيان التطبيق. من عنصر
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
الوصول إلى الملفات التي أنشأها تطبيقك في الأصل. يُعد طلب الإذن هذا ضروريًا لأنّ النظام يعتبر أنّ الملف مرتبط بالإصدار المثبَّت سابقًا من التطبيق، وليس بالإصدار المثبَّت حديثًا.
عندما يطلب تطبيق يستهدف الإصدار 36 من حزمة SDK أو إصدارًا أحدث على الأجهزة التي تعمل بالإصدار 16 من نظام التشغيل Android أو إصدار أحدث أذونات الوصول إلى الصور والفيديوهات، سيتم تلقائيًا تحديد أي صور يملكها التطبيق في أداة اختيار الصور للمستخدمين الذين يختارون حصر الوصول إلى الوسائط المحدّدة. يمكن للمستخدمين إلغاء اختيار أي من هذه العناصر المحدّدة مسبقًا، ما سيؤدي إلى إبطال إذن التطبيق بالوصول إلى هذه الصور والفيديوهات.
إضافة عنصر
لإضافة عنصر وسائط إلى مجموعة حالية، استخدِم رمزًا مشابهًا لما يلي. يصل مقتطف الرمز البرمجي هذا إلى VOLUME_EXTERNAL_PRIMARY
مستوى الصوت
على الأجهزة التي تعمل بالإصدار 10 من نظام التشغيل Android أو الإصدارات الأحدث. ويرجع ذلك إلى أنّه لا يمكنك تعديل محتوى وحدة تخزين على هذه الأجهزة إلا إذا كانت وحدة التخزين الأساسية، كما هو موضّح في قسم وحدات التخزين.
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()
.
إذن إدارة الوسائط
قد يثق المستخدمون في تطبيق معيّن لإدارة الوسائط، مثل إجراء تعديلات متكررة على ملفات الوسائط. إذا كان تطبيقك يستهدف الإصدار 11 من نظام التشغيل Android أو الإصدارات الأحدث ولم يكن تطبيق معرض الصور التلقائي على الجهاز، عليك عرض مربّع حوار تأكيد للمستخدم في كل مرة يحاول فيها تطبيقك تعديل ملف أو حذفه.
إذا كان تطبيقك يستهدف الإصدار 12 من نظام التشغيل Android (المستوى 31 لواجهة برمجة التطبيقات) أو الإصدارات الأحدث، يمكنك أن تطلب من المستخدمين منح تطبيقك الإذن الخاص إدارة الوسائط. يسمح هذا الإذن لتطبيقك بتنفيذ كل مما يلي بدون الحاجة إلى مطالبة المستخدم بتأكيد كل عملية على الملفات:
- تعديل الملفات باستخدام
createWriteRequest()
- نقل الملفات من المهملات وإليها باستخدام
createTrashRequest()
- احذف الملفات باستخدام
createDeleteRequest()
.
لإجراء ذلك، يُرجى إكمال الخطوات التالية:
يجب تعريف الإذن
MANAGE_MEDIA
والإذنREAD_EXTERNAL_STORAGE
في ملف البيان الخاص بتطبيقك.للاتصال بالرقم
createWriteRequest()
بدون عرض مربّع حوار تأكيد، يجب أيضًا بيان إذنACCESS_MEDIA_LOCATION
.اعرض واجهة مستخدم في تطبيقك لتوضيح سبب رغبة المستخدم في منح تطبيقك إذن الوصول إلى إدارة الوسائط.
استدعِ إجراء الغرض
ACTION_REQUEST_MANAGE_MEDIA
. سيتم توجيه المستخدمين إلى شاشة تطبيقات إدارة الوسائط في إعدادات النظام. من هنا، يمكن للمستخدمين منح التطبيق إذن الوصول الخاص.
حالات الاستخدام التي تتطلّب بديلاً عن "متجر الوسائط"
إذا كان تطبيقك يؤدي أحد الأدوار التالية بشكل أساسي، ننصحك باستخدام بديل لواجهات برمجة التطبيقات MediaStore
.
التعامل مع أنواع أخرى من الملفات
إذا كان تطبيقك يعمل مع مستندات وملفات لا تحتوي حصريًا على محتوى وسائط، مثل الملفات التي تستخدم امتداد الملف EPUB أو PDF، استخدِم إجراء النية ACTION_OPEN_DOCUMENT
، كما هو موضّح في دليل تخزين المستندات والملفات الأخرى والوصول إليها.
مشاركة الملفات في التطبيقات المصاحبة
في حال توفير مجموعة من التطبيقات المصاحبة، مثل تطبيق مراسلة وتطبيق ملف شخصي، عليك إعداد مشاركة الملفات باستخدام معرّفات الموارد المنتظمة (URI) content://
. ننصحك أيضًا باتّباع سير العمل هذا كإحدى أفضل الممارسات المتعلّقة بالأمان.
مراجع إضافية
لمزيد من المعلومات حول كيفية تخزين الوسائط والوصول إليها، يُرجى الاطّلاع على المراجع التالية.
نماذج
- MediaStore، متاح على GitHub