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

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

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

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

أنواع الخدمات التي تعمل في المقدّمة مطلوبة

如果您的应用以 Android 14(API 级别 34)或更高版本为目标平台,则必须为应用中的每个前台服务至少指定一项前台服务类型。您应选择一个能代表应用用例的前台服务类型。系统需要特定类型的前台服务满足特定用例。

如果应用中的用例与这些类型均不相关,强烈建议您迁移逻辑以使用 WorkManager用户发起的数据传输作业

فرض إذن BLUETOOTH_CONNECT في BluetoothAdapter

يفرض الإصدار 14 من نظام التشغيل Android إذن 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 期望您的应用从 onStartJobonStopJob。在 Android 14 之前,如果作业运行时间过长,系统会停止作业并静默失败。如果您的应用以 Android 14(API 级别 34)或更高版本为目标平台, 超过在主线程上授予的时间,应用会触发 ANR 显示“没有响应 onStartJob”错误消息或 “onStopJob没有回复”。

此 ANR 可能是由以下 2 种情况造成的: 1.有工作阻塞主线程,阻止回调 onStartJob 或者onStopJob在预期时间内执行并完成。 2. 开发者在 JobScheduler 中运行阻塞工作 回调 onStartJobonStopJob,阻止从 在预期的时限内完成

要解决第 1 个问题,您需要进一步调试阻塞主线程的因素 您可以使用以下代码 ApplicationExitInfo#getTraceInputStream(),用于获取 Tombstone ANR 发生时的跟踪信息如果您能够手动重现 ANR 问题 您可以录制系统轨迹,并使用 Android StudioPerfetto,以便更好地了解应用上运行的 在发生 ANR 时调用主线程 请注意,直接使用 JobScheduler API 或使用 androidx 库 WorkManager 时可能会发生这种情况。

如需解决问题 2,请考虑迁移到 WorkManager,它支持将 onStartJobonStopJob 中的任何处理封装在异步线程中。

JobScheduler 还引入了一项要求,即如果使用 setRequiredNetworkTypesetRequiredNetwork 约束条件,则必须声明 ACCESS_NETWORK_STATE 权限。如果您的应用未声明 ACCESS_NETWORK_STATE 权限 Android 14 或更高版本,则会导致 SecurityException

Tiles launch API

对于以 Android 14 及更高版本为目标平台的应用, TileService#startActivityAndCollapse(Intent) 已弃用,现在会抛出 调用时抛出异常。如果您的应用从功能块启动 activity,请使用 TileService#startActivityAndCollapse(PendingIntent)

الخصوصية

الوصول الجزئي إلى الصور والفيديوهات

Android 14 introduces Selected Photos Access, which allows users to grant apps access to specific images and videos in their library, rather than granting access to all media of a given type.

This change is only enabled if your app targets Android 14 (API level 34) or higher. If you don't use the photo picker yet, we recommend implementing it in your app to provide a consistent experience for selecting images and videos that also enhances user privacy without having to request any storage permissions.

If you maintain your own gallery picker using storage permissions and need to maintain full control over your implementation, adapt your implementation to use the new READ_MEDIA_VISUAL_USER_SELECTED permission. If your app doesn't use the new permission, the system runs your app in a compatibility mode.

تجربة المستخدم

إشعارات آمنة بوضع العرض بملء الشاشة

في الإصدار 11 من Android (المستوى 30 لواجهة برمجة التطبيقات)، كان بإمكان أي تطبيق استخدام Notification.Builder.setFullScreenIntent لإرسال رسائل بهدف عرض المحتوى بملء الشاشة عندما يكون الهاتف مقفلاً. يمكنك منح هذا الإذن تلقائيًا عند تثبيت التطبيق من خلال تحديد إذن USE_FULL_SCREEN_INTENT فيملف AndroidManifest.

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

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

يمكنك استخدام واجهة برمجة التطبيقات الجديدة NotificationManager.canUseFullScreenIntent للتحقّق مما إذا كان تطبيقك يملك الإذن. وإذا لم يكن الأمر كذلك، يمكن لتطبيقك استخدام الطلب الجديد ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT لفتح صفحة الإعدادات التي يمكن للمستخدمين فيها منح الإذن.

الأمان

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

For apps targeting Android 14 (API level 34) or higher, Android restricts apps from sending implicit intents to internal app components in the following ways:

  • Implicit intents are only delivered to exported components. Apps must either use an explicit intent to deliver to unexported components, or mark the component as exported.
  • If an app creates a mutable pending intent with an intent that doesn't specify a component or package, the system throws an exception.

These changes prevent malicious apps from intercepting implicit intents that are intended for use by an app's internal components.

For example, here is an intent filter that could be declared in your app's manifest file:

<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>

If your app tried to launch this activity using an implicit intent, an ActivityNotFoundException exception would be thrown:

Kotlin

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

Java

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

To launch the non-exported activity, your app should use an explicit intent instead:

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 (المستوى 34 لواجهة برمجة التطبيقات) أو الإصدارات الأحدث وتستخدم مستقبلات مسجّلة في السياق تحديد علامة للإشارة إلى ما إذا كان يجب تصدير المستقبل إلى جميع التطبيقات الأخرى على الجهاز أم لا: إما RECEIVER_EXPORTED أو RECEIVER_NOT_EXPORTED، على التوالي. يساعد هذا الشرط في حماية التطبيقات من الثغرات الأمنية من خلال الاستفادة من ميزات هذه الأجهزة المستلِمة التي تم تقديمها في Android 13.

استثناء لأجهزة الاستقبال التي تتلقّى عمليات بث النظام فقط

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

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

إذا كان تطبيقك يستهدف الإصدار 14 من نظام التشغيل Android (المستوى 34 لواجهة برمجة التطبيقات) أو الإصدارات الأحدث وكان يستخدم ميزة "تحميل الرمز الديناميكي" (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);

التعامل مع الملفات الحالية التي يتم تحميلها ديناميكيًا

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

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

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

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

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

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

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

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

对于以 Android 14(API 级别 34)或更高版本为目标平台的应用,在以下任一情况下,MediaProjection#createVirtualDisplay 都会抛出 SecurityException

您的应用必须在每次捕获会话之前征求用户同意。单次捕获会话是对 MediaProjection#createVirtualDisplay 的单次调用,并且每个 MediaProjection 实例只能使用一次。

处理配置变更

如果您的应用需要调用 MediaProjection#createVirtualDisplay 来处理配置更改(例如屏幕方向或屏幕大小更改),您可以按照以下步骤更新现有 MediaProjection 实例的 VirtualDisplay

  1. 使用新的宽度和高度调用 VirtualDisplay#resize
  2. VirtualDisplay#setSurface 提供新的 Surface,并为其指定新的宽度和高度。

注册回调

您的应用应注册回调,以处理用户不同意继续拍摄会话的情况。为此,请实现 Callback#onStop,并让应用释放所有相关资源(例如 VirtualDisplaySurface)。

如果您的应用未注册此回调,当您的应用调用它时,MediaProjection#createVirtualDisplay 会抛出 IllegalStateException

تعديل القيود المفروضة على استخدام واجهات برمجة التطبيقات غير التابعة لحزمة SDK

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

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

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

To learn more about the changes in this release of Android, see Updates to non-SDK interface restrictions in Android 14. To learn more about non-SDK interfaces generally, see Restrictions on non-SDK interfaces.