مرحبًا ، اسمي Andrey وأنا أعمل على تطبيقات Tinkoff و Tinkoff Junior لنظام Android. أريد أن أتحدث عن الطريقة التي نجمع بها تطبيقين متشابهين من قاعدة شفرة واحدة.
— , ̆ 14 . , (, ), , , (, ).
. 

في بداية المشروع ، درسنا خيارات مختلفة لتنفيذه واتخذنا عددًا من القرارات. أصبح من الواضح على الفور أن التطبيقين (Tinkoff و Tinkoff Junior) سيكون لهما جزء كبير من الكود الموحد. لم نرغب في التفرع من التطبيق القديم ، ثم نسخ إصلاحات الأخطاء والوظائف المشتركة الجديدة. للعمل مع تطبيقين في وقت واحد ، درسنا ثلاثة خيارات: نكهات Gradle ، Git Submodules ، Gradle Modules.
نكهات المهد
لقد حاول العديد من المطورين لدينا بالفعل استخدام Flavors ، بالإضافة إلى أنه يمكننا استخدام النكهات متعددة الأبعاد للاستخدام مع النكهات الحالية.
ومع ذلك ، النكهات لها عيب واحد قاتل. يأخذ Android Studio في الكود رمز النكهة النشطة فقط - أي ما يكمن في المجلد الرئيسي وفي مجلد النكهة. تعتبر بقية الكود نصًا جنبًا إلى جنب مع التعليقات. يفرض هذا قيودًا على بعض أدوات الاستوديو: البحث عن استخدام الكود ، وإعادة البناء ، وغيرها.
وحدات فرعية بوابة
هناك خيار آخر لتنفيذ فكرتنا وهو استخدام الوحدات الفرعية من git: نقل الكود الموحد إلى مستودع منفصل وتوصيله باعتباره وحدة فرعية إلى مستودعين مع رمز لتطبيق معين.
هذا النهج يزيد من تعقيد العمل مع شفرة المصدر للمشروع. أيضًا ، سيظل على المطورين العمل مع مستودعات التخزين الثلاثة لإجراء تعديلات عند تغيير واجهة برمجة التطبيقات للوحدة النمطية الشائعة.
وحدة متعددة العمارة
الخيار الأخير هو التبديل إلى بنية متعددة الوحدات. هذا النهج خالٍ من العيوب التي يعاني منها الآخران. ومع ذلك ، فإن الانتقال إلى بنية متعددة الوحدات يتطلب إعادة بناء ممتدة للوقت.
في الوقت الذي بدأنا فيه العمل على Tinkoff Junior ، كان لدينا وحدتان: وحدة API صغيرة تصف كيفية العمل مع الخادم ، ووحدة تطبيق كبيرة متجانسة ، حيث تم تركيز الجزء الأكبر من رمز المشروع.


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

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

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

لدينا وحدات تحتوي على ميزات ، والتي تتيح لنا التمييز بين رمز "الكبار" أو العام أو "الأطفال". ومع ذلك ، فإن وحدة التطبيق لا تزال كبيرة بما فيه الكفاية ، والآن يتم تخزين حوالي نصف المشروع هناك.
تحويل التطبيق إلى مكتبة
تحتوي الوثائق على إرشادات بسيطة لتحويل التطبيق إلى مكتبة. يحتوي على أربع نقاط بسيطة ، ويبدو أنه لا ينبغي أن تكون الصعوبات:
- افتح ملف
build.gradle
للوحدة النمطية - قم بإزالة
applicationId
من تكوين الوحدة النمطية - في بداية الملف ،
apply plugin: 'com.android.application'
apply plugin: 'com.android.library'
- حفظ التغييرات ومزامنة المشروع في Android Studio ( ملف> مشروع المزامنة مع ملفات Gradle )
ومع ذلك ، استغرق التحويل عدة أيام وتحول الفرق الناتج إلى الشكل التالي:
- تم تغيير 183 ملف
- 1601 الإدراج (+)
- عمليات حذف 1920 (-)
ما الخطأ الذي حدث؟
أولاً وقبل كل شيء ، في المكتبات ، لا تعد معرفات الموارد ثوابت . في المكتبات ، كما في التطبيقات ، يتم إنشاء ملف R.java بقائمة من معرفات الموارد. وفي المكتبات ، قيم المعرف ليست ثابتة. لا تسمح لك Java بتشغيل القيم غير الثابتة ، ويجب استبدال كل المفاتيح بآخر.
بعد ذلك ، واجهنا تصادم الحزمة.
افترض أن لديك مكتبة بها package = com.example ، وأن التطبيق مع package = com.example.app يعتمد على هذه المكتبة. بعد ذلك ، سيتم إنشاء الفصل com.example.R في المكتبة ، و com.example.app.R ، على التوالي ، في التطبيق. الآن ، لنقم بإنشاء نشاط com.example.MainActivity في التطبيق ، حيث سنحاول الوصول إلى الفئة R. بدون استيراد صريح ، سيتم استخدام فئة R للمكتبة ، حيث لا يتم تحديد موارد التطبيق ، ولكن موارد المكتبة فقط. ومع ذلك ، لن يقوم Android Studio بتسليط الضوء على الخطأ وكل شيء سيكون على ما يرام عند محاولة التبديل من التعليمات البرمجية إلى المورد.
خنجر
نستخدم خنجر كإطار لحقن التبعية.
في كل وحدة تحتوي على نشاط وشظايا وخدمات ، لدينا الواجهات المعتادة التي تصف طرق الحقن لهذه الكيانات. في وحدات التطبيق ( للبالغين والمبتدئين ) ، ترث واجهات مكون الخنجر من هذه الواجهات. في الوحدات ، نأتي بالمكونات إلى الواجهات اللازمة لهذه الوحدة.
Multibindingi
تم تبسيط تطوير مشروعنا إلى حد كبير من خلال استخدام multibindings.
في واحدة من الوحدات النمطية الشائعة ، نقوم بتعريف واجهة. في كل وحدة تطبيقية ( للبالغين ، المبتدئين ) نصف تنفيذ هذه الواجهة. باستخدام التعليق التوضيحي @Binds
، @Binds
الخنجر أنه في كل مرة بدلاً من واجهة ، من الضروري ضخ تطبيقه المحدد لتطبيق تابع أو طفل. نجمع أيضًا غالبًا مجموعة من تطبيقات الواجهة (Set أو Map) ، ويتم وصف هذه التطبيقات في وحدات نمطية مختلفة للتطبيق.
نكهات
لأغراض مختلفة ، نقوم بجمع العديد من خيارات التطبيق. يجب أيضًا وصف النكهات الموضحة في الوحدة الأساسية في الوحدات التابعة. أيضًا ، لكي يعمل Android Studio بشكل صحيح ، من الضروري تحديد خيارات التجميع المتوافقة في جميع وحدات المشروع.
النتائج
في وقت قصير قمنا بتنفيذ تطبيق جديد. نحن الآن نشحن الوظيفة الجديدة في تطبيقين ، نكتبها مرة واحدة.
في الوقت نفسه ، أمضينا بعض الوقت في إعادة هيكلة الديون ، وخفضنا في نفس الوقت الدين الفني ، وانتقلنا إلى بنية متعددة الوحدات. على طول الطريق ، واجهنا قيودًا من Android SDK و Android Studio ، والتي نجحنا في إدارتها.