خلفية
في بعض الأحيان ، عندما أسوف ، أقوم بالتنظيف: أنظف الطاولة ، وأطفئ الأشياء ، وأرتب الغرفة. في الواقع ، أنا رتب البيئة - فهي تنشطك وتهيئك للعمل. مع البرمجة ، لدي نفس الموقف ، أنا فقط أقوم بتنظيف المشروع: أقوم بإعادة البناء ، وصنع أدوات مختلفة ، وأبذل قصارى جهدي لتسهيل الحياة على نفسي وزملائي.
منذ بعض الوقت ، قررنا في فريق Android إنشاء أحد مشاريعنا - Wallet - متعدد الوحدات. وقد أدى ذلك إلى عدد من المزايا والمشكلات ، من بينها الحاجة إلى تكوين كل وحدة من البداية. بالطبع ، يمكنك فقط نسخ التكوين من وحدة إلى أخرى ، ولكن إذا أردنا تغيير شيء ما ، فسيتعين علينا التكرار في جميع الوحدات.
لا أحب هذا ، الفريق لا يعجبني ، وإليك الخطوات التي اتخذناها لتبسيط حياتنا وجعل التكوينات أسهل في الحفاظ عليها.
التكرار الأول - سحب إصدارات المكتبة
في الواقع ، كان هذا بالفعل في المشروع أمامي ، وربما تعرف هذا النهج. كثيرا ما أرى المطورين يستخدمونه.
النهج هو أنه من الضروري نقل إصدارات المكتبات إلى خصائص عالمية منفصلة للمشروع ، ثم تصبح متاحة خلال المشروع ، مما يساعد على إعادة استخدامها. يتم ذلك عادةً في ملف build.gradle على مستوى المشروع ، ولكن في بعض الأحيان يتم إخراج هذه المتغيرات في ملف .gradle منفصل وتضمينها في build.gradle الرئيسي.
على الأرجح ، لقد رأيت بالفعل مثل هذا الرمز في المشروع. ليس هناك سحر في ذلك، فإنه ليست سوى واحدة من ملحقات Gradle دعا ExtraPropertiesExtension . باختصار ، إنها مجرد خريطة <سلسلة ، كائن> ، متاحة من خلال تحويلة في كائن المشروع، وكل شيء آخر - العمل كما لو كان مع كائن ، وكتل التكوين وما إلى ذلك - سحر Gradle. أمثلة:
| .gradle | .gradle.kts |
|---|---|
|
|
ما يعجبني في هذا النهج هو أنه بسيط للغاية ويساعد على منع النسخ من الانهيار. لكن لها عيوب: تحتاج إلى التأكد من أن المطورين يستخدمون إصدارات من هذه المجموعة ، وهذا لا يبسط بشكل كبير إنشاء وحدات جديدة ، لأنه لا يزال يتعين عليك نسخ الكثير من الأشياء.
بالمناسبة ، يمكن تحقيق تأثير مماثل باستخدام gradle.properties بدلاً من ExtraPropertiesExtension ، فقط كن حذرًا : يمكن تجاوز إصداراتك عند البناء باستخدام علامات -P ، وإذا أشرت إلى متغير بالاسم ببساطة في البرامج النصية الرائعة ، فسيتم استبدال gradle.properties ومنهم. مثال مع gradle.properties وتجاوز:
// grdle.properties
overriden=2
// build.gradle
ext.dagger = 1
ext.overriden = 1
// module/build.gradle
println(rootProject.ext.dagger) // 1
println(dagger) // 1
println(rootProject.ext.overriden)// 1
println(overriden) // 2
التكرار الثاني - project.subprojects
فضولي ، الذي يذكرني بعدم رغبتي في نسخ الكود والتعامل مع تكوين كل وحدة ، قادني إلى الخطوة التالية: تذكرت أنه في بناء الجذر يوجد كتلة يتم إنشاؤها افتراضيًا - جميع المشاريع .
allprojects {
repositories {
google()
jcenter()
}
}
ذهبت إلى التوثيق ووجدت أنه من الممكن تمرير كتلة من التعليمات البرمجية إليه لتهيئة هذا المشروع وجميع المشاريع المتداخلة. لكن هذا ليس ما احتاجه تمامًا ، لذلك قمت بالتمرير إلى أبعد من ذلك ووجدت مشاريع فرعية - طريقة لتكوين جميع المشاريع المتداخلة مرة واحدة. اضطررت إلى إضافة بعض الشيكات ، وهذا ما حدث .
مثال على تكوين وحدات عبر project.subprojects
subprojects { project ->
afterEvaluate {
final boolean isAndroidProject =
(project.pluginManager.hasPlugin('com.android.application') ||
project.pluginManager.hasPlugin('com.android.library'))
if (isAndroidProject) {
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
android {
compileSdkVersion rootProject.ext.compileSdkVersion
defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
vectorDrawables.useSupportLibrary = true
}
compileOptions {
encoding 'UTF-8'
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
androidExtensions {
experimental = true
}
}
}
dependencies {
if (isAndroidProject) {
// android dependencies here
}
// all subprojects dependencies here
}
project.tasks
.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile)
.all {
kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8.toString()
}
}
}
الآن ، بالنسبة لأي وحدة نمطية مع com.android.application أو المكون الإضافي com.android.library متصل ، يمكننا تكوين أي شيء: المكونات الإضافية ، تكوينات المكونات الإضافية ، التبعيات.
سيكون كل شيء على ما يرام إذا لم يكن هناك مشكلتان: إذا أردنا تجاوز بعض المعلمات المحددة في المشاريع الفرعية في الوحدة النمطية ، فلن نتمكن من القيام بذلك ، لأن الوحدة قد تم تكوينها قبل تطبيق المشاريع الفرعية (بفضل afterEvaluate ). وأيضًا ، إذا أردنا عدم تطبيق هذا التكوين التلقائي في الوحدات النمطية الفردية ، فسيبدأ ظهور العديد من عمليات الفحص الإضافية في كتلة المشاريع الفرعية. لذلك بدأت بالتفكير أكثر.
التكرار الثالث - buildSrc و plugin
حتى هذه اللحظة ، سمعت عن buildSrc عدة مرات ورأيت أمثلة تم فيها استخدام buildSrc كبديل للخطوة الأولى في هذه المقالة. وسمعت أيضًا عن الإضافات gradle ، لذلك بدأت في الحفر في هذا الاتجاه. تبين أن كل شيء بسيط للغاية: لدى Gradle وثائق لتطوير المكونات الإضافية المخصصة ، والتي تتم كتابة كل شيء فيها.
بعد الفهم قليلاً ، قمت بإنشاء مكون إضافي يمكنه تكوين كل ما يحتاج إلى التغيير مع القدرة على التغيير إذا لزم الأمر.
كود البرنامج المساعد
import org.gradle.api.JavaVersion
import org.gradle.api.Plugin
import org.gradle.api.Project
class ModulePlugin implements Plugin<Project> {
@Override
void apply(Project target) {
target.pluginManager.apply("com.android.library")
target.pluginManager.apply("kotlin-android")
target.pluginManager.apply("kotlin-android-extensions")
target.pluginManager.apply("kotlin-kapt")
target.android {
compileSdkVersion Versions.sdk.compile
defaultConfig {
minSdkVersion Versions.sdk.min
targetSdkVersion Versions.sdk.target
javaCompileOptions {
annotationProcessorOptions {
arguments << ["dagger.gradle.incremental": "true"]
}
}
}
// resources prefix: modulename_
resourcePrefix "${target.name.replace("-", "_")}_"
lintOptions {
baseline "lint-baseline.xml"
}
compileOptions {
encoding 'UTF-8'
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
testOptions {
unitTests {
returnDefaultValues true
includeAndroidResources true
}
}
}
target.repositories {
google()
mavenCentral()
jcenter()
// add other repositories here
}
target.dependencies {
implementation Dependencies.dagger.dagger
implementation Dependencies.dagger.android
kapt Dependencies.dagger.compiler
kapt Dependencies.dagger.androidProcessor
testImplementation Dependencies.test.junit
// add other dependencies here
}
}
}
يبدو الآن تكوين المشروع الجديد مثل تطبيق البرنامج المساعد: 'ru.yandex.money.module ' وهذا كل شيء. يمكنك إجراء الإضافات الخاصة بك إلى كتلة android أو التبعيات ، ويمكنك إضافة مكونات إضافية أو تخصيصها ، ولكن الشيء الرئيسي هو أن الوحدة الجديدة تم تكوينها في سطر واحد ، وتكوينها دائمًا ما يكون ملائمًا ولم يعد مطور المنتج بحاجة إلى التفكير في إعدادها.
من بين السلبيات ، أود أن أشير إلى أن هذا الحل يتطلب وقتًا إضافيًا ودراسة المادة ، لكن من وجهة نظري ، الأمر يستحق ذلك. إذا كنت ترغب في نقل المكون الإضافي كمشروع منفصل في المستقبل ، فأنا لا أوصي بإعداد التبعيات بين الوحدات في المكون الإضافي .
نقطة مهمة: إذا كنت تستخدم مكون android gradle الإضافي أقل من 4.0 ، فمن الصعب جدًا القيام ببعض الأشياء في نصوص kotlin - على الأقل يكون تكوين كتلة android أسهل في البرامج النصية الرائعة. هناك مشكلة في حقيقة أن بعض الأنواع غير متوفرة في وقت الترجمة ، وأن groovy مكتوب ديناميكيًا ، ولا يهمه =)
التالي - مكون إضافي مستقل أو monorepo
بالطبع ، الخطوة الثالثة ليست كلها. لا يوجد حد للكمال ، لذلك هناك خيارات للمكان التالي.
الخيار الأول هو البرنامج المساعد المستقل لـ gradle. بعد الخطوة الثالثة ، لم يعد الأمر صعبًا: تحتاج إلى إنشاء مشروع منفصل ، ونقل الكود هناك ، وتهيئة المنشور.
الإيجابيات: يمكن أن يكون المكون الإضافي مضطربًا بين عدة مشاريع ، مما يبسط الحياة ليس في مشروع واحد ، ولكن في النظام البيئي.
السلبيات: الإصدار - عند تحديث مكون إضافي ، سيتعين عليك تحديث وظائفه والتحقق منها في عدة مشاريع في وقت واحد ، وقد يستغرق ذلك بعض الوقت. بالمناسبة ، زملائي من تطوير الواجهة الخلفية لديهم حل ممتاز حول هذا الموضوع ، الكلمة الأساسية هي أداة تحديث - أداة تمشي في حد ذاتها عبر المستودعات وتحديث التبعيات. لن أتطرق إلى هذا لفترة طويلة ، دعهم يخبرونهم بأنفسهم بشكل أفضل.
Monorepo - يبدو بصوت عالٍ ، لكن ليس لدي أي خبرة في ذلك ، ولكن هناك اعتبارات فقط أنه يمكن استخدام مشروع واحد ، مثل buildSrc ، في العديد من المشاريع الأخرى في وقت واحد ، وهذا يمكن أن يساعد في حل المشكلة مع الإصدار. إذا كان لديك فجأة خبرة مع monorepo ، فشاركها في التعليقات حتى أتمكن أنا والقراء الآخرين من معرفة شيء عنها.
مجموع
في مشروع جديد ، قم بالخطوة الثالثة على الفور - buildSrc و plugin - سيكون الأمر أسهل للجميع ، خاصة بعد أن قمت بإرفاق الكود . والخطوة الثانية - project.subprojects - تستخدم لربط الوحدات المشتركة ببعضها البعض.
إذا كان لديك شيء تضيفه أو تعترض عليه ، فاكتب في التعليقات أو ابحث عني على الشبكات الاجتماعية.