أوجه انتباهكم إلى ترجمة المقال الأصلي لجيمي سانسون

إنشاء نشاط قبل Android 9 Pie
حقن التبعية (DI) هو نموذج شائع يستخدم في جميع أشكال التنمية لعدد من الأسباب. بفضل مشروع Dagger ، يتم استخدامه كقالب مستخدم في التطوير لنظام Android. التغييرات الأخيرة التي طرأت على Android 9 Pie جعلتنا أكثر قدرة عندما يتعلق الأمر AppComponentFactory
DI ، خاصة مع فئة AppComponentFactory
الجديدة.
تعتبر شركة DI مهمة جدًا عندما يتعلق الأمر بالتطور الحديث لنظام Android. يسمح لك هذا بتقليل إجمالي الكود عند الحصول على روابط للخدمات المستخدمة بين الفئات ، وعمومًا ما يقسم التطبيق إلى مكونات. في هذه المقالة ، سوف نركز على Dagger 2 ، مكتبة DI الأكثر شيوعًا المستخدمة في تطوير Android. من المفترض أن لديك بالفعل معرفة أساسية بكيفية عمل ذلك ، ولكن ليس من الضروري أن تفهم كل التفاصيل الدقيقة. تجدر الإشارة إلى أن هذه المقالة هي بعض المغامرة. هذا مثير للاهتمام وكل شيء ، ولكن في وقت كتابة هذا التقرير ، لم يظهر Android 9 Pie حتى على لوحة إصدار النظام الأساسي ، لذلك ربما لن يكون هذا الموضوع ذا صلة بالتطوير اليومي لعدة سنوات على الأقل.
حقن التبعية في Android اليوم
ببساطة ، نحن نستخدم DI لتوفير حالات الطبقات "التبعية" لفئاتنا التابعة ، أي تلك التي تقوم بالعمل. دعنا نفترض أننا نستخدم نموذج مستودع التخزين لمعالجة منطقنا المتعلق بالبيانات ، ونريد استخدام مستودعنا في النشاط لإظهار بعض البيانات للمستخدم. قد نرغب في استخدام نفس المستودع في عدة أماكن ، لذلك نستخدم حقن التبعية لتسهيل مشاركة نفس الحالة بين مجموعة من الفئات المختلفة.
أولاً ، سنوفر مستودعًا. سنقوم بتعريف وظيفة Provides
في الوحدة النمطية ، مما يسمح لخنجر أن يعرف أن هذا هو بالضبط المثال الذي نريد تنفيذه. يرجى ملاحظة أن مستودعنا يحتاج إلى نسخة سياق للعمل مع الملفات والشبكة. سنزودها بسياق التطبيق.
@Module class AppModule(val appContext: Context) { @Provides @ApplicationScope fun provideApplicationContext(): Context = appContext @Provides @ApplicationScope fun provideRepository(context: Context): Repository = Repository(context) }
نحتاج الآن إلى تعريف Component
للتعامل مع تنفيذ الفئات التي نريد أن نستخدم فيها Repository
.
@ApplicationScope @Component(modules = [AppModule::class]) interface ApplicationComponent { fun inject(activity: MainActivity) }
أخيرًا ، يمكننا تهيئة نشاطنا لاستخدام مستودعنا. افترض أننا أنشأنا مثيل ApplicationComponent
بنا في مكان آخر.
class MainActivity: AppCompatActivity() { @Inject lateinit var repository: Repository override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState)
هذا كل شئ! أنشأنا فقط حقن التبعية داخل التطبيق باستخدام خنجر. هناك عدة طرق للقيام بذلك ، ولكن هذا يبدو أسهل طريقة.
ما هو الخطأ في النهج الحالي؟
في الأمثلة المذكورة أعلاه ، رأينا نوعين مختلفين من الحقن ، أحدهما أكثر وضوحًا من الآخر.
يُعرف الأول الذي ربما فاتته التضمين في المُنشئ . هذه طريقة لتوفير التبعيات من خلال مُنشئ الفصل ، مما يعني أن الفصل الذي يستخدم التبعيات ليس لديه فكرة عن أصل الحالات. يعتبر هذا أنقى شكل من أشكال حقن التبعية ، حيث أنه يلخص منطق الحقن الخاص بنا في فئات Module
التعليمية لدينا بشكل مثالي. في مثالنا ، استخدمنا هذا النهج لتوفير مستودع:
fun provideRepository(context: Context): Repository = Repository(context)
لهذا كنا بحاجة إلى Context
، والذي قدمناه في وظيفة provideApplicationContext()
.
الشيء الثاني والأكثر وضوحًا ، الذي رأيناه هو تنفيذ الفصل في هذا المجال . تم استخدام هذه الطريقة في MainActivity
لدينا لتوفير متجرنا. نحدد هنا الحقول كمتلقين للحقن باستخدام الشرح Inject
. بعد ذلك ، في وظيفة onCreate
بنا onCreate
نخبر ApplicationComponent
أنه يجب حقن التبعيات في حقولنا. لا يبدو نظيفًا مثل التضمين في مُنشئ ، لأن لدينا إشارة صريحة إلى المكون الخاص بنا ، مما يعني أن مفهوم التضمين يتسرب إلى فصولنا التابعة. عيب آخر في فئات Android Framework ، حيث نحتاج إلى التأكد من أن أول شيء نقوم به هو توفير التبعيات. إذا حدث هذا عند نقطة الخطأ في دورة الحياة ، فقد نحاول بطريق الخطأ استخدام كائن لم تتم تهيئته بعد.
من الناحية المثالية ، يجب أن تتخلص تمامًا من التطبيقات في حقول الفصل. يتخطى هذا النهج معلومات التنفيذ للفئات التي لا تحتاج إلى معرفتها ، ويمكن أن تسبب مشاكل في دورة الحياة. لقد رأينا محاولات للقيام بذلك بشكل أفضل ، و Dagger على Android هو وسيلة موثوقة للغاية ، ولكن في النهاية سيكون من الأفضل لو تمكنا من استخدام التضمين في المُنشئ. حاليًا ، لا يمكننا استخدام هذا النهج لعدد من فئات الإطار ، مثل "النشاط" ، "الخدمة" ، "التطبيق" ، إلخ ، حيث يتم إنشاؤها من قبل النظام لدينا. يبدو أننا في الوقت الحالي عالقون بشأن إدخال الدروس في الحقول. ومع ذلك ، يقوم Android 9 Pie بإعداد شيء مثير للاهتمام ، والذي ربما يغير كل شيء بشكل أساسي.
حقن التبعية في Android 9 Pie
كما هو مذكور في بداية المقالة ، يحتوي Android 9 Pie على فئة AppComponentFactory. الوثائق الخاصة به نادرة إلى حد ما ، ويتم نشرها ببساطة على موقع المطور على هذا النحو:
الواجهة المستخدمة للتحكم في إنشاء عناصر البيان.
إنه فضول. تشير "عناصر البيان" هنا إلى الفئات التي AndroidManifest
ملف AndroidManifest
بنا - مثل النشاط والخدمة وفئة التطبيق لدينا. يسمح لنا هذا "بالتحكم في إنشاء" هذه العناصر ... لذلك ، مهلا ، هل يمكننا الآن وضع قواعد لإنشاء أنشطتنا؟ ياله من فرحة!
دعونا حفر أعمق. سنبدأ من خلال توسيع AppComponentFactory
وتجاوز أسلوب instantiateActivity
.
class InjectionComponentFactory: AppComponentFactory() { private val repository = NonContextRepository() override fun instantiateActivity(cl: ClassLoader, className: String, intent: Intent?): Activity { return when { className == MainActivity::class.java.name -> MainActivity(repository) else -> super.instantiateActivity(cl, className, intent) } } }
نحتاج الآن إلى إعلان مصنع المكونات الخاص بنا في البيان الموجود داخل علامة التطبيق .
<application android:allowBackup="true" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:name=".InjectionApp" android:appComponentFactory="com.mypackage.injectiontest.component.InjectionComponentFactory" android:theme="@style/AppTheme" tools:replace="android:appComponentFactory">
أخيرًا ، يمكننا إطلاق تطبيقنا ... وهو يعمل! NonContextRepository
توفير NonContextRepository
لدينا من خلال منشئ MainActivity. برشاقة!
يرجى ملاحظة أن هناك بعض التحفظات. لا يمكننا استخدام Context
هنا ، لأنه حتى قبل وجوده ، تحدث مكالمة إلى وظيفتنا - وهذا أمر مربك! يمكننا أن نذهب أبعد من ذلك حتى يقوم المُنشئ بتنفيذ فئة التطبيق الخاصة بنا ، ولكن دعونا نرى كيف يمكن أن تجعل Dagger هذا الأمر أسهل.
يجتمع - خنجر متعدد الربط
لن أخوض في تفاصيل عملية الربط المتعددة Dagger تحت الغطاء ، لأن هذا خارج نطاق هذه المقالة. كل ما تحتاج إلى معرفته هو أنه يوفر طريقة جيدة للحقن في مُنشئ الفصل دون الحاجة إلى استدعاء المُنشئ يدويًا. يمكننا استخدام هذا لتنفيذ فئات الإطار بسهولة بطريقة قابلة للتطوير. دعونا نرى كيف يضيف كل شيء.
لنقم بإعداد نشاطنا أولاً لمعرفة أين نذهب بعد ذلك.
class MainActivity @Inject constructor( private val repository: NonContextRepository ): Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState)
هذا يدل على الفور أنه لا يوجد تقريبا أي ذكر لحقن التبعية. الشيء الوحيد الذي نراه هو Inject
الشرح قبل المنشئ.
أنت الآن بحاجة إلى تغيير المكون ووحدة Dagger:
@Component(modules = [ApplicationModule::class]) interface ApplicationComponent { fun inject(factory: InjectionComponentFactory) }
@Module(includes = [ComponentModule::class]) class ApplicationModule { @Provides fun provideRepository(): NonContextRepository = NonContextRepository() }
لم يتغير شيء كثير. الآن نحن بحاجة فقط إلى تنفيذ مصنع المكونات لدينا ، ولكن كيف يمكننا إنشاء عناصر البيان؟ نحن هنا بحاجة إلى ComponentModule
. لنرى:
@Module abstract class ComponentModule { @Binds @IntoMap @ComponentKey(MainActivity::class) abstract fun bindMainActivity(activity: MainActivity): Any @Binds abstract fun bindComponentHelper(componentHelper: ComponentHelper): ComponentInstanceHelper } @Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER) @Retention(AnnotationRetention.RUNTIME) @MapKey internal annotation class ComponentKey(val clazz: KClass<out Any>)
نعم ، حسنًا ، مجرد شروح قليلة. نحن هنا نربط نشاطنا بخريطة ، وننفذ هذه الخريطة في فصل ComponentHelper
بنا ونقدم هذا ComponentHelper
- كل ذلك في إرشادات Binds
. يعرف Dagger كيفية إنشاء مثيل لـ MainActivity
بفضل MainActivity
Inject
بحيث يمكنه "ربط" الموفر بهذه الفئة ، وتوفير التبعيات التي نحتاجها تلقائيًا MainActivity
. لدينا ComponentHelper
النحو التالي.
class ComponentHelper @Inject constructor( private val creators: Map<Class<out Any>, @JvmSuppressWildcards Provider<Any>> ): ComponentInstanceHelper { @Suppress("UNCHECKED_CAST") override fun <T> resolve(className: String): T? = creators .filter { it.key.name == className } .values .firstOrNull() ?.get() as? T } interface InstanceComponentHelper { fun <T> resolve(className: String): T? }
ببساطة ، لدينا الآن خريطة الفصل للموردين لهذه الفئات. عندما نحاول حل فئة بالاسم ، نجد ببساطة المزود لهذه الفئة (إذا كان لدينا واحدة) ، نسميها للحصول على مثيل جديد لهذه الفئة ، وإعادتها.
أخيرًا ، نحتاج إلى إجراء تغييرات على AppComponentFactory
الخاص بنا لاستخدام فئة المساعد الجديدة الخاصة بنا.
class InjectionComponentFactory: AppComponentFactory() { @Inject lateinit var componentHelper: ComponentInstanceHelper init { DaggerApplicationComponent.create().inject(this) } override fun instantiateActivity(cl: ClassLoader, className: String, intent: Intent?): Activity { return componentHelper .resolve<Activity>(className) ?.apply { setIntent(intent) } ?: super.instantiateActivity(cl, className, intent) } }
قم بتشغيل الكود مرة أخرى. كل شيء يعمل! يالها من فرحة.
مشاكل تنفيذ المنشئ
قد لا يبدو هذا العنوان مؤثرًا جدًا. على الرغم من أنه يمكننا تضمين معظم الحالات في الوضع العادي عن طريق ضخها في المُنشئ ، إلا أنه لا توجد لدينا طريقة واضحة لتوفير سياق لتبعياتنا بطرق قياسية. لكن Context
في Android هو كل شيء. هناك حاجة للوصول إلى الإعدادات ، والشبكة ، وتكوين التطبيق وأكثر من ذلك بكثير. غالبًا ما تكون تبعياتنا أشياء تستخدم خدمات مرتبطة بالبيانات ، مثل الشبكة والإعدادات. يمكننا الالتفاف على هذا من خلال إعادة كتابة تبعياتنا لتكون وظائف خالصة ، أو عن طريق تهيئة كل شيء مع مثيلات السياق في فئة Application
لدينا ، ولكن الأمر يتطلب الكثير من العمل لتحديد أفضل طريقة للقيام بذلك.
عيب آخر لهذا النهج هو تعريف النطاق. في Dagger ، واحدة من المفاهيم الأساسية لتطبيق حقن التبعية عالية الأداء مع الفصل الجيد للعلاقات الطبقية هي نمطية الرسم البياني للكائن واستخدام النطاق. على الرغم من أن هذا النهج لا يحظر استخدام الوحدات ، إلا أنه يحد من استخدام النطاق. يوجد AppComponentFactory
بمستوى مختلف تمامًا من التجريد بالنسبة إلى فئات إطار العمل القياسية الخاصة بنا - لا يمكننا الحصول على رابط له برمجيًا ، لذلك ليس لدينا طريقة لتوجيهه لتوفير تبعيات للأنشطة في نطاق مختلف.
هناك العديد من الطرق لحل مشاكلنا مع النطاقات في الممارسة العملية ، واحدة منها هي استخدام FragmentFactory
لتضمين شظايانا في مُنشئ ذو نطاقات. لن أخوض في التفاصيل ، لكن اتضح أن لدينا الآن طريقة لإدارة إنشاء الأجزاء ، والتي لا تمنحنا فقط حرية أكبر بكثير من حيث النطاق ، ولكن أيضًا لها توافق مع الإصدارات السابقة.
استنتاج
قدم Android 9 Pie طريقة لاستخدام التضمين في المُنشئ لتوفير التبعيات في فصول العمل الإطارية لدينا ، مثل "النشاط" و "التطبيق". لقد رأينا أنه باستخدام Dagger Multi-ملزم ، يمكننا بسهولة توفير التبعيات على مستوى التطبيق.
يعتبر المنشئ الذي يقوم بتنفيذ جميع مكوناتنا جذابًا للغاية ، ويمكننا حتى القيام بشيء لجعله يعمل بشكل صحيح مع حالات السياق. هذا مستقبل واعد ، ولكنه متاح فقط بدءًا من API 28. إذا كنت تريد الوصول إلى أقل من 0.5٪ من المستخدمين ، فيمكنك تجربته. خلاف ذلك ، يجب عليك الانتظار لمعرفة ما إذا كانت هذه الطريقة لا تزال ذات صلة في غضون بضع سنوات.