التغييرات في السلوك: التطبيقات التي تستهدف الإصدار 14 من نظام التشغيل Android أو الإصدارات الأحدث

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

تأكَّد أيضًا من مراجعة قائمة التغييرات في السلوك التي تؤثر في جميع التطبيقات التي تعمل على الإصدار 14 من نظام التشغيل Android بغض النظر عن targetSdkVersion التطبيق.

الوظيفة الأساسية

يجب إدراج أنواع الخدمات التي تعمل في المقدّمة.

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

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

فرض إذن BLUETOOTH_CONNECT في BluetoothAdapter

يفرض Android 14 إذن BLUETOOTH_CONNECT عند استدعاء طريقة BluetoothAdapter getProfileConnectionState() للتطبيقات التي تستهدِف Android 14 (المستوى 34 من واجهة برمجة التطبيقات).

سبق أن تطلّبت هذه الطريقة إذن BLUETOOTH_CONNECT، ولكن لم يتم فرضها. تأكَّد من أنّ تطبيقك يذكر BLUETOOTH_CONNECT في ملف AndroidManifest.xml الخاص به كما هو موضّح في المقتطف التالي، وتأكَّد من أنّ المستخدم قد منح الإذن قبل الاتصال بالرقم getProfileConnectionState.

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

تحديثات OpenJDK 17

يواصل Android 14 العمل على تحديث المكتبات الأساسية لنظام Android للتوافق مع الميزات المتوفّرة في أحدث إصدارات OpenJDK LTS، بما في ذلك تحديثات المكتبة ودعم لغة Java 17 لمطوّري التطبيقات والأنظمة الأساسية.

يمكن أن تؤثر بعض هذه التغييرات في مدى توافق التطبيقات:

  • التغييرات في التعبيرات العادية: أصبح الآن مسموحًا لمراجع المجموعات غير الصالحة اتّباع دلالات OpenJDK عن كثب قد ترى حالات جديدة يتم فيها عرض IllegalArgumentException بواسطة الفئة java.util.regex.Matcher، لذا احرص على اختبار تطبيقك بحثًا عن المناطق التي تستخدم التعبيرات العادية. لتفعيل هذا التغيير أو إيقافه أثناء الاختبار، يمكنك تبديل علامة DISALLOW_INVALID_GROUP_REFERENCE باستخدام أدوات إطار عمل التوافق.
  • معالجة UUID: تُجري الآن طريقة java.util.UUID.fromString() عمليات تحقق أكثر صرامة عند التحقق من وسيطة الإدخال، ولذلك قد يظهر لك IllegalArgumentException أثناء إلغاء التسلسل. لتفعيل هذا التغيير أو إيقافه أثناء الاختبار، يمكنك تبديل علامة ENABLE_STRICT_VALIDATION باستخدام أدوات إطار عمل التوافق.
  • مشاكل ProGuard: في بعض الحالات، تؤدي إضافة فئة java.lang.ClassValue إلى حدوث مشكلة إذا حاولت تصغير تطبيقك وإخفاء مفاتيح فك تشفيره وتحسينه باستخدام ProGuard. تنشأ المشكلة من مكتبة Kotlin التي تغيّر سلوك وقت التشغيل استنادًا إلى ما إذا كان Class.forName("java.lang.ClassValue") يعرض فئة أم لا. إذا تم تطوير تطبيقك بإصدار قديم من وقت التشغيل بدون توفّر الفئة java.lang.ClassValue، قد تزيل هذه التحسينات طريقة computeValue من الفئات المشتقة من java.lang.ClassValue.

تعزز JobScheduler سلوك الشبكة ومعاودة الاتصال

تتوقّع خدمة JobScheduler منذ وقت تقديمها عودة تطبيقك من onStartJob أو onStopJob في غضون بضع ثوانٍ. قبل الإصدار Android 14، إذا كانت الوظيفة تستمر لفترة طويلة جدًا، فإنها تتوقف وتفشل في صمت. إذا كان تطبيقك يستهدف الإصدار 14 من نظام التشغيل Android أو الإصدارات الأحدث ويتجاوز الوقت الممنوح في سلسلة التعليمات الرئيسية، يعرض التطبيق خطأ ANR مع ظهور رسالة الخطأ "ما مِن استجابة على onStartJob" أو "لم يتم الردّ على onStopJob". ننصحك بالنقل إلى WorkManager، الذي يوفّر إمكانية معالجة البيانات غير المتزامنة أو نقل أي عمل مكثف إلى سلسلة محادثات في الخلفية.

تفرض JobScheduler أيضًا شرطًا للإقرار بإذن ACCESS_NETWORK_STATE في حال استخدام قيد setRequiredNetworkType أو setRequiredNetwork. إذا لم يعلن تطبيقك عن إذن ACCESS_NETWORK_STATE عند جدولة المهمة وكان يستهدف الإصدار 14 من نظام التشغيل Android أو إصدارًا أحدث، سيؤدي ذلك إلى الحصول على SecurityException.

الأمان

القيود المفروضة على الأغراض الضمنية والمعلَّقة

بالنسبة إلى التطبيقات التي تستهدف الإصدار 14 من نظام التشغيل Android، يحظر نظام Android التطبيقات من إرسال أغراض ضمنية إلى مكوّنات التطبيقات الداخلية بالطرق التالية:

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

تمنع هذه التغييرات التطبيقات الضارة من اعتراض الأهداف الضمنية المعدّة للاستخدام من خلال مكوّنات التطبيق الداخلية.

على سبيل المثال، إليك فلتر الغرض الذي يمكن تضمينه في ملف بيان تطبيقك:

<activity
    android:name=".AppActivity"
    android:exported="false">
    <intent-filter>
        <action android:name="com.example.action.APP_ACTION" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

إذا حاول تطبيقك بدء هذا النشاط باستخدام هدف ضمني، سيتم طرح استثناء:

Kotlin

// Throws an exception when targeting Android 14.
context.startActivity(Intent("com.example.action.APP_ACTION"))

Java

// Throws an exception when targeting Android 14.
context.startActivity(new Intent("com.example.action.APP_ACTION"));

لتشغيل النشاط غير المُصدَّر، يجب أن يستخدم تطبيقك غرضًا صريحًا بدلاً من ذلك:

Kotlin

// This makes the intent explicit.
val explicitIntent =
        Intent("com.example.action.APP_ACTION")
explicitIntent.apply {
    package = context.packageName
}
context.startActivity(explicitIntent)

Java

// This makes the intent explicit.
Intent explicitIntent =
        new Intent("com.example.action.APP_ACTION")
explicitIntent.setPackage(context.getPackageName());
context.startActivity(explicitIntent);

على أجهزة استقبال البث المسجّلة في وقت التشغيل تحديد سلوك التصدير.

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

استثناء للمستلمين الذين يستلمون عمليات بث النظام فقط

إذا كان تطبيقك يسجِّل جهاز استقبال فقط لعمليات البث من خلال النظام من خلال طرق Context#registerReceiver، مثل Context#registerReceiver()، يجب ألا يتم تحديد علامة عند تسجيل جهاز الاستقبال.

تحميل رموز ديناميكية أكثر أمانًا

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

إذا كان عليك تحميل الرمز ديناميكيًا، استخدِم الطريقة التالية لضبط الملف المحمَّل ديناميكيًا (مثل ملف DEX أو JAR أو APK) كملف للقراءة فقط بعد فتح الملف وقبل كتابة أي محتوى:

Kotlin

val jar = File("DYNAMICALLY_LOADED_FILE.jar")
val os = FileOutputStream(jar)
os.use {
    // Set the file to read-only first to prevent race conditions
    jar.setReadOnly()
    // Then write the actual file content
}
val cl = PathClassLoader(jar, parentClassLoader)

Java

File jar = new File("DYNAMICALLY_LOADED_FILE.jar");
try (FileOutputStream os = new FileOutputStream(jar)) {
    // Set the file to read-only first to prevent race conditions
    jar.setReadOnly();
    // Then write the actual file content
} catch (IOException e) { ... }
PathClassLoader cl = new PathClassLoader(jar, parentClassLoader);

التعامل مع الملفات المتوفرة من قبل والتي تم تحميلها ديناميكيًا

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

قيود إضافية على بدء الأنشطة من الخلفية

بالنسبة إلى التطبيقات التي تستهدف Android 14، يقيّد النظام أيضًا الحالات التي يتم فيها السماح للتطبيقات ببدء الأنشطة من الخلفية:

  • عندما يرسل أحد التطبيقات PendingIntent باستخدام PendingIntent#send() أو طرق مشابهة، يجب أن يفعّل التطبيق الآن إذا كان يريد منح امتيازات خاصة بإطلاق النشاط في الخلفية، وذلك لبدء الغرض المعلق. لتفعيل الميزة، يجب أن يجتاز التطبيق حزمة ActivityOptions تتضمّن setPendingIntentBackgroundActivityStartMode(MODE_BACKGROUND_ACTIVITY_START_ALLOWED).
  • عندما يربط تطبيق مرئي خدمة بتطبيق آخر في الخلفية باستخدام طريقة bindService()، يجب أن يفعّل التطبيق المرئي الآن إذا أراد منح امتيازات إطلاق النشاط في الخلفية إلى الخدمة المرتبطة. لتفعيل الميزة، يجب أن يتضمّن التطبيق العلامة BIND_ALLOW_ACTIVITY_STARTS عند طلب البيانات باستخدام طريقة bindService().

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

مسح مسار ملفات Zip

بالنسبة إلى التطبيقات التي تستهدف الإصدار 14 من نظام التشغيل Android، يمنع Android الثغرة الأمنية "مسح مسار ملفات Zip" بالطريقة التالية: ZipFile(String) وZipInputStream.getNextEntry() يعرضان الخطأ ZipException إذا كانت أسماء إدخالات ملفات ZIP تحتوي على ".." أو تبدأ بـ "/".

يمكن للتطبيقات إيقاف عملية التحقّق هذه من خلال الاتصال بالرقم dalvik.system.ZipPathValidator.clearCallback().

بالنسبة إلى التطبيقات التي تستهدف Android 14 (المستوى 34 لواجهة برمجة التطبيقات)، يتم عرض SecurityException في حال استخدام MediaProjection#createVirtualDisplay في أي من السيناريوهين التاليين:

يجب أن يطلب تطبيقك من المستخدم منح الموافقة قبل كل جلسة تسجيل. وجلسة الالتقاط الفردية هي استدعاء واحد في MediaProjection#createVirtualDisplay، ويجب استخدام كل مثيل MediaProjection مرة واحدة فقط.

التعامل مع التغييرات في الإعدادات

إذا كان تطبيقك يحتاج إلى استدعاء MediaProjection#createVirtualDisplay للتعامل مع تغييرات الضبط (مثل تغيير اتجاه الشاشة أو تغيير حجم الشاشة)، يمكنك اتّباع الخطوات التالية لتعديل VirtualDisplay لمثيل MediaProjection الحالي:

  1. استدعِ VirtualDisplay#resize بالعرض والارتفاع الجديدين.
  2. أضِف عنصر Surface جديدَين يتضمّن قيمة العرض والارتفاع الجديدة VirtualDisplay#setSurface.

تسجيل مكالمة معاودة الاتصال

يجب أن يسجّل تطبيقك معاودة الاتصال للتعامل مع الحالات التي لا يمنح فيها المستخدم موافقته على متابعة جلسة التسجيل. لتنفيذ ذلك، نفِّذ السمة Callback#onStop واطلب من تطبيقك إصدار أي موارد ذات صلة (مثل VirtualDisplay وSurface).

إذا لم يسجِّل تطبيقك معاودة الاتصال هذه، سيطرح MediaProjection#createVirtualDisplay علامة IllegalStateException عندما يستدعي التطبيق.

القيود المعدّلة غير المرتبطة بحزمة تطوير البرامج (SDK)

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

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

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

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