تزریق وابستگی در اندروید با استفاده از Hilt

آفلاین
user-avatar
پوریا شریفی
06 شهریور 1399, خواندن در 8 دقیقه

من از سال 2012 در حال توسعه برنامه‌های اندرویدی هستم، و در آن زمان کتابخانه تزریق وابستگی اصلی RoboGuice بود. در ماه مه 2013، Dagger1 به نسخه 1.0 رفت. انتشار اولیه Dagger2 در ماه مه 2015 انجام شد. من این افتخار را داشتم که از هر سه کتابخانه استفاده کنم و تغییر در تزریق وابستگی برای اندروید را در طی سال‌ها مشاهده کنم.

RoboGuice یک درگاه اندرویدی Google Guice بود، که ویژگی‌های بیشتری نسبت به تزریق وابستگی عادی داشت، مانند layout injection. RoboGuice به Reflection بسیار وابسته بود و فرد می‌تواند تاثیر آن را بر روی راه‌اندازی برنامه مشاهده کند و همچنین مشکلات فقط در زمان اجرا قابل مشاهد بود.

Dagger1 پردازنده حاشیه نویس(annotation) را اضافه کرد. که این دو کار را انجام داد:

  1. تایید در زمان ساخته شدن را ارائه می‌دهد
  2. مهاجرت از reflection آغاز شد، با این وجود Dagger1 کاملاً از reflection خلاص نشد و بنابراین برخی خطاها فقط در زمان اجرا یافت می‌شد. اما به دلیل نحوه ساخت نمودار، این خطاها تمایل به نمایش در زمان ایجاد نمودار داشتند.

Dagger2(در ادامه فقط از کلمه Dagger استفاده می‌شود) مهاجرت از reflection به استفاده از کدهای تولید شده را کامل کرد. این امر به توسعه‌دهندگان اجازه می‌دهد تا Dagger را از فایل progaurd حذف کنند زیرا همه کدها استاتیک و کامپایل شده است. همه مشکلات Dagger اکنون می‌تواند در زمان کامپایل مشاهده شود، که بهبود تایید در زمان ساخت در Dagger1 بود. همچنین با استفاده از این کد تولید شده، زمان راه‌اندازی برای ساخت نمودار بطور چشم‌گیری بهبود یافته است. Dagger گرچه عالی و سودمند است، اما هنوز هم اشکالاتی دارد:

  1. دارای یک منحنی یادگیری شیب‌دار است
  2. در هنگام برخورد با scopeها پیچیده‌تر می‌شود
  3. می‌تواند کدهای تکراری بسیار زیادی داشته باشد
  4. دارای وابستگی به کدهای تولید شده

Dagger برای اندروید سعی در کمک داشت اما بعضی مسائل را تشدید کرد.

Hilt در بالای Dagger ساخته شده است، بنابراین تمام مزایای Dagger راحفظ می‌کند، و به رفع مشکلات آن می‌پردازد.

شروع به کار

برای استفاده از Dagger باید کامپوننت‌ها، ماژول‌ها، ساخت کامپوننت، تفاوت بین تزریق به سازنده و تزریق به فیلد، و چگونگی اتصال کامپوننت به تزریق فیلد را برای شروع درک کنید. سپس اگر کسی بخواهد کار جالبی انجام دهد Scopeها را اضافه می‌کنیم مانند singleton و Reuseable، همچنین Scopeهای سفارشی، SubComponent و Bind را ذکر نکردیم. این لیست همچنان می‌تواند ادامه داشته باشد.

Hilt منحنی یادگیری را کاملا حذف نمی‌کند بلکه باعث می‌شود آن را کنترل کنید. پس از درج کتابخانه‌ها، پردازشگر حاشیه‌نویس، و افزونه‌ها، استفاده از Hilt کاملا بدون وقفه رو به جلو است.

  1. کلاس Application را با @HiltAndroidApp حاشیه نویسی می‌کنیم
  2. هرگونه Activity، Fragment، Service، و BroadcastReceiver را با @AndroidEntryPoint حاشیه‌نویسی می‌کنیم
  3. اکنون تزریق وابستگی به این کلاس‌ها با فرمت‌های زیر انجام می‌شود:

@Inject lateinit var bar: Bar
@Inject lateinit var gson: Gson

این همان تزریق فیلد است.

برای اینکه Hilt و Dagger بتوانند این وابستگی‌ها را تزریق بکنند، باید به Dagger بگویم که چگونه آن را ایجاد کند. در کلاس‌هایمان به سادگی می‌توانیم با حاشیه نویس کردن سازنده با @Inject تزریق به سازنده را انجام دهیم: 

class Bar @Inject constructor(private val foo: Foo){}

کلاس‌های ارائه شده توسط اندروید یا کتابخانه‌های دیگر باید با ماژول ارائه شوند.

@Module
@InstallIn(ApplicationComponent::class)
class AppModule {
  @Provides
  fun provideGson(): Gson {
    return GsonBuilder()
      .registerTypeAdapter(
          OffsetDateTime::class.java, OffsetDateTimeTypeAdapter()
      ).registerTypeAdapter(
          LocalTime::class.java, LocalTimeTypeAdapter()
      ).create()
  }
}

و این باعث می‌شود که شما از Hilt برای تزریق وابستگی استفاده کنید.

Scopes

Scopeها برای مدیریت حافظه و ایجاد شیء بسیار مهم هستند. Dagger بطور پیش‌فرض سه دامنه را ارئه می‌دهد.

  1. همانند مثال‌های قبل که scope نشان داده نشده است، Dagger هربار که تزریق شود نمونه جدیدی از شیء را ایجاد می‌کند
  2. @Singleton که یک نمونه از شیء را برای کل طول عمر نمودار حفظ می‌کند 
  3. @Reuseable که بسیار مورد علاقه من است، این scope از یک نمونه تا زمانی که حافظه برای آن وجود داشته باشد از آن استفاده می‌کند

در حالی که این scopeها بخوبی کار می‌کنند، اما موارد بسیاری وجود دارد که این scopeها بسیار گسترده هستند و یا بطور تصادفی موثر هستند. Dagger به توسعه‌دهندگان این امکان را می‌دهد تا scopeهای سفارشی را تعریف کنند، اما برای استفاده کامل از آن‌ها component و یا subComponentها باید ساخته شود. Hilt پنج scope دارد که به طور مستقیم به چرخه حیات اندروید گره خورده است که scope مورد نظر را بدون پیچیدگی اضافی فراهم می‌کند. این scopeها عبارتند از:

  1. @Singleton | ApplicationComponent(این مورد در Dagger وجود دارد اما Hilt آن را به چرخه حیات اپلیکیشن گره داده است)
  2. @ActivityRetainedScope | ActivityRetainedComponent
  3. @ActivityScope | ActivityComponent
  4. @FragmentScope | FragmentComponent
  5. @ViewScope | ViewComponent & ViewWithFragmentComponent
  6. @ServiceScope | ServiceComponent

Dagger هنگام ایجاد، نابودی و سلسه مراتب اضافی، مستندات خوبی در مورد این Scopeها ارائه می‌دهد. این scopeها به چرخه حیات همتای اندرویدی خود گره خورده‌اند. همانند سلسله مراتب کلاس‌های اندرویدی، scopeها می‌توانند از scope والد خود ارث ببرند؛ به عنوان مثال فرگمنت‌ها می‌توانند از اشیائی که در scope فرگمنت، اکتیوتی یا اپلیکیشن هستند استفاده کنند. اگر Bar در اکتیویتی و فرگمنت استفاده می‌شود، باید آن را در scope اکتیویتی قرار داد، همانند زیر:

@ActivityScoped class Bar @Inject constructor(private val foo: Foo){}

اگر gson فقط در سرویس‌ها استفاده می‌شود می‌توانیم همانند زیر ان را scope بندی کنیم:

@Module
@InstallIn(ServiceComponent::class)
class AppModule {
  @Provides 
  @ServiceScoped
  fun provideGson(): Gson {
    return GsonBuilder()
      .registerTypeAdapter(
          OffsetDateTime::class.java, OffsetDateTimeTypeAdapter()
      ).registerTypeAdapter(
          LocalTime::class.java, LocalTimeTypeAdapter()
      ).create()
  }
}

از آنجا که Hilt نیاز به ساختن کامپوننت را برطرف می‌کند، ما به راهی نیاز داریم تا ماژول‌های خود را با کامپوننت مناسب پیوند دهیم. اینجاست که @InstallIn وارد بازی می‌شود. این امر به توسعه‌دهندگان این امکان را می‌دهد تا وابستگی‌ها را به راحتی با scopeها تقسیم بندی کنند و خوانندگان این ماژول می‌توانند به سرعت ببینند که وابستگی‌ها، به ویژه وابستگی‌های بدون scope در کجا عمدتاً مورد استفاده قرار می‌گیرند. 

این scopeها روشی ساده برای پیاده‌سازی بهترین شیوه برای حفظ ردپای حافظه است، و در حالی که هنوز هم از به اشتراک گذاری اشیاء بهرمند می‌شوید.

تکرار کدها

چرا از تکرار کدها در تزریق وابستگی بسیار ذکر شده است؟ توسعه‌دهندگان دوست دارند کد بنویسند، بخصوص کدهای جذاب. کدهای تکراری جذاب نیستند.

کامپوننت‌ها تکراری هستند و می‌توانند خودکار باشند. با معرفی دو حاشیه نویس جدید @HiltAndroidApp و @AndroidEntryPoint اکثریت کدهای تکراری از Dagger حذف می‌شوند. اما بطور کلی فقط یک کامپوننت اصلی در هر برنامه وجود دارد، بنابراین چگونه آن کد تکراری محسوب می‌شود؟ اگر فقط یک برنامه را بنویسید، بله، کامپوننت اصلی تکراری نیست. با این حال بسیاری از توسعه‌دهندگان چندین برنامه می‌نویسند، و این کامپوننت را تکراری می‌کند. علاوه بر این subComponentها بسیار تکراری هستند، بخصوص اگر آن‌ها را در هفت scope مختلف گسترش دهید.

Hilt پشتیبانی برای ViewModel و Worker را اضافه کرده است. خوشبختانه Hilt تمام این قابلیت‌ها را با روشی بسیار مشابه برای ما فراهم می‌کند، و ارزش بسیار خوبی را ارائه می‌دهد و مانع از وجود کدهای تکراری می‌شود. اگر Hilt ادغام بیشتری با سایر کتابخانه‌های مبتنی بر JetPcak factory ایجاد کند، واقعاً جالب و سودمند خواهد بود.

وابستگی به کدهای تولید شده

واقعاً آزار دهنده است وقتی برای اضافه کردن کد باید یک پروژه بسازید. پرادازشگر حاشیه نویس Dagger کلاس یا کلاس‌هایی را ایجاد می‌کند که کامپوننت شما که بطور مستقیم به آن‌ها ارجاع می‌شود را گسترش می‌دهد. بنابراین برای گرفتن هرگونه کمک از IDE برای جلوگیری از import کردن بخش‌های مختلف یا تکمیل کدها، باید پروژه را بعد از ساخت کامپوننت بسازید. این به نظر یک چیز کوچک است، اما هنگامی که Dagger را به چندین پروژه اضافه می‌کنید یا برای اولین بار شروع به استفاده از Dagger می‌کنید، بسیار دردناک است. برای ساختن آن هیچ reflection وجود ندارد و هیچ پیکربندی برای proguard به Dagger ارجاع نشده است. توسعه‌دهندگان Hilt این مشکل را با افزونه‌ای که اضافه کردند حل کردند. افزونه هر @AndroidEntryPoint را مجبور به ارث‌بری از کلاس تولید شده می‌کند. در حالی که افزونه مورد نیاز نیست، می‌توانید این کار را به صورت دستی انجام دهید، اما چرا اجازه ندهیم که افزونه و سیستم ساخت همه کارها را برای ما انجام دهد.

Hilt بسیار عالی است. یک کتابخانه عالی را می‌گیرد و آن را ساده می‌کند و قابلیت‌های خوبی برای توسعه اندروید اضافه می‌کند. من بسیار سپاسگزارم که تیم JetPack به جامعه برنامه‌نویسان گوش فرا داد و چندین کتابخانه شگفت‌انگیز برای ما توسعه داده است.

  1. Reflection ابزاری شگفت‌انگیز و قدرتمند است، با این وجود اشکالاتی دارد که بر عملکرد تاثیر می‌گذارد. Retrofit یک نمونه عالی برای استفاده مناسب از reflection است، استفاده کم و تاثیرات عملکردی تحت تاثیر سایر عملیات طولانی مدت قرار گرفته است(I / O).
  2. مهاجرت از Dagger به Hilt نیز دشوار نیست. من یک پروژه گرفتم و طی چهار ساعت به Hilt مهاجرت کردم.

منبع

چه امتیازی به این مقاله می دید؟
خیلی بد
بد
متوسط
خوب
عالی

دیدگاه‌ها و پرسش‌ها

برای ارسال دیدگاه لازم است، ابتدا وارد سایت شوید.

در حال دریافت نظرات از سرور، لطفا منتظر بمانید

در حال دریافت نظرات از سرور، لطفا منتظر بمانید

آفلاین
user-avatar
پوریا شریفی @pouryasharifi78
ابتدا که با برنامه‌نویسی آشنا شدم به سمت php و طراحی وب رفتم، بعد از اون به توسعه‌ی اندروید علاقه‌مند شدم و تقریبا ۲ سال است که مشغول به برنامه‌نویسی...
دنبال کردن

گفتگو‌ برنامه نویسان

بخشی برای حل مشکلات برنامه‌نویسی و مباحث پیرامون آن وارد شو