تُعدّ بنية التطبيق أساسًا لتطبيق Android عالي الجودة. يتيح لك التصميم المعرَّف جيدًا إنشاء تطبيق قابل للتوسّع والصيانة، ويمكنه التكيّف مع منظومة Android المتزايدة باستمرار، بما في ذلك الهواتف والأجهزة اللوحية والأجهزة القابلة للطي وأجهزة ChromeOS وشاشات السيارات وأجهزة الواقع الممتد (XR).
تركيب التطبيق
يتألف تطبيق Android نموذجي من عدة مكوّنات للتطبيق، مثل الخدمات وموفّري المحتوى وأجهزة استقبال البث. يتم تعريف هذه المكوّنات في بيان التطبيق.
تُعد واجهة المستخدم للتطبيق أيضًا أحد المكوّنات. في السابق، كانت واجهات المستخدم تُنشأ باستخدام أنشطة متعدّدة. ومع ذلك، تستخدم التطبيقات الحديثة بنية
تتضمّن نشاطًا واحدًا. يعمل Activity الفردي كحاوية للشاشات أو وجهات Jetpack Compose.
أشكال الأجهزة المتعددة
يمكن تشغيل التطبيقات على أشكال متعددة من الأجهزة، بما في ذلك الهواتف والأجهزة اللوحية والأجهزة القابلة للطي وأجهزة ChromeOS وغيرها. لا تفترض أنّ تطبيقك سيظل دائمًا ثابتًا في الاتجاه العمودي أو الأفقي. تؤدي تغييرات الضبط، مثل تدوير الجهاز أو طي جهاز قابل للطي وفتحه، إلى إعادة إنشاء واجهة المستخدم في تطبيقك، ما يؤثر في حالة التطبيق.
قيود الموارد
تكون الأجهزة الجوّالة، حتى تلك ذات الشاشات الكبيرة، محدودة الموارد، لذا قد يوقف نظام التشغيل عملية تطبيقك في أي وقت لتوفير الموارد لعمليات أخرى.
شروط إطلاق المتغيّر
في بيئة محدودة الموارد، يمكن تشغيل مكوّنات تطبيقك بشكل فردي وبترتيب غير منتظم، بل يمكن لنظام التشغيل أو المستخدم إيقافها في أي وقت. نتيجةً لذلك، لا تخزِّن أي بيانات أو حالات للتطبيق في مكوّنات تطبيقك. اجعل مكونات تطبيقك مستقلة عن بعضها البعض.
المبادئ المعمارية الشائعة
إذا لم يكن بإمكانك استخدام مكوّنات التطبيق لتخزين بيانات التطبيق وحالته، كيف يمكنك تصميم تطبيقك؟
مع زيادة حجم تطبيقات Android، من المهم تحديد بنية تتيح توسيع نطاق التطبيق. تحدّد بنية التطبيق المصمَّمة بشكل جيد الحدود بين أجزاء التطبيق والمسؤوليات التي يتحمّلها كل جزء.
فصل الاهتمامات
صمِّم بنية تطبيقك وفقًا لبعض المبادئ المحدّدة.
المبدأ الأكثر أهمية هو فصل الاهتمامات: أي تقسيم تطبيقك إلى طرق وفئات وملفات وحِزم ووحدات وطبقات تتضمّن مسؤوليات وحدودًا محدّدة بوضوح.
من الأخطاء الشائعة كتابة كل الرمز في Activity.
يتمثّل الدور الأساسي Activity في استضافة واجهة مستخدم تطبيقك. يتحكّم نظام التشغيل Android في دورة حياتها، وغالبًا ما يتم إيقافها وإعادة إنشائها استجابةً لإجراءات المستخدمين، مثل تدوير الشاشة أو أحداث النظام، مثل انخفاض الذاكرة.
ونظرًا إلى طبيعتها المؤقتة، لا يمكن استخدامها للاحتفاظ ببيانات التطبيق أو حالته. إذا خزّنت بيانات في Activity، سيتم فقدان هذه البيانات عند إعادة إنشاء المكوّن. لضمان استمرار البيانات وتوفير تجربة مستخدم مستقرة، لا تعتمد على عناصر واجهة المستخدم هذه في إدارة الحالة.
التنسيقات التكيّفية
يمكنك إنشاء تطبيقات تتعامل بسلاسة مع تغييرات الإعدادات، مثل تغييرات اتجاه الجهاز أو تغييرات حجم نافذة التطبيق. استخدِم التصاميم الأساسية المتجاوبة لتقديم تجربة مثالية للمستخدمين على مجموعة متنوعة من أشكال الأجهزة.
إنشاء واجهة مستخدم Drive من نماذج البيانات
من المبادئ المهمة الأخرى أن تستند واجهة المستخدم إلى نماذج البيانات، ويُفضّل أن تكون نماذج ثابتة. تمثّل نماذج البيانات بيانات التطبيق، وهي مستقلة عن عناصر واجهة المستخدم والمكوّنات الأخرى في تطبيقك، ما يعني أنّها غير مرتبطة بدورة حياة واجهة المستخدم ومكوّنات التطبيق، ولكن سيتم إتلافها عند إزالة نظام التشغيل لعملية التطبيق من الذاكرة.
تُعدّ النماذج الثابتة مثالية للأسباب التالية:
لا يفقد المستخدمون البيانات إذا أوقف نظام التشغيل Android تطبيقك لإتاحة المزيد من الموارد.
يستمر تطبيقك في العمل في الحالات التي يكون فيها الاتصال بالشبكة متقطعًا أو غير متاح.
استند في تصميم بنية تطبيقك إلى فئات نماذج البيانات لجعل تطبيقك قويًا وقابلاً للاختبار.
المصدر الوحيد للحقيقة
عند تحديد نوع بيانات جديد في تطبيقك، عليك تعيين مصدر واحد للحقيقة (SSOT) له. ويكون نظام SSOT هو المالك لهذه البيانات، ولا يمكن لأي نظام آخر تعديلها أو تغييرها. لتحقيق ذلك، تعرض SSOT البيانات باستخدام نوع غير قابل للتغيير، ولتعديل البيانات، تعرض SSOT دوال أو تتلقّى أحداثًا يمكن أن تستدعيها أنواع أخرى.
يوفّر هذا النمط مزايا متعددة:
- تجميع كل التغييرات التي تم إجراؤها على نوع معيّن من البيانات في مكان واحد
- يحمي البيانات حتى لا تتلاعب بها أنواع أخرى
- تسهيل تتبُّع التغييرات التي يتم إجراؤها على البيانات، ما يسهّل رصد الأخطاء
في التطبيقات التي تعمل بلا إنترنت أولاً، يكون مصدر صحة بيانات التطبيق عادةً قاعدة بيانات. في حالات أخرى، يمكن أن يكون مصدر المعلومات الصحيحة ViewModel.
تدفّق البيانات أحادي الاتجاه
يتم غالبًا استخدام مبدأ المصدر المرجعي الوحيد مع نمط تدفّق البيانات أحادي الاتجاه (UDF). في UDF، يتدفق الحالة في اتجاه واحد فقط، عادةً من المكوّن الرئيسي إلى المكوّن الفرعي. الأحداث التي تعدّل تدفّق البيانات في الاتجاه المعاكس
في نظام التشغيل Android، تنتقل الحالة أو البيانات عادةً من أنواع النطاق الأعلى في التسلسل الهرمي إلى أنواع النطاق الأدنى. يتم عادةً تنشيط الأحداث من الأنواع ذات النطاق الأضيق إلى أن تصل إلى مصدر الحقيقة الوحيد لنوع البيانات المعنيّ. على سبيل المثال، تتدفق بيانات التطبيق عادةً من مصادر البيانات إلى واجهة المستخدم. تنتقل أحداث المستخدمين، مثل الضغط على الأزرار، من واجهة المستخدم إلى مصدر الحقيقة الوحيد (SSOT) حيث يتم تعديل بيانات التطبيق وعرضها بنوع غير قابل للتغيير.
يحافظ هذا النمط بشكل أفضل على اتساق البيانات، وهو أقل عرضة للأخطاء، وأسهل في تصحيح الأخطاء، ويوفّر جميع مزايا نمط مصدر الحقيقة الوحيد.
لمزيد من المعلومات حول تدفق البيانات أحادي الاتجاه، راجِع مقالة تدفق البيانات أحادي الاتجاه في Jetpack Compose.
بنية التطبيق المقترَحة
مع مراعاة مبادئ التصميم الشائعة، صمِّم كل تطبيق باستخدام طبقتَين على الأقل:
- طبقة واجهة المستخدم: تعرض بيانات التطبيق على الشاشة
- طبقة البيانات: تحتوي على المنطق التجاري لتطبيقك وتعرض بيانات التطبيق
يمكنك إضافة طبقة أخرى تُعرف باسم طبقة النطاق لتبسيط وإعادة استخدام التفاعلات بين طبقتَي واجهة المستخدم والبيانات.
بنية التطبيقات الحديثة
تستخدم بنية تطبيقات Android الحديثة الأساليب التالية (من بين أساليب أخرى):
- البنية التكيّفية والمتعدّدة الطبقات
- تدفّق البيانات أحادي الاتجاه (UDF) في جميع طبقات التطبيق
- طبقة واجهة المستخدم التي تتضمّن عناصر الاحتفاظ بالحالة لإدارة تعقيد واجهة المستخدم
- الروتينات المشتركة وعمليات التدفق
- أفضل الممارسات المتعلّقة بإدخال التبعية
- تحسين الأداء باستخدام R8 و"ملفات Baseline Profiles"، وقياس الأداء باستخدام Macrobenchmark
لمزيد من المعلومات، يُرجى الاطّلاع على اقتراحات بشأن بنية Android.
طبقة واجهة المستخدم
دور طبقة واجهة المستخدم (أو طبقة العرض) هو عرض بيانات التطبيق على الشاشة. عندما تتغيّر البيانات، سواء بسبب تفاعل المستخدم (مثل الضغط على زر) أو الإدخال الخارجي (مثل استجابة الشبكة)، يتم تعديل واجهة المستخدم لعرض التغييرات.
تتألف طبقة واجهة المستخدم من نوعَين من البِنى:
- عناصر واجهة المستخدم التي تعرض البيانات على الشاشة يمكنك إنشاء هذه العناصر باستخدام دوال Jetpack Compose لتوفير تصاميم تكيُّفية.
- عناصر الاحتفاظ بالحالة (مثل
ViewModel) التي تحتفظ بالبيانات وتعرضها في واجهة المستخدم وتتعامل مع المنطق يجب أن تكون مدة بقاء عناصر الاحتفاظ بالحالة هي نفسها مدة بقاء عنصر في واجهة المستخدم الذي توفّر الحالة له. على سبيل المثال، يجب الاحتفاظ بـ ViewModel لشاشة معيّنة في الذاكرة إلى أن تتم إزالة الشاشة من سجلّ التصفّح الخلفي للتطبيق. لمزيد من المعلومات، اطّلِع على فترات بقاء الحالة.
بالنسبة إلى واجهات المستخدم المتكيّفة، تعرض عناصر الاحتفاظ بالحالة، مثل عناصر ViewModel، حالة واجهة المستخدم التي تتكيّف مع فئات أحجام النوافذ المختلفة. يمكنك استخدام
currentWindowAdaptiveInfo() لاشتقاق حالة واجهة المستخدم هذه. يمكن بعد ذلك أن تستخدم المكوّنات، مثل
NavigationSuiteScaffold، هذه المعلومات للتبديل تلقائيًا
بين أنماط التنقّل المختلفة (على سبيل المثال، NavigationBar أو NavigationRail أو NavigationDrawer) استنادًا إلى مساحة الشاشة المتاحة.
لمزيد من المعلومات، يُرجى الاطّلاع على طبقة واجهة المستخدم وبنية Compose UI.
لمزيد من المعلومات حول التطبيقات التكيّفية والتنقّل، يُرجى الاطّلاع على إنشاء تطبيقات تكيّفية وإنشاء تنقّل تكيّفي.
طبقة البيانات
تحتوي طبقة البيانات في التطبيق على منطق النشاط التجاري. منطق النشاط التجاري هو ما يمنح تطبيقك قيمته، وهو يتضمّن قواعد تحدّد كيفية إنشاء تطبيقك للبيانات وتخزينها وتغييرها.
تتكوّن طبقة البيانات من مستودعات، يمكن أن يحتوي كلّ منها على صفر إلى العديد من مصادر البيانات. أنشئ فئة مستودع لكل نوع مختلف من البيانات التي تتعامل معها في تطبيقك. على سبيل المثال، يمكنك إنشاء فئة MoviesRepository للبيانات المتعلقة بالأفلام أو فئة PaymentsRepository للبيانات المتعلقة بالدفعات.
تكون فئات المستودع مسؤولة عمّا يلي:
- إتاحة البيانات لبقية التطبيق
- تجميع التغييرات التي تطرأ على البيانات في مكان واحد
- حلّ التعارضات بين مصادر البيانات المتعددة
- تجريد مصادر البيانات من بقية التطبيق
- يحتوي على منطق النشاط التجاري
تتحمّل كل فئة من فئات مصادر البيانات مسؤولية التعامل مع مصدر واحد فقط للبيانات، ويمكن أن يكون هذا المصدر ملفًا أو مصدر شبكة أو قاعدة بيانات محلية. فئات مصادر البيانات هي الرابط بين التطبيق والنظام لتنفيذ عمليات البيانات.
لمزيد من المعلومات، يُرجى الاطّلاع على صفحة "طبقة البيانات".
طبقة النطاق
طبقة النطاق هي طبقة اختيارية بين طبقتَي واجهة المستخدم والبيانات.
تتولّى طبقة النطاق تغليف منطق الأعمال المعقّد أو منطق الأعمال الأبسط الذي يتم إعادة استخدامه من خلال نماذج عرض متعدّدة. طبقة النطاق اختيارية لأنّ بعض التطبيقات لا تتضمّن هذه المتطلبات. استخدِمها فقط عند الحاجة، مثلاً للتعامل مع التعقيد أو تفضيل إمكانية إعادة الاستخدام.
يُطلق على الفئات في طبقة النطاق عادةً اسم حالات الاستخدام أو المتفاعلات.
تكون كل حالة استخدام مسؤولة عن وظيفة واحدة. على سبيل المثال، يمكن أن يتضمّن تطبيقك الفئة GetTimeZoneUseCase إذا كانت نماذج عرض متعددة تعتمد على المناطق الزمنية لعرض الرسالة المناسبة على الشاشة.
لمزيد من المعلومات، يُرجى الاطّلاع على صفحة طبقة النطاق.
إدارة التبعيات بين المكوّنات
تعتمد الفئات في تطبيقك على فئات أخرى لتعمل بشكل صحيح. يمكنك استخدام أحد أنماط التصميم التالية لجمع التبعيات الخاصة بفئة معيّنة:
- إدخال التبعية (DI): يتيح إدخال التبعية للفئات تحديد الاعتماديات بدون إنشائها. في وقت التشغيل، يكون هناك صف آخر مسؤولاً عن توفير هذه التبعيات.
- محدد موقع الخدمة: يوفّر نمط محدد موقع الخدمة سجلّاً يمكن للفئات من خلاله الحصول على التبعيات بدلاً من إنشائها.
تتيح لك هذه الأنماط توسيع نطاق الرمز البرمجي لأنّها توفّر أنماطًا واضحة لإدارة التبعيات بدون تكرار الرمز البرمجي أو إضافة تعقيد. تتيح لك الأنماط أيضًا التبديل بسرعة بين عمليات التنفيذ التجريبية والعلنية.
أفضل الممارسات العامة
البرمجة مجال إبداعي، وتصميم تطبيقات Android ليس استثناءً من ذلك. هناك العديد من الطرق لحلّ المشاكل، فقد تحتاج إلى نقل البيانات بين أنشطة أو أجزاء متعددة، أو استرداد البيانات عن بُعد وحفظها على الجهاز لاستخدامها في وضع عدم الاتصال بالإنترنت، أو التعامل مع أي عدد من السيناريوهات الشائعة الأخرى التي تواجهها التطبيقات غير البسيطة.
على الرغم من أنّ الاقتراحات التالية ليست إلزامية، إلا أنّ اتّباعها في معظم الحالات يجعل قاعدة الرموز البرمجية أكثر فعالية وقابلة للاختبار والصيانة.
لا تخزِّن البيانات في مكوّنات التطبيق.
تجنَّب تحديد نقاط دخول تطبيقك، مثل الأنشطة والخدمات ومستقبِلات البث، كمصادر للبيانات. اجعل نقاط الدخول تتوافق مع المكوّنات الأخرى لاسترداد المجموعة الفرعية من البيانات ذات الصلة بنقطة الدخول هذه فقط. لكل مكوّن من مكونات التطبيق مدة بقاء قصيرة، وذلك حسب تفاعل المستخدم مع الجهاز وسعة النظام.
تقليل الاعتماد على فئات Android:
اجعل مكوّنات تطبيقك هي الفئات الوحيدة التي تعتمد على واجهات برمجة التطبيقات لحزمة تطوير البرامج (SDK) لإطار عمل Android، مثل Context أو Toast. يساعد تجريد الفئات الأخرى في تطبيقك من مكوّنات التطبيق في تسهيل الاختبار ويقلّل من الربط داخل تطبيقك.
تحديد حدود واضحة للمسؤولية بين الوحدات في تطبيقك:
لا توزّع الرمز الذي يحمّل البيانات من الشبكة على عدة فئات أو حِزم في قاعدة الرموز البرمجية. وبالمثل، لا تحدِّد مسؤوليات متعددة غير ذات صلة، مثل تخزين البيانات مؤقتًا وربط البيانات، في الفئة نفسها. اتّبِع بنية التطبيق المقترَحة.
عرض أقل قدر ممكن من كل وحدة
لا تنشئ اختصارات تعرض تفاصيل التنفيذ الداخلية. قد توفّر بعض الوقت على المدى القصير، ولكن من المحتمل أن تتراكم عليك ديون فنية عدة مرات مع تطوّر قاعدة الرموز البرمجية.
ركِّز على الميزات الأساسية الفريدة في تطبيقك لتميّزه عن التطبيقات الأخرى.
لا تُكرّر كتابة الرمز البرمجي النموذجي نفسه مرارًا وتكرارًا. بدلاً من ذلك، ركِّز وقتك وجهدك على ما يميّز تطبيقك عن غيره. دع مكتبات Jetpack والمكتبات الأخرى المقترَحة تتولّى معالجة الرموز النمطية المتكرّرة.
استخدام التنسيقات الأساسية وأنماط تصميم التطبيقات:
توفّر مكتبات Jetpack Compose واجهات برمجة تطبيقات قوية لإنشاء واجهات مستخدم متكيّفة. استخدِم التنسيقات الأساسية في تطبيقك لتحسين تجربة المستخدم على أشكال وأحجام عرض متعددة. راجِع معرض أنماط تصميم التطبيقات لاختيار التنسيقات الأنسب لحالات الاستخدام.
الاحتفاظ بحالة واجهة المستخدم عند إجراء تغييرات في الإعدادات:
عند تصميم تنسيقات متجاوبة، يجب الحفاظ على حالة واجهة المستخدم عند إجراء تغييرات في الإعدادات، مثل تغيير حجم الشاشة والطي وتغيير اتجاه الشاشة. يجب أن تتأكّد البنية من الحفاظ على حالة المستخدم الحالية، ما يوفّر تجربة سلسة.
تصميم مكوّنات واجهة مستخدم قابلة لإعادة الاستخدام والدمج:
إنشاء مكوّنات واجهة مستخدم قابلة لإعادة الاستخدام والدمج من أجل توفير تصميم متكيّف يتيح لك ذلك دمج المكوّنات وإعادة ترتيبها لتناسب أحجام الشاشات وأوضاعها المختلفة بدون الحاجة إلى إعادة تصميم كبيرة.
فكِّر في كيفية جعل كل جزء من تطبيقك قابلاً للاختبار بشكل منفصل.
تسهّل واجهة برمجة التطبيقات المحدّدة جيدًا لجلب البيانات من الشبكة اختبار الوحدة التي تحتفظ بهذه البيانات في قاعدة بيانات محلية. في المقابل، إذا جمعت منطق هاتين الدالتين في مكان واحد، أو وزّعت رمز الشبكة على قاعدة الرموز البرمجية بأكملها، سيصبح الاختبار أكثر صعوبة، إن لم يكن مستحيلاً.
تكون الأنواع مسؤولة عن سياسة التزامن.
إذا كان أحد الأنواع ينفّذ عملية حظر طويلة الأمد، يجب أن يكون هذا النوع مسؤولاً عن نقل عملية الحساب هذه إلى سلسلة التعليمات المناسبة. يعرف النوع نوع الحساب الذي يتم إجراؤه والمؤشر الذي سيتم تشغيل الحساب فيه. يجب أن تكون الأنواع آمنة للاستخدام في سلسلة التعليمات الرئيسية، أي يمكن استدعاؤها من سلسلة التعليمات الرئيسية بدون حظرها.
الاحتفاظ بأكبر قدر ممكن من البيانات الحديثة وذات الصلة:
بهذه الطريقة، يمكن للمستخدمين الاستفادة من وظائف تطبيقك حتى عندما يكون الجهاز في وضع عدم الاتصال بالإنترنت. تذكَّر أنّ بعض المستخدمين لا يمكنهم الاستفادة من اتصال سريع ومستمر بالإنترنت، وحتى إذا كان ذلك متاحًا لهم، قد لا يتمكنون من الحصول على قوّة إشارة جيدة في الأماكن المزدحمة.
مزايا البنية
إنّ تنفيذ بنية جيدة في تطبيقك يحقّق الكثير من المزايا لفِرق المشروع والهندسة، ومنها:
- تحسين قابلية الصيانة والجودة والمتانة للتطبيق بشكل عام
- يسمح هذا الإذن للتطبيق بتغيير حجمه. يمكن لعدد أكبر من المستخدمين والمجموعات المساهمة في قاعدة الرموز البرمجية نفسها مع الحد الأدنى من تعارضات الرموز البرمجية.
- تساعد في عملية الإعداد. وبما أنّ التصميم المعماري يوفّر الاتساق في مشروعك، يمكن لأعضاء الفريق الجدد التعرّف على المشروع بسرعة أكبر وتحقيق كفاءة أعلى في وقت أقل.
- تسهيل الاختبار تشجّع البنية الجيدة على استخدام أنواع أبسط يسهل اختبارها بشكل عام.
- تتيح لك هذه الميزة التحقيق في الأخطاء بشكل منهجي من خلال عمليات محددة جيدًا.
مع أنّ التصميم الجيد يتطلّب استثمارًا مسبقًا للوقت، إلا أنّه يؤثّر أيضًا بشكل مباشر في المستخدمين. ويستفيدون من تطبيق أكثر استقرارًا وميزات أكثر بفضل فريق هندسي أكثر إنتاجية.
نماذج
توضّح النماذج التالية بنية التطبيق الجيدة: