توصيات لبنية Android

تعرض هذه الصفحة العديد من أفضل الممارسات والاقتراحات المتعلّقة بالبنية. استخدِمها لتحسين جودة تطبيقك وموثوقيته وقابليته للتوسّع. كما تسهّل عليك هذه الأدوات صيانة تطبيقك واختباره.

تم تجميع أفضل الممارسات أدناه حسب الموضوع. لكل منها أولوية تعكس مدى أهمية الاقتراح، وفي ما يلي قائمة الأولويات:

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

البنية المتعدّدة الطبقات

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

الاقتراح الوصف
استخدِم طبقة بيانات محدّدة بوضوح. تعرض طبقة البيانات بيانات التطبيق لبقية التطبيق، وتحتوي على الغالبية العظمى من منطق النشاط التجاري لتطبيقك.
  • أنشئ مستودعات حتى إذا كانت تحتوي على مصدر بيانات واحد فقط.
  • في التطبيقات الصغيرة، يمكنك اختيار وضع أنواع طبقة البيانات في حزمة أو وحدة data.
استخدِم طبقة واجهة مستخدم محدّدة بوضوح. تعرض طبقة واجهة المستخدِم بيانات التطبيق على الشاشة وتشكّل نقطة التفاعل الأساسية للمستخدم. Jetpack Compose هي مجموعة الأدوات الحديثة المقترَحة لإنشاء واجهة مستخدم تطبيقك.
  • في التطبيقات الصغيرة، يمكنك اختيار وضع أنواع طبقة البيانات في حزمة أو وحدة ui.
لمزيد من المعلومات حول أفضل الممارسات المتعلّقة بطبقة واجهة المستخدم، يُرجى الاطّلاع على طبقة واجهة المستخدم.
عرض بيانات التطبيق من طبقة البيانات باستخدام مستودع

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

  • قواعد البيانات وDataStore وSharedPreferences وواجهات برمجة التطبيقات في Firebase
  • مقدّمو خدمات الموقع الجغرافي في نظام تحديد المواقع العالمي (GPS)
  • مزوّدو بيانات البلوتوث
  • مقدّمو خدمات حالة الاتصال بالشبكة
استخدِم الروتينات المشتركة والتدفقات. استخدِم الروتينات المشتركة والتدفقات للتواصل بين الطبقات.

لمزيد من المعلومات حول أفضل الممارسات المتعلّقة بالكوروتينات، يُرجى الاطّلاع على أفضل الممارسات المتعلّقة بالكوروتينات في Android.

استخدِم طبقة نطاق. استخدِم طبقة النطاق مع حالات الاستخدام إذا كنت بحاجة إلى إعادة استخدام منطق النشاط التجاري الذي يتفاعل مع طبقة البيانات في عدة ViewModels، أو إذا كنت تريد تبسيط تعقيد منطق النشاط التجاري في ViewModel معيّن.

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

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

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

لمزيد من المعلومات عن أفضل الممارسات المتعلّقة بـ ViewModel، راجِع اقتراحات حول بنية التطبيق.

لمزيد من المعلومات حول مزايا ViewModels، يُرجى الاطّلاع على مقالة ViewModel كعنصر الاحتفاظ بالحالة لمنطق النشاط التجاري.

استخدام ميزة جمع حالات واجهة المستخدم المتوافقة مع مراحل النشاط اجمع حالة واجهة المستخدم من واجهة المستخدم باستخدام أداة إنشاء الكوروتينات المناسبة المدرِكة لدورة الحياة، collectAsStateWithLifecycle.

مزيد من المعلومات حول collectAsStateWithLifecycle

لا ترسِل الأحداث من ViewModel إلى واجهة المستخدم. معالجة الحدث على الفور في ViewModel وتعديل الحالة باستخدام نتيجة معالجة الحدث لمزيد من المعلومات عن أحداث واجهة المستخدم، اطّلِع على مقالة التعامل مع أحداث ViewModel.
استخدِم تطبيقًا بنشاط واحد. استخدِم التنقّل 3 للتنقّل بين الشاشات والربط بصفحات في تطبيقك إذا كان تطبيقك يتضمّن أكثر من شاشة واحدة.
استخدِم Jetpack Compose. استخدِم Jetpack Compose لإنشاء تطبيقات جديدة للهواتف والأجهزة اللوحية والأجهزة القابلة للطي وأجهزة Wear OS.

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

  @Composable
  fun MyScreen(
      viewModel: MyViewModel = viewModel()
  ) {
      val uiState by viewModel.uiState.collectAsStateWithLifecycle()
  }

ViewModel

تتولّى ViewModels مسؤولية توفير حالة واجهة المستخدم والوصول إلى طبقة البيانات. في ما يلي بعض أفضل الممارسات المتعلّقة بـ ViewModels:

الاقتراح الوصف
يجب أن تكون ViewModels مستقلة عن دورة حياة Android. في ViewModels، لا تحتفظ بمرجع لأي نوع ذي صلة بدورة الحياة. لا تمرِّر Activity أو Context أو Resources كعنصر تابع. إذا كان هناك شيء يحتاج إلى Context في ViewModel، عليك تقييم ما إذا كان ذلك في الطبقة الصحيحة بعناية.
استخدِم الروتينات المشتركة والتدفقات.

يتفاعل ViewModel مع طبقات البيانات أو النطاقات باستخدام ما يلي:

  • مسارات Kotlin لتلقّي بيانات التطبيق
  • دوال suspend لتنفيذ الإجراءات باستخدام viewModelScope
استخدِم ViewModels على مستوى الشاشة.

لا تستخدِم ViewModels في أجزاء واجهة المستخدم القابلة لإعادة الاستخدام. يجب استخدام ViewModels في ما يلي:

  • عناصر قابلة للإنشاء على مستوى الشاشة
  • الوجهات أو الرسوم البيانية عند استخدام Jetpack Navigation

بالنسبة إلى العناصر القابلة للإنشاء الأكثر تعقيدًا أو تلك التي تتضمّن سلوكًا ديناميكيًا استنادًا إلى الحالة، استخدِم rememberViewModelStoreOwner() لتحديد نطاق ViewModel مباشرةً إلى موقع استدعاء العنصر القابل للإنشاء.

استخدِم عناصر الاحتفاظ بالحالة العادية في مكوّنات واجهة المستخدم القابلة لإعادة الاستخدام. استخدِم عناصر الاحتفاظ بالحالة العادية للتعامل مع التعقيد في مكوّنات واجهة المستخدم القابلة لإعادة الاستخدام. عند إجراء ذلك، يمكن نقل الحالة والتحكّم فيها خارجيًا.
لا تستخدِم AndroidViewModel. استخدِم فئة ViewModel، وليس AndroidViewModel. لا تستخدِم الفئة Application في ViewModel. بدلاً من ذلك، يمكنك نقل العنصر التابع إلى واجهة المستخدِم أو طبقة البيانات.
عرض حالة واجهة المستخدم اجعل ViewModels تعرض البيانات لواجهة المستخدم من خلال سمة واحدة تُسمى uiState. إذا كانت واجهة المستخدم تعرض عدة أجزاء من البيانات غير المرتبطة، يمكن أن تعرض الآلة الافتراضية عدة سمات لحالة واجهة المستخدم.
  • اجعل uiState StateFlow.
  • أنشئ uiState باستخدام عامل التشغيل stateIn مع السياسة WhileSubscribed(5000) إذا كانت البيانات تأتي كتدفق بيانات من طبقات أخرى من التدرّج الهرمي. (راجِع مثال الرمز البرمجي هذا).
  • في الحالات الأبسط التي لا تتضمّن مصادر بيانات واردة من طبقة البيانات، يمكن استخدام MutableStateFlow المعروضة على أنّه StateFlow غير قابل للتغيير.
  • يمكنك اختيار ${Screen}UiState كفئة بيانات يمكن أن تحتوي على بيانات وأخطاء وإشارات تحميل. يمكن أن يكون هذا الصف أيضًا صفًا محكمًا إذا كانت الحالات المختلفة حصرية.

يوضّح المقتطف التالي كيفية عرض حالة واجهة المستخدم من ViewModel:

@HiltViewModel
class BookmarksViewModel @Inject constructor(
    newsRepository: NewsRepository
) : ViewModel() {

    val feedState: StateFlow<NewsFeedUiState> =
        newsRepository
            .getNewsResourcesStream()
            .mapToFeedState(savedNewsResourcesState)
            .stateIn(
                scope = viewModelScope,
                started = SharingStarted.WhileSubscribed(5_000),
                initialValue = NewsFeedUiState.Loading
            )

    // ...
}

مراحل النشاط

اتّبِع أفضل الممارسات للتعامل مع دورة حياة النشاط:

الاقتراح الوصف
استخدِم التأثيرات التي تراعي مراحل النشاط في العناصر القابلة للإنشاء بدلاً من إلغاء عمليات معاودة الاتصال بدورة الحياة Activity.

لا تستبدِل طرق دورة حياة Activity، مثل onResume، لتنفيذ مهام متعلقة بواجهة المستخدم. بدلاً من ذلك، استخدِم LifecycleEffects في Compose أو نطاقات الروتينات المشتركة التي تراعي مراحل النشاط:

  • استخدِم LifecycleStartEffect لتنفيذ عمل متزامن عند بدء نشاطك وإيقافه.
  • استخدِم LifecycleResumeEffect لتنفيذ العمل المتزامن عند استئناف نشاطك وإيقافه مؤقتًا.
  • استخدِم repeatOnLifecycle لتنفيذ عمل غير متزامن استجابةً لأحداث مراحل النشاط.
  • جمع البيانات غير المتزامنة من "عمليات سير العمل" باستخدام collectAsStateWithLifecycle

يوضّح المقتطف التالي كيفية تنفيذ العمليات في حالة Lifecycle معيّنة:

  @Composable
  fun LocationChangedEffect(
    locationManager: LocationManager,
    onLocationChanged: (Location) -> Unit
  ) {
    val currentOnLocationChanged by rememberUpdatedState(onLocationChanged)

    LifecycleStartEffect(locationManager) {
        val listener = LocationListener { newLocation ->
            currentOnLocationChanged(newLocation)
        }

        try {
            locationManager.requestLocationUpdates(
                LocationManager.GPS_PROVIDER,
                1000L,
                1f,
                listener,
            )
        } catch (e: SecurityException) {
            // TODO: Handle missing permissions
        }

        onStopOrDispose {
            locationManager.removeUpdates(listener)
        }
    }
  }

التعامل مع التبعيات

اتّبِع أفضل الممارسات عند إدارة التبعيات بين المكوّنات:

الاقتراح الوصف
استخدِم إدخال التبعية. استخدِم أفضل ممارسات تضمين التبعية، وخاصةً تضمين الدالة الإنشائية عند الإمكان.
استخدِم نطاقًا لمكوّن معيّن عند الضرورة. يجب تحديد نطاق حاوية التبعية عندما يحتوي النوع على بيانات قابلة للتغيير يجب مشاركتها أو عندما يكون النوع مكلفًا في عملية التهيئة ويتم استخدامه على نطاق واسع في التطبيق.
استخدِم Hilt. استخدِم Hilt أو إدخال التبعية يدويًا في التطبيقات البسيطة. استخدِم Hilt إذا كان مشروعك معقّدًا بما يكفي، مثلاً إذا كان يتضمّن أيًا مما يلي:
  • شاشات متعدّدة باستخدام ViewModels
  • استخدام WorkManager
  • يحتوي على ViewModels محدّدة النطاق في سجلّ التنقّل السابق

الاختبار

في ما يلي بعض أفضل الممارسات المتعلّقة بالاختبار:

الاقتراح الوصف
تحديد ما يجب اختباره:

ما لم يكن المشروع بسيطًا مثل تطبيق "hello world"، اختبِره. يجب تضمين ما يلي على الأقل:

  • اختبارات الوحدة لـ ViewModels، بما في ذلك Flows
  • اختبارات الوحدات لكيانات طبقة البيانات، أي المستودعات ومصادر البيانات
  • اختبارات التنقّل في واجهة المستخدم التي تكون مفيدة كاختبارات الانحدار في عملية التكامل المستمر
  • اختبارات الأداء باستخدام Macrobenchmark لقياس وقت بدء تشغيل التطبيق وعرض واجهة المستخدم
يجب تفضيل عمليات المحاكاة على عمليات التزوير. لمزيد من المعلومات حول استخدام العناصر الوهمية، يُرجى الاطّلاع على استخدام العناصر الوهمية في Android.
اختبار StateFlows عند اختبار StateFlow، اتّبِع الخطوات التالية:

لمزيد من المعلومات، يُرجى الاطّلاع على ما يجب اختباره في Android واختبار تصميم Compose.

الأداء

في ما يلي بعض أفضل الممارسات لتحسين الأداء:

الاقتراح الوصف
استخدِم R8 لتقليل حجم تطبيقك وتحسينه. فعِّل R8 في الوضع الكامل في إعدادات التصميم لإزالة الرموز البرمجية غير المستخدَمة وتشويش الفئات وإعادة كتابة الرموز البرمجية لتحسين أداء وقت التشغيل.
استخدام "ملفات تعريف بدء التشغيل" و"ملفات المرجع" إنشاء ملفات شخصية للمرجع وملفات تعريف بدء التشغيل لتجميع مسارات الرموز البرمجية المهمة مسبقًا يؤدي ذلك إلى تقليل وقت استجابة بدء التشغيل (TTID وTTFD) بشكل كبير والحدّ من أخطاء ANR والتشوّش.
اختبار الأداء باستخدام Macrobenchmark استخدِم مكتبة Macrobenchmark لاختبار أداء تطبيقك ومنع حدوث أخطاء.

النماذج

اتّبِع أفضل الممارسات التالية عند تطوير النماذج في تطبيقاتك:

الاقتراح الوصف
إنشاء نموذج لكل طبقة في التطبيقات المعقّدة

في التطبيقات المعقّدة، أنشِئ نماذج جديدة في طبقات أو مكوّنات مختلفة عندما يكون ذلك منطقيًا. في ما يلي أمثلة:

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

اصطلاحات التسمية

عند تسمية قاعدة الرموز البرمجية، يجب مراعاة أفضل الممارسات التالية:

الاقتراح الوصف
طُرق التسمية
اختيارية
استخدِم عبارات فعلية لتسمية الطرق، مثل makePayment().
تسمية السمات
اختيارية
استخدِم عبارات اسمية لتسمية السمات، مثل inProgressTopicSelection.
تسمية مصادر البيانات
اختيارية
عندما تعرض فئة مصدر بيانات Flow أو أي مصدر بيانات آخر، يكون اصطلاح التسمية هو get{model}Stream، على سبيل المثال، getAuthorStream(): Flow<Author>. إذا كانت الدالة تعرض قائمة بالنماذج، استخدِم اسم النموذج بصيغة الجمع: getAuthorsStream(): Flow<List<Author>>.
تنفيذ الواجهات التي تحمل أسماء
اختيارية
استخدِم أسماء ذات دلالة لعمليات تنفيذ الواجهات. استخدِم Default كبادئة إذا لم تتمكّن من العثور على اسم أفضل. على سبيل المثال، بالنسبة إلى واجهة NewsRepository، قد يكون لديك OfflineFirstNewsRepository أو InMemoryNewsRepository. إذا لم تتمكّن من العثور على اسم مناسب، استخدِم DefaultNewsRepository. ابدأ عمليات التنفيذ الوهمية بالبادئة Fake، كما في FakeAuthorsRepository.

مراجع إضافية

لمزيد من المعلومات حول بنية Android، يُرجى الاطّلاع على المراجع الإضافية التالية:

الوثائق

مشاهدة المحتوى