تطوير Android / الأساسي: أسس Android وCompose / الدرس 3
المكوّنات وملفات manifest وعمليات Intent
افهم كيف يدخل Android إلى التطبيق وكيف تسمح العقود الصريحة للمكونات والتطبيقات الأخرى بالتعاون الآمن.
في هذه الصفحة
أهداف الدرس
- ميّز بين الأنشطة والخدمات ومستقبلات البث وموفري المحتوى.
- أعلن إتاحة المكونات ومرّر معاملات صغيرة ومتحققاً منها.
اختر المكوّن بحسب مسؤوليته
يوفر النشاط نقطة دخول لتفاعل المستخدم ويستضيف عادة شجرة تنقل Compose. تمثل الخدمة عملاً بلا شاشة خاصة، وهي ليست تلقائياً خيط تنفيذ في الخلفية. يعالج مستقبل البث حدثاً وارداً لفترة قصيرة، بينما يتيح موفر المحتوى بيانات منظمة عبر عقد URI مضبوط. يستطيع Android إنشاء المكونات المعلنة دون المرور بشاشة سابقة. لذلك يجب ألا تعتمد التهيئة على تشغيل نشاط معين أولاً، ويجب أن تكون للكائنات العامة للتطبيق مسؤوليات صريحة ومحدودة.
أعلن الإتاحة الخارجية بقصد
يساهم manifest ببيانات التثبيت وإعلانات المكونات. يحتاج نشاط التشغيل مرشح Intent يطابق عقد المشغّل ويجب أن يستطيع المشغّل الوصول إليه. يمكن إبقاء الأنشطة الداخلية غير متاحة خارجياً. للتطبيقات التي تستهدف Android 12، أي API 31، أو أعلى، يجب أن تصرح المكونات ذات مرشحات Intent بقيمة android:exported وفق قواعد المنصة. يمنح إتاحة المكوّن تطبيقات أخرى نقطة دخول محتملة ولا يصادق معاملاتها. افحص manifest المدمج لأن ملفات المكتبات ومتغيرات البناء قد تضيف إعلانات تتجاوز الملف الذي عدلته.
<activity android:name=".MainActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".DetailActivity" android:exported="false" />
احتفظ بإعلانات نطاقات manifest المولّدة وإعداد application. يمكن تشغيل MainActivity من الخارج، بينما DetailActivity داخلي. يستخدم المثال نشاطين لتعليم حدود المكونات، رغم أن تطبيق Compose يستخدم نشاطاً واحداً غالباً.
مرّر المعرّفات بدلاً من شبكات الكائنات
يسمي Intent الصريح مكوّن الوجهة. يصف Intent الضمني إجراءً وبيانات ويدع النظام يختار المعالج. في الحالتين عامل الإضافات المستقبلة كإدخال غير موثوق واجعلها صغيرة. يتيح تمرير معرّف قاعدة البيانات للوجهة تحميل البيانات الحالية ومعالجة الحذف، بينما ينشئ تمرير كائن كبير نسخاً قديمة وقد يتجاوز حدود المعاملة. استخدم ثوابت لأسماء الإضافات وتحقق من القيم المطلوبة قبل العرض. السجل المفقود حالة عادية يجب عرضها بوضوح، وليس سبباً للتعطل.
const val EXTRA_ITEM_ID = "item_id"
fun Activity.openItem(id: String) {
require(id.isNotBlank())
startActivity(Intent(this, DetailActivity::class.java)
.putExtra(EXTRA_ITEM_ID, id))
}
fun Intent.readItemId(): String? =
getStringExtra(EXTRA_ITEM_ID)?.takeIf { it.isNotBlank() }
ضعه في التطبيق نفسه الذي يعلن DetailActivity واستورد Activity وIntent. يجب أن يعالج النشاط المستقبل المعرّف null برسالة أو خروج آمن ثم يستعلم من مصدر البيانات. لا تنفذ هاتان الدالتان شاشة الوجهة.
استخدم عقود المنصة للإجراءات الخارجية
لاختيار مستند أو طلب إذن، فضّل عقد Activity Result المسجل بواجهات تراعي دورة الحياة بدلاً من إدارة رموز الطلب يدوياً. يجب أن يبقى التسجيل ثابتاً عبر إعادة الإنشاء وأن تتحمل دالة النتيجة الإلغاء. URI المعاد مرجع له أذونات وقواعد عمر، وليس بالضرورة مساراً في نظام الملفات. عالج غياب تطبيق خارجي يلبي إجراءً ضمنياً. عند مشاركة ملفات خاصة استخدم URI للمحتوى بصلاحيات ضيقة بدلاً من كشف المسارات الداخلية أو افتراض استحقاق كل مستقبِل وصولاً دائماً.
طابق السياق مع الملكية
يتيح Context الوصول إلى الموارد وخدمات النظام، لكن للسياقات أعماراً وقدرات واجهة مختلفة. لا ينبغي لمستودع طويل العمر الاحتفاظ بنشاط لمجرد الوصول إلى قاعدة بيانات؛ استخدم سياق التطبيق المناسب لتلك البنية. ما زالت عمليات الواجهة تحتاج مالك واجهة مناسباً وتلتزم بقيود التشغيل من الخلفية. اجعل مستقبلات البث قصيرة وسلّم العمل الدائم إلى مجدول مناسب. يمنع رسم حدود الملكية مبكراً تسرب الذاكرة وتفسير توفر مرجع كائن على أنه إذن لتنفيذ إجراء منصي في أي وقت.
الأخطاء الشائعة وإصلاحها
يفشل التثبيت بعد إضافة مرشح Intent
- لماذا يحدث؟
- حُذف إعلان exported المطلوب لإعداد الاستهداف.
- كيف تصلحه؟
- اضبط exported صراحة بحسب حاجة المستدعين الخارجيين للوصول.
- كيف تتجنب تكراره؟
- افحص manifest المدمج واختبر التثبيت بعد تغيير الاستهداف.
تتعطل شاشة التفاصيل بسبب إضافة مفقودة
- لماذا يحدث؟
- تفترض الوجهة التزام كل مستدعٍ بالدالة الداخلية.
- كيف تصلحه؟
- تحقق من المعرّف واعرض حالة قابلة للاسترداد للإدخال المفقود.
- كيف تتجنب تكراره؟
- اختبر الدخول المباشر بمعاملات غائبة ومشوهة.
تجمّد خدمة واجهة المستخدم
- لماذا يحدث؟
- تنفذ استدعاءات الخدمة عملاً حاجباً على الخيط الرئيسي.
- كيف تصلحه؟
- انقل العمل الحاجب إلى بنية تنفيذ غير متزامنة مناسبة.
- كيف تتجنب تكراره؟
- راجع الخيوط منفصلة عن اختيار المكوّن.
اختبر فهمك
1. يتلقى نشاط محرر خاص معرّفات من تطبيقك. حدد قيمة exported وسياسة المعاملات.
اعرض الحل المشروح
استخدم exported=false عندما لا يلزم وصول خارجي. تحقق من المعرّف رغم ذلك لأن الأخطاء الداخلية والحالة المستعادة قد تنتج إدخالاً سيئاً. حمّل السجل وعالج عدم وجوده بصورة مستقلة. يقلل التحكم بالإتاحة سطح الدخول لكنه لا يلغي الحاجة إلى عقد وجهة صحيح.
2. يريد مطور إرسال صورة كاملة كإضافة Intent إلى نشاط آخر. اقترح عقداً أكثر أماناً.
اعرض الحل المشروح
خزّن الصورة أو احتفظ بها عبر مصدر البيانات المناسب ومرّر معرّفاً صغيراً أو URI للمحتوى. تحقق من الوصول في الوجهة وامنح المستقبل الأذونات اللازمة فقط. يتجنب ذلك المعاملات الضخمة ويسمح بالتفكير الصريح في الملكية والتنظيف ومدة الوصول.
أهم ما تعلمته
- المكوّنات نقاط دخول للنظام ذات دورات حياة مستقلة.
- تعالج إتاحة manifest والتحقق من الإدخال مشكلتين مختلفتين.
- فضّل العقود الصغيرة ومعالجة النتائج المراعية لدورة الحياة.