منح إذن وصول جزئي إلى الصور والفيديوهات

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

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

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

حزمة SDK المستهدفة تم الإعلان عن READ_MEDIA_VISUAL_USER_SELECTED تم تفعيل إذن الوصول إلى الصور المحدّدة سلوك تجربة المستخدم
حزمة تطوير البرامج (SDK) 33 لا لا لا ينطبق
نعم نعم التحكّم في التطبيق
حزمة تطوير البرامج (SDK) 34 لا نعم تتحكّم فيها أنظمة التشغيل (سلوك التوافق)
نعم نعم التحكّم في التطبيق

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

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

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

طلب الأذونات

أولاً، اطلب أذونات التخزين الصحيحة في ملف بيان Android، استنادًا إلى إصدار نظام التشغيل:

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

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

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

بعد ذلك، اطلب أذونات التشغيل الصحيحة وفقًا لإصدار نظام التشغيل:

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

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

بعض التطبيقات لا تحتاج إلى أذونات

اعتبارًا من Android 10 (المستوى 29 من واجهة برمجة التطبيقات)، لم تعُد التطبيقات بحاجة إلى أذونات الوصول إلى مساحة التخزين لإضافة الملفات إلى مساحة التخزين المشتركة. وهذا يعني أنّ التطبيقات يمكنها إضافة صور إلى معرض الصور، وتسجيل الفيديوهات وحفظها في مساحة التخزين المشتركة، أو تنزيل فواتير بتنسيق PDF بدون الحاجة إلى طلب أذونات التخزين. إذا كان تطبيقك يضيف الملفات فقط إلى مساحة التخزين المشترَكة ولا يبحث عن الصور أو الفيديوهات، يجب التوقف عن طلب أذونات التخزين وضبط maxSdkVersion لإصدار 28 من واجهة برمجة التطبيقات في AndroidManifest.xml:

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

التعامل مع إعادة اختيار الوسائط

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

لا يمكن معالجة ملف ‎.ZIP
الشكل 2. يتيح مربّع الحوار الجديد أيضًا للمستخدم إعادة اختيار الصور والفيديوهات التي يريد إتاحتها لتطبيقك.

عند فتح مربّع الحوار للاختيار، يتم عرض الصور أو الفيديوهات أو كليهما استنادًا إلى الأذونات المطلوبة. على سبيل المثال، إذا كنت تطلب إذن READ_MEDIA_VIDEO بدون إذن READ_MEDIA_IMAGES، لن تظهر سوى الفيديوهات في واجهة المستخدم للمستخدمين لاختيار الملفات.

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

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

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

طلب البحث في مكتبة الجهاز

بعد التأكّد من أنّ لديك أذونات الوصول المناسبة إلى مساحة التخزين، يمكنك التفاعل مع MediaStore لطلب البحث في مكتبة الجهاز (يعمل الأسلوب نفسه سواء كان إذن الوصول الممنوح جزئيًا أو كاملاً):

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

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

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

    val images = mutableListOf<Media>()

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

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

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

    return@withContext images
}

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

طلب آخر اختيار

يمكن للتطبيقات على الإصدار 15 من نظام التشغيل Android والإصدارات الأحدث والإصدار 14 من نظام التشغيل Android المتوافقة مع تحديثات نظام Google Play البحث في آخر مجموعة من الصور والفيديوهات التي اختارها المستخدم للوصول الجزئي من خلال تفعيل QUERY_ARG_LATEST_SELECTION_ONLY:

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

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

الاحتفاظ بإمكانية الوصول إلى الصور والفيديوهات عند ترقية الجهاز

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

الأذونات من Android 13

إليك الموقف التالي:

  1. تم تثبيت تطبيقك على جهاز يعمل بنظام التشغيل Android 13.
  2. منح المستخدم إذن READ_MEDIA_IMAGES وإذن READ_MEDIA_VIDEO لتطبيقك.
  3. وبعد ذلك، يتم ترقية الجهاز إلى Android 14 عندما يكون تطبيقك لا يزال مثبّتًا.
  4. يبدأ تطبيقك في استهداف الإصدار 14 من نظام التشغيل Android (المستوى 34 لواجهة برمجة التطبيقات) أو الإصدارات الأحدث.

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

الأذونات من الإصدار Android 12 والإصدارات الأقدم

إليك الموقف التالي:

  1. تم تثبيت تطبيقك على جهاز يعمل بنظام التشغيل Android 13.
  2. لقد منح المستخدم إذن READ_EXTERNAL_STORAGE أو WRITE_EXTERNAL_STORAGE لتطبيقك.
  3. بعد ذلك، تتم ترقية الجهاز إلى الإصدار 14 من نظام التشغيل Android بينما يظل تطبيقك مثبَّتًا.
  4. يبدأ تطبيقك في استهداف الإصدار 14 من نظام التشغيل Android (المستوى 34 لواجهة برمجة التطبيقات) أو الإصدارات الأحدث.

في هذه الحالة، سيظل بإمكان تطبيقك الوصول الكامل إلى الصور والفيديوهات الخاصة بالمستخدم. يمنح النظام أيضًا إذن READ_MEDIA_IMAGES وإذن READ_MEDIA_VIDEO إلى تطبيقك تلقائيًا.

أفضل الممارسات

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

عدم تخزين حالة الإذن بشكل دائم

لا تخزِّن حالة الإذن بشكل دائم، بما في ذلك SharedPreferences أو DataStore. قد لا تكون الحالة المخزّنة متزامنة مع الحالة الفعلية. يمكن أن تتغيّر حالة الإذن بعد إعادة ضبط الإذن أو وضع إسبات التطبيق أو إجراء تغيير من قِبل المستخدم في إعدادات التطبيق أو عندما يشغّل التطبيق في الخلفية. بدلاً من ذلك، تحقَّق من أذونات مساحة التخزين باستخدام ContextCompat.checkSelfPermission().

لا تفترض إمكانية الوصول الكامل إلى الصور والفيديوهات

استنادًا إلى التغييرات التي تم إدخالها في Android 14، قد لا يحصل تطبيقك إلا على إذن بالوصول بشكل جزئي إلى مكتبة الصور على الجهاز. إذا كان التطبيق يخزِّن بيانات MediaStore في ذاكرة التخزين المؤقت عند إجراء طلب بحث باستخدام ContentResolver، قد لا تكون ذاكرة التخزين المؤقت محدَّثة.

  • ابحث دائمًا عن MediaStore باستخدام ContentResolver بدلاً من الاعتماد على ملف cache مخزّن.
  • الاحتفاظ بالنتائج في الذاكرة عندما يكون تطبيقك في المقدّمة
  • أعِد تحميل النتائج عندما يمر تطبيقك onResume دورة حياة التطبيق لأنّ المستخدم قد ينتقل من الوصول الكامل إلى الوصول الجزئي من خلال إعدادات الأذونات.

التعامل مع إذن الوصول إلى معرّف الموارد المنتظم (URI) على أنّه مؤقت

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

فلترة نوع الوسائط القابلة للاختيار حسب الإذن

يراعي مربّع الحوار للاختيار نوع الإذن المطلوب:

  • يؤدي طلب READ_MEDIA_IMAGES فقط إلى عرض الصور القابلة للاختيار فقط.
  • عند طلب READ_MEDIA_VIDEO فقط، يظهر الفيديو فقط كخيار قابل للاختيار.
  • يؤدي طلب كل من READ_MEDIA_IMAGES وREAD_MEDIA_VIDEO إلى عرض مكتبة الصور بالكامل قابلة للاختيار.

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

طلب الأذونات في عملية واحدة

لمنع المستخدمين من رؤية مربّعات حوار متعددة لوقت تشغيل النظام، اطلب إذنَي قراءة ملف الوسائط (READ_MEDIA_IMAGES أو READ_MEDIA_VIDEO أو كليهما) وREAD_MEDIA_VISUAL_USER_SELECTED وACCESS_MEDIA_LOCATION في عملية واحدة.

السماح للمستخدمين بإدارة اختياراتهم

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

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

وضع التوافق

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

السلوك أثناء الاختيار الأولي للوسائط

أثناء الاختيار الأوّلي، إذا اختار المستخدم "اختيار الصور والفيديوهات" (راجِع الشكل 1)، يتم منح الإذنَينREAD_MEDIA_IMAGES وREAD_MEDIA_VIDEO أثناء جلسة التطبيق، ما يمنح إذنًا مؤقتًا ووصولًا مؤقتًا إلى الصور والفيديوهات التي يختارها المستخدم. عندما ينتقل تطبيقك إلى الخلفية أو عندما يُغلق المستخدم تطبيقك بشكل نشط، يرفض النظام في نهاية المطاف هذه الأذونات. وهذا السلوك مشابه للأذونات الأخرى التي تُمنح لمرة واحدة.

السلوك أثناء إعادة اختيار الوسائط

إذا كان تطبيقك يحتاج إلى الوصول إلى صور وفيديوهات إضافية في وقت لاحق، يجب طلب إذن READ_MEDIA_IMAGES أو إذن READ_MEDIA_VIDEO يدويًا مرة أخرى. يتّبع النظام الخطوات نفسها المتّبعة في طلب الإذن الأوّلي، ويطلب من المستخدمين اختيار الصور والفيديوهات (راجِع الشكل 2).

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