دليل بنية التطبيق

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

تجارب مستخدم التطبيق المتوافق مع الأجهزة الجوّالة

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

ضع في اعتبارك أن الأجهزة المحمولة محدودة الموارد أيضًا، لذلك في أي وقت، قد يوقف نظام التشغيل بعض عمليات التطبيق لإفساح المجال أمام عمليات جديدة.

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

المبادئ المعمارية المشتركة

إذا كان لا يجب عليك استخدام مكونات التطبيق لتخزين بيانات التطبيق وحالته، كيف ينبغي لك تصميم تطبيقك بدلاً من ذلك؟

مع زيادة حجم تطبيقات Android، من المهم تحديد بنية تسمح للتطبيق بالتوسّع، وتزيد من متانة التطبيق، وتسهل اختباره.

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

الفصل بين المخاوف

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

يُرجى العِلم أنّك لا تملك عمليات تنفيذ للسمتَين Activity وFragment، بل إنّها ليست سوى فئتَين ملتصقتَين تمثّلان العقد بين نظام التشغيل Android والتطبيق. ويمكن لنظام التشغيل إتلاف هذه التطبيقات في أي وقت استنادًا إلى تفاعلات المستخدمين أو بسبب ظروف النظام، مثل انخفاض الذاكرة. لتوفير تجربة مستخدم مُرضية وتجربة صيانة للتطبيق أكثر قابلية للإدارة، من الأفضل تقليل اعتمادك عليها.

واجهة مستخدم Drive من نماذج البيانات

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

تعتبر النماذج المستمرة مثالية للأسباب التالية:

  • لن يفقد المستخدمون أي بيانات إذا أتلف نظام Android تطبيقك لإخلاء الموارد.

  • يستمر التطبيق في العمل في الحالات التي يكون فيها الاتصال بالشبكة غير مستقر أو غير متاح.

إذا بنيت بنية تطبيقك على فئات نماذج البيانات، فإنك تجعل تطبيقك أكثر قابلية للاختبار وقوة.

مصدر واحد للحقيقة

عند تحديد نوع بيانات جديد في تطبيقك، يجب تعيين مصدر واحد للحقيقة (SSOT) له. جدير بالذكر أنّ خدمة الدخول الموحَّد (SSOT) هي مالك هذه البيانات، ولا يمكن سوى لخدمة الدخول المُوحَّد (SSOT) تعديلها أو تغييرها. لتحقيق ذلك، تعرض خدمة الدخول الموحَّد (SSOT) البيانات باستخدام نوع غير قابل للتغيير، ولتعديل البيانات، تعرض خدمة الدخول الموحَّد (SSOT) الدوال أو استقبال أحداث يمكن للأنواع الأخرى استدعاءها.

يوفر هذا النمط فوائد متعددة:

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

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

تدفق بيانات أحادي الاتجاه

غالبًا ما يُستخدم مبدأ المصدر الوحيد للحقيقة في أدلةنا مع نمط تدفق البيانات أحادي الاتجاه (UDF). في UDF، تتدفق الحالة في اتجاه واحد فقط. الأحداث التي تعدِّل تدفق البيانات في الاتجاه المقابل.

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

يضمن هذا النمط بشكل أفضل اتساق البيانات، وأقل عرضة للأخطاء، ويسهل تصحيح الأخطاء، ويوفر جميع مزايا نمط تسجيل الدخول الأحادي (SSOT).

يوضّح هذا القسم كيفية تنظيم بنية تطبيقك باتّباع أفضل الممارسات الموصى بها.

بالنظر إلى المبادئ المعمارية الشائعة المذكورة في القسم السابق، يجب أن يحتوي كل تطبيق على طبقتين على الأقل:

  • طبقة واجهة المستخدم التي تعرض بيانات التطبيق على الشاشة.
  • طبقة البيانات التي تحتوي على منطق العمل لتطبيقك وتكشف بيانات التطبيق.

يمكنك إضافة طبقة إضافية تُسمى طبقة النطاق لتبسيط وإعادة استخدام التفاعلات بين واجهة المستخدم وطبقات البيانات.

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

بنية التطبيق الحديثة

تحث بنية التطبيق الحديثة على استخدام الأساليب التالية، من بين أساليب أخرى:

  • بنية تفاعلية ومتعددة الطبقات.
  • تدفق البيانات أحادي الاتجاه (UDF) في جميع طبقات التطبيق
  • طبقة واجهة مستخدم تضم أصحاب الحالات لإدارة مدى تعقيد واجهة المستخدم.
  • الكوروتين والتدفقات.
  • أفضل ممارسات حقن التبعية.

لمزيد من المعلومات، يُرجى الاطّلاع على الأقسام التالية وصفحات "البنية" الأخرى في جدول المحتويات وصفحة الاقتراحات التي تتضمن ملخّصًا لأهم الممارسات الأكثر أهمية.

طبقة واجهة المستخدم

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

تتكون طبقة واجهة المستخدم من شيئين:

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

لمعرفة المزيد من المعلومات عن هذه الطبقة، يمكنك الاطّلاع على صفحة طبقة واجهة المستخدم.

طبقة البيانات

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

تتألف طبقة البيانات من مستودعات يمكن أن يحتوي كل منها على عدد يتراوح بين الصفر والكثير من مصادر البيانات. يجب إنشاء فئة مستودع لكل نوع مختلف من البيانات التي تعالجها في تطبيقك. على سبيل المثال، يمكنك إنشاء فئة MoviesRepository للبيانات المتعلقة بالأفلام، أو فئة PaymentsRepository للبيانات المتعلقة بالدفعات.

في البنية النموذجية، توفّر مستودعات طبقة البيانات البيانات
    إلى باقي التطبيق وتعتمد على مصادر البيانات.
الشكل 3. دور طبقة البيانات في بنية التطبيق.

تكون فصول المستودعات مسؤولة عن المهام التالية:

  • عرض البيانات لبقية التطبيق.
  • مركزية التغييرات على البيانات.
  • حل التعارضات بين مصادر بيانات متعددة.
  • استخلاص مصادر البيانات من بقية التطبيق.
  • يتضمن منطق العمل.

يجب أن تتحمل كل فئة من فئات مصدر البيانات مسئولية العمل مع مصدر واحد فقط للبيانات، والذي يمكن أن يكون ملفًا أو مصدر شبكة أو قاعدة بيانات محلية. فئات مصادر البيانات هي جسر بين التطبيق والنظام لعمليات البيانات.

لمعرفة المزيد من المعلومات عن هذه الطبقة، اطّلِع على صفحة طبقة البيانات.

طبقة النطاق

طبقة النطاق هي طبقة اختيارية تقع بين واجهة المستخدم وطبقات البيانات.

تكون طبقة النطاق مسؤولة عن تغليف منطق العمل المعقد أو منطق الأعمال البسيط الذي يُعاد استخدامه من خلال نماذج عرض متعددة. هذه الطبقة اختيارية لأنه لن تكون هذه المتطلبات لجميع التطبيقات. يجب عليك استخدامه فقط عند الحاجة - على سبيل المثال، للتعامل مع التعقيد أو أفضلية إعادة الاستخدام.

وعند تضمين هذه الطبقة، توفّر طبقة النطاق الاختيارية تبعيات لطبقة واجهة المستخدم وتعتمد على طبقة البيانات.
الشكل 4. دور طبقة النطاق في بنية التطبيق.

ويُطلق على الفئات في هذه الطبقة اسم حالات الاستخدام أو التفاعلات. ينبغي أن تتحمل كل حالة استخدام مسؤولية وظيفة واحدة. على سبيل المثال، يمكن أن يشتمل تطبيقك على فئة GetTimeZoneUseCase إذا كانت نماذج ViewModels متعددة تعتمد على المناطق الزمنية لعرض الرسالة المناسبة على الشاشة.

لمزيد من المعلومات عن هذه الطبقة، راجِع صفحة طبقة النطاق.

إدارة التبعيات بين المكونات

تعتمد الصفوف في تطبيقك على فئات أخرى لكي تعمل بشكل صحيح. يمكنك استخدام أي من أنماط التصميم التالية لجمع تبعيات فئة معينة:

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

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

ننصحك باتّباع أنماط حقن الاعتماد واستخدام مكتبة Hilt في تطبيقات Android. ينشئ Hilt العناصر تلقائيًا من خلال اتّباع شجرة التبعية، ويوفّر ضمات وقت التجميع على التبعيات، كما ينشئ حاويات التبعية لفئات إطار عمل Android.

أفضل الممارسات العامة

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

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

لا تخزِّن البيانات في مكوّنات التطبيق.

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

تقليل الاعتمادية على صفوف Android:

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

يجب وضع حدود واضحة للمسؤولية بين الوحدات المختلفة في تطبيقك.

على سبيل المثال، لا تنشر الرمز الذي يحمِّل البيانات من الشبكة على مستوى عدة فئات أو حِزم في قاعدة التعليمات البرمجية. وبالمثل، لا تحدد المسئوليات المتعددة غير ذات الصلة - مثل التخزين المؤقت للبيانات وربط البيانات - في نفس الفئة. وسيساعدك اتباع بنية التطبيق المقترحة في ذلك.

اعرض أقل قدر ممكن من كل وحدة.

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

ركِّز على جوهر تطبيقك الفريد حتى يتميّز عن التطبيقات الأخرى.

يجب عدم إعادة تصميم العجلة من خلال كتابة الرمز النموذجي نفسه مرارًا وتكرارًا. وبدلاً من ذلك، ركِّز على وقتك وطاقتك لما يجعل تطبيقك فريدًا من نوعه، واسمح لمكتبات Jetpack والمكتبات الأخرى المقترحة بالتعامل مع النص النموذجي المتكرر.

فكِّر في كيفية جعل كل جزء من تطبيقك قابلاً للاختبار بشكل منفصل.

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

تكون الأنواع مسؤولة عن سياسة التزامن الخاصة بها.

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

الإبقاء على أكبر قدر ممكن من البيانات الجديدة وذات الصلة بالموضوع.

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

فوائد الهندسة المعمارية

إن تنفيذ بنية جيدة في تطبيقك يجلب الكثير من الفوائد لفرق المشروع والهندسة:

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

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

عيّنات

توضح نماذج Google التالية بنية جيدة للتطبيق. انتقل إلى استكشافها لرؤية هذا الإرشادات عمليًا: