يحمي Android المستخدمين من التطبيقات الضارة ويقدّم تجربة واجهة مستخدم موثوق بها. يشمل إطار عمل "أمان النشاط" القواعد والقيود على النظام الأساسي. تمنع هذه القواعد والقيود حدوث انقطاعات غير مرغوب فيها في واجهة المستخدم، وعمليات اختطاف المهام، والتهديدات الأمنية الأخرى. تتعلّق هذه التهديدات بوقت ظهور مكوّنات التطبيق على الشاشة وكيفية ظهورها. يفرض مكوّن رئيسي في إطار العمل هذا قيودًا على بدء الأنشطة من الخلفية.
القيود المفروضة على بدء النشاط في الخلفية
يحدث بدء النشاط في الخلفية (BAL) عندما يحاول تطبيق ليس في المقدّمة أو لا يتضمّن أي أنشطة مرئية أو عندما تحاول PendingIntent تلقّاها من تطبيق مختلف بدء نشاط جديد. هذا هو بدء النشاط في الخلفية (BAL).
على الرغم من وجود حالات استخدام مشروعة، مثل بدء تشغيل تطبيق ساعة المنبّه، تؤدي عمليات بدء النشاط في الخلفية غير المقيدة إلى تجربة سيئة للمستخدمين وتُنشئ ثغرات أمنية.
لماذا يتم فرض قيود على بدء النشاط في الخلفية؟
اعتبارًا من Android 10 (مستوى واجهة برمجة التطبيقات 29)، فرض النظام الأساسي قيودًا على الحالات التي يمكن فيها للتطبيقات بدء الأنشطة من الخلفية. تساعد إجراءات الحماية هذه في منع السلوك الضار للتطبيقات وتحسين تجربة المستخدمين من خلال الحدّ من حالات إساءة الاستخدام الشائعة، بما في ذلك:
- اختطاف واجهة المستخدم والإعلانات المنبثقة: يبدأ تطبيق في الخلفية بشكل غير متوقّع نشاطًا (غالبًا ما يكون إعلانًا) فوق التطبيق الذي يتفاعل معه المستخدم حاليًا، ما يؤدي إلى اختطاف جلسته.
- التصيّد الاحتيالي وانتحال الشخصية: يبدأ تطبيق في الخلفية نشاطًا ينتحل شخصية تطبيق آخر (على سبيل المثال، شاشة تسجيل دخول مزيّفة لتطبيق مشروع) لسرقة بيانات اعتماد المستخدم. يتم ذلك غالبًا من خلال هجوم "شطيرة النشاط"، حيث يتم إدراج نشاط ضار في مكدس المهام لتطبيق مشروع.
- الاستيلاء على النقرات: يعرض تطبيق في الخلفية نشاطًا شفافًا أو محجوبًا فوق تطبيق آخر لاعتراض نقرات المستخدم وخداعه لاتخاذ إجراءات غير مقصودة.
- تنشيط التطبيق: ينشّط مكوّن في الخلفية من تطبيق ما مكوّنات تطبيق آخر في المقدّمة لزيادة مقاييس المستخدمين النشطين يوميًا بشكل غير مشروع.
الخدمات التي تعمل في المقدّمة (للمهام المستمرة)
إذا كان تطبيقك بحاجة إلى تنفيذ مهمة طويلة الأمد في الخلفية يجب أن يكون المستخدم على علم بها، مثل تشغيل الموسيقى أو تتبُّع تمرين، عليك استخدام خدمة تعمل في المقدّمة. يجب أن تعرض الخدمة التي تعمل في المقدّمة إشعارًا دائمًا لا يمكن للمستخدم إغلاقه. يمكن أن يوفّر هذا الإشعار عناصر تحكّم تفاعلية (على سبيل المثال، أزرار التشغيل/الإيقاف المؤقت لتطبيق موسيقى). يُبقي ذلك المستخدم على اطّلاع ويمنحه القدرة على التحكّم، ولكن لا يقطع تجربته بعرض نشاط بملء الشاشة.
من خلال اتّباع هذا التسلسل الهرمي، بدءًا من الإشعارات العادية والانتقال إلى الخيارات الأكثر تدخلاً عند الضرورة فقط، يمكنك إنشاء تجربة أفضل وأكثر قابلية للتنبؤ بها لمستخدميك.
الحالات التي يُسمح فيها ببدء النشاط في الخلفية (الاستثناءات)
يمكن للتطبيق بدء نشاط من الخلفية إذا استوفى أحد الشروط التالية:
- يتضمّن التطبيق نافذة مرئية، مثل نشاط في المقدّمة.
- التطبيق هو محرر أسلوب الإدخال (IME) الحالي.
- بدأ النشاط من
PendingIntentأرسله النظام (على سبيل المثال، من نقرة على إشعار). - منح المستخدم التطبيق إذن
SYSTEM_ALERT_WINDOW. - تم منح التطبيق إذن
START_ACTIVITIES_FROM_BACKGROUND. - التطبيق مرتبط بخدمة تم منحها إذن بدء الأنشطة في الخلفية.
- يبدأ مشغّل التطبيقات على الجهاز عملية الإطلاق، مثلاً عندما ينقر المستخدم على رمز تطبيق أو يتفاعل مع أداة.
- يتم الإطلاق من جزء أساسي في نظام التشغيل يجب تشغيله في جميع الأوقات، مثل خدمة "الاتصال الهاتفي" التي تبدأ شاشة المكالمة الواردة.
إجراءات الحماية الجديدة والموافقات المطلوبة
لتعزيز الأمان بشكل أكبر، قدّم Android قواعد أكثر صرامة تتطلب موافقات صريحة للتطبيقات التي تستخدم PendingIntent وIntentSender لبدء الأنشطة. لا يُسمح بالإطلاق إلا إذا وافق التطبيق الذي أنشأ PendingIntent أو التطبيق الذي يرسله على منح امتيازات الإطلاق في الخلفية.
في معظم الحالات، يجب أن يكون التطبيق الذي يرسل PendingIntent هو الذي يوافق على منح امتيازات الإطلاق في الخلفية، لأنّه عادةً ما يكون التطبيق الذي يتفاعل معه المستخدم مباشرةً (على سبيل المثال، النقر على زر).
يجب أن يوافق المُرسِلون على استخدام PendingIntent
عندما يستهدف تطبيقك Android 14 (مستوى واجهة برمجة التطبيقات 34) أو الإصدارات الأحدث، لن يمنح امتيازات بدء النشاط في الخلفية تلقائيًا عند إرسال PendingIntent. إذا لم توافق صراحةً على منح امتيازات الإطلاق في الخلفية، قد يتم حظر بدء النشاط، ما لم يكن منشئ PendingIntent قد منح امتيازاته الخاصة من قبل.
لضمان نجاح عملية الإطلاق، يجب أن يوافق المُرسِل على منح امتيازاته من خلال استدعاء ActivityOptions.setPendingIntentBackgroundActivityStartMode() و الوضع المقترَح هو ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE (تمت إضافته في حزمة SDK 36).
هذا الوضع أكثر صرامة وأمانًا. لا يمنح الإذن إلا إذا كان التطبيق المُرسِل مرئيًا على الشاشة في لحظة إرسال PendingIntent. يضمن ذلك بشكل أقوى أنّ بدء النشاط هو نتيجة مباشرة لتفاعل المستخدم مع تطبيقك.
استخدِم ActivityOptions.setPendingIntentBackgroundActivityStartMode() لمنح الامتيازات.
// Sender Side
ActivityOptions options = ActivityOptions.makeBasic()
.setPendingIntentBackgroundActivityStartMode(
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE);
try {
myPendingIntent.send(options.toBundle());
} catch (PendingIntent.CanceledException e) {
Log.e(TAG, "The PendingIntent was canceled", e);
}
// Sender Side
val options = ActivityOptions.makeBasic().apply {
pendingIntentBackgroundActivityStartMode = ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE
}
try {
myPendingIntent.send(options.toBundle())
} catch (e: PendingIntent.CanceledException) {
Log.e(TAG, "The PendingIntent was canceled", e)
}
يجب أن يوافق المنشئون على استخدام PendingIntent
عندما يستهدف تطبيقك Android 15 (مستوى واجهة برمجة التطبيقات 35) أو الإصدارات الأحدث، لن يمنح التطبيق الذي ينشئ PendingIntent امتيازات الإطلاق في الخلفية تلقائيًا. للسماح للمُرسِل باستخدام امتيازات بدء النشاط في الخلفية لتطبيقك، عليك الموافقة صراحةً على منحها.
استخدِم ActivityOptions.setPendingIntentCreatorBackgroundActivityStartMode() لمنح الامتيازات.
// Creator Side
Intent intent = new Intent(context, MyActivity.class);
ActivityOptions options = ActivityOptions.makeBasic().setPendingIntentCreatorBackgroundActivityStartMode(ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
PendingIntent pendingIntent = PendingIntent.getActivity(context, REQUEST_CODE, intent, PendingIntent.FLAG_IMMUTABLE, options.toBundle());
// Creator Side
val intent = Intent(context, MyActivity::class.java)
val options = ActivityOptions.makeBasic().apply {
pendingIntentCreatorBackgroundActivityStartMode = ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
}
val pendingIntent = PendingIntent.getActivity(context, REQUEST_CODE, intent,
PendingIntent.FLAG_IMMUTABLE, options.toBundle())
الإطلاق باستخدام IntentSender
تنطبق أيضًا القيود نفسها المفروضة على بدء النشاط في الخلفية عند بدء الأنشطة باستخدام
IntentSender. بما أنّه يتم الحصول على IntentSender من خلال
PendingIntent.getIntentSender، يخضع لمتطلبات الموافقة
المماثلة.
- اعتبارًا من Android 14 (مستوى واجهة برمجة التطبيقات 34)، يتطلب استخدام Context.startIntentSender()
موافقة من جانب المُرسِل. عليك أيضًا تقديم حزمة
ActivityOptionsهنا.
ActivityOptions options = ActivityOptions.makeBasic()
.setPendingIntentBackgroundActivityStartMode(
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
context.startIntentSender(myIntentSender, fillInIntent, flagsMask,
flagsValues, extraFlags, options.toBundle());
val options = ActivityOptions.makeBasic().apply {
pendingIntentBackgroundActivityStartMode = ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
}
context.startIntentSender(myIntentSender, fillInIntent, flagsMask,
flagsValues, extraFlags, options.toBundle())
- اعتبارًا من Android 17 (مستوى واجهة برمجة التطبيقات 37 والإصدارات الأحدث)، يتطلب استخدام IntentSender.sendIntent() موافقة من جانب المُرسِل.
ActivityOptions options = ActivityOptions.makeBasic()
.setPendingIntentBackgroundActivityStartMode(
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
myIntentSender.sendIntent(context, code, intent, onFinished, handler,
requiredPermission, options.toBundle());
val options = ActivityOptions.makeBasic().apply {
pendingIntentBackgroundActivityStartMode = ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
}
myIntentSender.sendIntent(context, code, intent, onFinished, handler,
requiredPermission, options.toBundle())
- استخدام ActivityResultLauncher
: تستخدم واجهة برمجة التطبيقات AndroidX هذه Context.startIntentSender() داخليًا، وبالتالي تتأثر بالقيود المفروضة على بدء النشاط في الخلفية.
مخطط التسلسل: قيود بدء النشاط في الخلفية
يوضّح هذا المخطّط البياني عملية بدء نشاط بشكل آمن باستخدام PendingIntent. يعتمد نجاح عملية الإطلاق على سلسلة امتيازات صالحة، حيث يمنح أحد التطبيقَين المشاركَين على الأقل امتيازاته ولديه القدرة على بدء نشاط من الخلفية
- الإنشاء والتفويض (التطبيق أ - المنشئ)
- ينشئ تطبيق المنشئ
PendingIntent - إذا كان التطبيق يستهدف حزمة SDK 35 أو الإصدارات الأحدث، يجب أن يفوّض المنشئ صراحةً امتيازات بدء النشاط في الخلفية باستخدام setPendingIntentCreatorBackgroundActivityStartMode() إذا كان يريد استخدام امتيازاته. لا يتم تفويض أي امتيازات تلقائيًا.
- يتم بعد ذلك تسليم
PendingIntentإلى تطبيق آخر (التطبيق ب)
- ينشئ تطبيق المنشئ
- الإطلاق والمساهمة (التطبيق ب - المُرسِل)
- في وقت لاحق، يبدأ تطبيق المُرسِل عملية الإطلاق من خلال استدعاء
PendingIntent.send(). - إذا كان التطبيق يستهدف حزمة SDK 34 أو الإصدارات الأحدث، يجب أن يساهم المُرسِل صراحةً بامتيازاته الخاصة باستخدام setPendingIntentBackgroundActivityStartMode() إذا كان يريد استخدام امتيازاته. لا يتم تفويض أي امتيازات تلقائيًا.
- في وقت لاحق، يبدأ تطبيق المُرسِل عملية الإطلاق من خلال استدعاء
- التحقّق من الأمان في نظام Android
- يعترض نظام Android طلب الإطلاق ويُجري فحصًا أمنيًا.
- يقيّم النظام شرطَين:
- هل فوّض المنشئ امتيازاته؟ وهل يستوفي تطبيق المنشئ حاليًا أحد الاستثناءات العامة لبدء النشاط في الخلفية؟
- هل ساهم المُرسِل بامتيازاته؟ وهل يستوفي تطبيق المُرسِل حاليًا أحد الاستثناءات العامة لبدء النشاط في الخلفية؟
- النتيجة
- مسموح: إذا تم استيفاء شرط واحد على الأقل من الشرطَين في الخطوة 3، يتم التحقّق من صحة سلسلة الامتيازات. يبدأ نظام Android النشاط المستهدَف، ويتلقّى المُرسِل نتيجة ناجحة.
- محظور: إذا لم يتم استيفاء أي من الشرطَين، يحظر النظام عملية الإطلاق. لا يتلقّى تطبيق المُرسِل قيمة إرجاع مباشرة أو استثناء يشير إلى الفشل. بدلاً من ذلك، يسجّل نظام Android داخليًا الرسالة "تم حظر بدء النشاط في الخلفية" في Logcat، ويجب أن يتحقّق المطوّرون منها لتحديد المشاكل وحلّها.
منع اختطاف المهام
لمنع هجمات اختطاف المهام داخل التطبيق (مثل "شطيرة النشاط")، يقدّم Android 15 قواعد جديدة للتطبيقات التي تستهدف مستوى واجهة برمجة التطبيقات 37 أو الإصدارات الأحدث.
- القاعدة 1: ضمن مهمة واحدة، لا يمكن بدء نشاط إلا من خلال نشاط آخر ينتمي إلى التطبيق نفسه (أي له رقم تعريف المستخدم نفسه) مثل النشاط الحالي في أعلى المهمة.
- القاعدة 2: لا يُسمح إلا للنشاط ضمن مهمة في المقدّمة يتطابق رقم تعريف المستخدم الخاص به مع رقم تعريف المستخدم للنشاط في أعلى المهمة بإنشاء مهمة جديدة أو نقل مهمة مختلفة حالية إلى المقدّمة.
موافقة المطوّر على إجراءات الحماية داخل المهام
يمكن تفعيل هذا السلوك بدءًا من حزمة SDK المستهدَفة 37، ويجب الموافقة صراحةً على تفعيله. تم تصميم هذا السلوك لمنع اختطاف المهام داخل التطبيق (أو شطيرة الأنشطة)، حيث يمكن لتطبيق ضار بدء نشاط في مهمة تطبيقك لانتحال شخصيته وسرقة بيانات المستخدم.
تفعيل إجراءات الحماية
للموافقة على تفعيل ميزة "أمان بدء النشاط" لتطبيقك، اضبط السمة android:allowCrossUidActivitySwitchFromBelow على false في ملف AndroidManifest.xml. هذا إعداد على مستوى التطبيق يحمي جميع الأنشطة في تطبيقك تلقائيًا.
إنشاء استثناءات لأنشطة معيّنة
إذا فعّلت ميزة "أمان بدء النشاط" لتطبيقك ولكنك بحاجة إلى السماح لتطبيق آخر ببدء نشاط معيّن موثوق به، يمكنك إنشاء استثناء مستهدَف. لاستثناء نشاط واحد من إجراء الحماية هذا، استدعِ setAllowCrossUidActivitySwitchFromBelow(true) ضمن طريقة onCreate() لهذا النشاط. يسمح ذلك ببدء هذا النشاط الواحد بينما تظل بقية أجزاء تطبيقك محمية.
تحديد المشاكل وحلّها
يمكنك فلترة Logcat للعثور على الرسائل ذات الصلة باستخدام تعبير عادي. يتم غالبًا استخدام العلامة ActivityTaskManager، ويمكن أن تساعد الفلترة حسب ActivityTaskManager في عزل السجلات.
فهم رسائل السجلّ الرئيسية
تم حظر الإطلاق (خطأ): تشير هذه الرسالة إلى أنّه تم حظر بدء نشاط.
- المعنى: تم رفض بدء نشاط لأنّ الموافقة اللازمة على استخدام PendingIntent غير متوفّرة لدى المُرسِل (الذي يستهدف حزمة SDK 34 أو الإصدارات الأحدث) أو المنشئ (الذي يستهدف حزمة SDK 35 أو الإصدارات الأحدث).
- الإجراء: عليك تعديل الرمز البرمجي لتضمين الموافقة الصحيحة على استخدام ActivityOptions.
عند تحليل السجلات، تحقَّق من هذه الحقول:
- realCallingPackage: التطبيق الذي أرسل PendingIntent. هذا هو المُرسِل.
- callingPackage: التطبيق الذي أنشأ PendingIntent. هذا هو المنشئ.
وضع التدقيق الصارم
اعتبارًا من Android 16، يمكن لمطوّر التطبيق تفعيل وضع التدقيق الصارم لتلقّي إشعار عند حظر بدء نشاط (أو عند احتمال حظره عند رفع حزمة SDK المستهدَفة للتطبيق).
في ما يلي مثال على الرمز البرمجي لتفعيل وضع التدقيق الصارم من وقت مبكر في طريقة Application.onCreate() لتطبيقك أو نشاطك أو مكوّن التطبيق الآخر:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
StrictMode.setVmPolicy(
StrictMode.VmPolicy.Builder()
.detectBlockedBackgroundActivityLaunch()
.penaltyLog()
.build());
)
}