عندما يتم بناء تطبيقك على بنية متعددة الوحدات ، يجب عليك تكريس الكثير من الوقت لضمان كتابة جميع الاتصالات بين الوحدات بشكل صحيح في التعليمات البرمجية. يمكن تكليف نصف هذا العمل بإطار Dagger 2. رئيس مجموعة Yandex.Map لنظام Android ، تحدث فلاديمير تاجاكوف
نوكسا عن إيجابيات وسلبيات
التعددية المتعددة والتنظيم المريح لـ DI داخل الوحدات باستخدام Dagger 2.
- اسمي فلاديمير ، أقوم بتطوير Yandex.Maps واليوم سأخبرك عن النموذجية والخنجر الثاني.
فهمت الجزء الأطول عندما درسته بنفسي ، الأسرع. الجزء الثاني ، الذي جلست فيه لعدة أسابيع ، سأخبرك بسرعة وإيجاز.

لماذا بدأنا عملية صعبة من التقسيم إلى وحدات في الخرائط؟ أردنا فقط زيادة سرعة البناء ، والجميع يعرفون ذلك.
النقطة الثانية من الهدف هي تقليل ربط الشفرة. أخذت العتاد من ويكيبيديا. هذا يعني أننا أردنا تقليل الترابط بين الوحدات بحيث تكون الوحدات منفصلة ويمكن استخدامها خارج التطبيق. البيان الأولي للمشكلة: يجب أن تكون مشاريع Yandex الأخرى قادرة على استخدام جزء من وظائف الخرائط تمامًا كما نفعل. ومن أجل تطوير هذه الوظيفة ، نحن منخرطون في تطوير المشروع.
أريد رمي شبشب مشتعل نحو [k] apt ، مما يبطئ سرعة التجميع. أنا لا أكرهه كثيرًا ، لكني أحبه كثيرًا. سمح لي باستخدام خنجر.

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

سيتألف التقرير من ثلاثة أجزاء: صغيرة وكبيرة ومعقدة. أولا ، الفرق بين التنفيذ و API في AGP. ظهر Android Gradle Plugin 3.0 مؤخرًا نسبيًا. كيف كان كل شيء قبله؟

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

بعد إعادة تجميع المشروع. من الواضح أنه بعد تغيير الوحدة ، يجب إعادة بنائها وتجميعها. حسنًا

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

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

هنا في التنفيذ ، من الواضح أن هناك صلة بين Core و App. هنا يمكننا أن نفهم بوضوح أننا لسنا بحاجة إليها ، نريد التخلص منه ، لذا قم بإزالته. كل شيء أصبح أسهل.

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

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

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

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

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

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

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

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

إذا كان لديك مشروع أصغر ، فعندئذٍ مع Common يمكنك أن تشعر براحة أكبر ، ثم لا يجب أن تكون متحمسًا للغاية.

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

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

كيف تبدو؟ نكرر أن هذا يخرج API من المشاهير ، وينفذ تنفيذه ، ونراقب الآن يديك ، وانتبه إلى الأسهم من الميزة إلى المشاهير. هذا يحدث. سقطت API الخاصة بوحدتك في Common ، وظل التنفيذ فيها ، وظهر المصنع الذي يوفر تنفيذ API في وحدتك الرئيسية. إذا شاهد شخص ما موبيوس ، فإن دينيس نيكليودوف تحدث عنه. مخطط مشابه جدا.
نستخدم Dagger في المشروع ، نحن نحبه ، وأردنا الحصول على أقصى استفادة من هذه الميزة في سياق الوحدات المختلفة.

أردنا أن يكون لكل وحدة رسم بياني مستقل للاعتماد ، وأن يكون لها مكون جذر محدد يمكنك من خلاله القيام بأي شيء ، أردنا أن نحصل على كود خاص بنا لكل وحدة Gradle. لا نريد أن يتسلل الرمز الذي تم إنشاؤه إلى الرمز الرئيسي. أردنا أكبر قدر ممكن من التحقق من وقت الترجمة. نعاني من [k] apt ، على الأقل يجب أن نحصل على بعض الربح مما يعطيه Dagger. ومع كل هذا ، لم نرغب في إجبار أي شخص على استخدام خنجر. لا الشخص الذي ينفذ وحدة الميزات الجديدة بشكل منفصل ، ولا الشخص الذي يستهلكها بعد ذلك ، هم زملائنا الذين يطلبون بعض الميزات لأنفسهم.
كيفية تنظيم رسم بياني منفصل عن التبعية داخل وحدة الميزات لدينا؟

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

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

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

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

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

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

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

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

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

نتيجة مهمة أخرى. قبل تسليط الضوء على وحدة الميزات ، بدا الأمر كما يلي: في الإطار الرئيسي كان 28 ثانية ، والآن حان 49 ثانية. لقد خصصنا الوحدة الأولى ، وتلقى بالفعل تباطؤ التجميع مرتين تقريبًا.

وخيار آخر هو تجميع تدريجي بسيط لوحدتنا ، وليس ميزة ، كما في السابق. كانت 28 ثانية حتى تم تخصيص الوحدة. عندما خصصنا رمزًا لا يحتاج إلى إعادة البناء في كل مرة ، و [k] apt ، والذي لم يكن بحاجة إلى تنفيذه في كل مرة ، فزنا بثلاث ثوانٍ. الله يعلم ما ، ولكن آمل أنه مع كل وحدة نمطية جديدة ، سينخفض الوقت فقط.
فيما يلي روابط مفيدة للمقالات:
API مقابل التنفيذ ،
مقالة مع قياسات وقت البناء ،
وحدة نموذجية . سيكون العرض التقديمي
متاحًا . شكرا لك