Dagger és Android

A Dagger 2 egyik fő előnye a legtöbb más függőségi injektálási keretrendszerrel szemben, hogy szigorúan generált implementációja (nincs reflexió) azt jelenti, hogy Android alkalmazásokban is használható. Azonban még mindig van néhány megfontolandó szempont, amikor a Dagger-t Android alkalmazásokon belül használjuk.

Filozófia

Míg az Androidra írt kód Java forráskód, gyakran egészen más a stílusa. Általában ezek a különbségek azért vannak, hogy alkalmazkodjanak a mobil platform egyedi teljesítőképességi szempontjaihoz.

Az Androidra szánt kódra általánosan alkalmazott minták közül azonban sok ellentétes a más Java kódra alkalmazottakkal. Még azEffective Java-ban található tanácsok nagy része is alkalmatlannak tekinthető az Androidhoz.

Az idiomatikus és hordozható kód céljainak elérése érdekében a Dagger a ProGuard-ra támaszkodik a lefordított bytecode utólagos feldolgozásához. Ez lehetővé teszi, hogy a Daggerto olyan forráskódot adjon ki, amely mind a szerveren, mind az Androidon természetesnek tűnik, miközben a különböző eszközláncok felhasználásával olyan bytecode-ot állít elő, amely mindkét környezetben hatékonyan fut. Továbbá a Dagger kifejezett célja annak biztosítása, hogy az általa generált Java forráskód következetesen kompatibilis legyen aProGuard optimalizációkkal.

Természetesen nem minden probléma kezelhető ilyen módon, de ez az elsődleges mechanizmus, amellyel az Android-specifikus kompatibilitás biztosítható.

tl;dr

A Dagger feltételezi, hogy az Android felhasználók az R8-at vagy a ProGuardot fogják használni.

Miért nehéz a Dagger Androidon

Az Android alkalmazás Daggerrel való írásának egyik központi nehézsége, hogy sok Android keretrendszer osztályát maga az operációs rendszer instanciálja, mintActivity és Fragment, de a Dagger akkor működik a legjobban, ha az összes injektált objektumot létre tudja hozni. Ehelyett a tagok injektálását egy életciklus-módszerben kell végrehajtania. Ez azt jelenti, hogy sok osztály végül így néz ki:

public class FrombulationActivity extends Activity { @Inject Frombulator frombulator; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // DO THIS FIRST. Otherwise frombulator might be null! ((SomeApplicationBaseType) getContext().getApplicationContext()) .getApplicationComponent() .newActivityComponentBuilder() .activity(this) .build() .inject(this); // ... now you can write the exciting code }}

Ez néhány problémával jár:

  1. A kód másolása megnehezíti a későbbi refaktorálást. Ahogy egyre több és több fejlesztő másolja be ezt a blokkot, egyre kevesebben fogják tudni, hogy valójában mit csinál.

  2. Még alapvetőbb, hogy az injektálást kérő típusnak(FrombulationActivity) tudnia kell az injektoráról. Még ha ez konkrét típusok helyett interfészeken keresztül történik is, ez megszegi a függőségi injektálás egyik alapelvét: egy osztály nem tudhat semmit arról, hogyan injektálják.

dagger.android

A dagger.androidben található osztályok egy megközelítést kínálnak a fenti problémák egyszerűsítésére. Ez megköveteli néhány extra API és fogalom megtanulását, de kevesebb boilerplate-et és injektálást biztosít az Android osztályokba az életciklus megfelelő helyén.

Egy másik megközelítés a normál Dagger API-k használata és az olyan útmutatók követése, mint az itt található.Ez lehet, hogy egyszerűbb megérteni, de azzal a hátránnyal jár, hogy kézzel kell plusz boilerplate-et írni.

A Jetpack és a Dagger csapatok együtt dolgoznak egy új kezdeményezésen a Dagger Androidon, amely remélhetőleg nagy változást jelent a jelenlegi statusquo-hoz képest. Bár sajnos még nincs kész, ezt érdemes lehet megfontolni, amikor eldönti, hogyan használja ma a Dagger-t az Android projektjeiben.

Activity objektumok beillesztése

  1. Telepítse a AndroidInjectionModule-t az alkalmazáskomponensben, hogy biztosítsa, hogy az ezen alaptípusokhoz szükséges összes kötés elérhető legyen.

  2. Azzal kezdjük, hogy írunk egy @Subcomponent, amely megvalósítjaAndroidInjector<YourActivity>, egy@Subcomponent.Factory, amely kiterjesztiAndroidInjector.Factory<YourActivity>:

    @Subcomponent(modules = ...)public interface YourActivitySubcomponent extends AndroidInjector<YourActivity> { @Subcomponent.Factory public interface Factory extends AndroidInjector.Factory<YourActivity> {}}
  3. Az alkomponens definiálása után adjuk hozzá a komponenshierarchiához egy olyan modul definiálásával, amely megköti az alkomponens gyárat, és hozzáadjuk ahhoz a komponenshez, amely befecskendezi a Application-unkat:

    @Module(subcomponents = YourActivitySubcomponent.class)abstract class YourActivityModule { @Binds @IntoMap @ClassKey(YourActivity.class) abstract AndroidInjector.Factory<?> bindYourAndroidInjectorFactory(YourActivitySubcomponent.Factory factory);}@Component(modules = {..., YourActivityModule.class})interface YourApplicationComponent { void inject(YourApplication application);}

    Pro-tipp: Ha az alkomponensednek és gyárának nincs más metódusa vagy szupertípusa a 2. lépésben említetteken kívül, akkor a@ContributesAndroidInjector segítségével generálhatod őket magadnak. A 2. és 3. lépés helyett adjunk hozzá egy abstract modul metódust, amely visszaadja az aktivitásunkat, jegyezzük meg @ContributesAndroidInjector-val, és adjuk meg a modulokat, amelyeket az alkomponensbe szeretnénk telepíteni. Ha az alkomponensnek hatókörökre van szüksége, alkalmazza a metódusra is a hatókör megjegyzést.

    @ActivityScope@ContributesAndroidInjector(modules = { /* modules to install into the subcomponent */ })abstract YourActivity contributeYourAndroidInjector();
  4. A következő lépésben a Application implementálja HasAndroidInjectorés @Inject aDispatchingAndroidInjector<Object> visszaadja a androidInjector() metódust:

    public class YourApplication extends Application implements HasAndroidInjector { @Inject DispatchingAndroidInjector<Object> dispatchingAndroidInjector; @Override public void onCreate() { super.onCreate(); DaggerYourApplicationComponent.create() .inject(this); } @Override public AndroidInjector<Object> androidInjector() { return dispatchingAndroidInjector; }}
  5. Végül a Activity.onCreate() módszeredben hívd meg a AndroidInjection.inject(this)et a super.onCreate(); hívása előtt:

    public class YourActivity extends Activity { public void onCreate(Bundle savedInstanceState) { AndroidInjection.inject(this); super.onCreate(savedInstanceState); }}
  6. Gratulálunk!

Hogyan működött?

AndroidInjection.inject() kap egy DispatchingAndroidInjector<Object>-et a Application-tól, és átadja a tevékenységedet a inject(Activity)-nak. ADispatchingAndroidInjector megkeresi a AndroidInjector.Factory aktivitásod osztályát (ami YourActivitySubcomponent.Factory), létrehozza a AndroidInjector-et (ami YourActivitySubcomponent), és átadja az aktivitásodat a inject(YourActivity)-nak.

Fragment objektumok injektálása

A Fragment injektálása ugyanolyan egyszerű, mint egy Activity injektálása. Definiáld az alkomponensedet ugyanígy.

Ahelyett, hogy a onCreate()-be injektálnál, mint az Activitytípusok esetében, injektáld az Fragment-öket a onAttach()-be.

Az Activity-ekhez definiált modulokkal ellentétben, a Fragment-ek esetében választhatsz, hova telepítsd a modulokat. A Fragment komponensedet egy másik Fragment komponens, egy Activity komponens vagy aApplication komponens szubkomponensévé teheted – minden attól függ, hogy a Fragment milyen más kötéseket igényel. Miután eldöntöttük a komponens helyét, tegyük a megfelelő típusimplementációt HasAndroidInjector-nek (ha még nem teszi). Például, ha a Fragmentkell a YourActivitySubcomponent kötődése, a kódod valahogy így fog kinézni:

public class YourActivity extends Activity implements HasAndroidInjector { @Inject DispatchingAndroidInjector<Object> androidInjector; @Override public void onCreate(Bundle savedInstanceState) { AndroidInjection.inject(this); super.onCreate(savedInstanceState); // ... } @Override public AndroidInjector<Object> androidInjector() { return androidInjector; }}public class YourFragment extends Fragment { @Inject SomeDependency someDep; @Override public void onAttach(Activity activity) { AndroidInjection.inject(this); super.onAttach(activity); // ... }}@Subcomponent(modules = ...)public interface YourFragmentSubcomponent extends AndroidInjector<YourFragment> { @Subcomponent.Factory public interface Factory extends AndroidInjector.Factory<YourFragment> {}}@Module(subcomponents = YourFragmentSubcomponent.class)abstract class YourFragmentModule { @Binds @IntoMap @ClassKey(YourFragment.class) abstract AndroidInjector.Factory<?> bindYourFragmentInjectorFactory(YourFragmentSubcomponent.Factory factory);}@Subcomponent(modules = { YourFragmentModule.class, ... }public interface YourActivityOrYourApplicationComponent { ... }

Base Framework Types

Mivel a DispatchingAndroidInjector futásidőben megkeresi a megfelelőAndroidInjector.Factory osztályt, egy alaposztály képes implementálniHasAndroidInjector, valamint hívni AndroidInjection.inject(). Mindegyik alosztálynak csak egy megfelelő @Subcomponent-t kell kötnie. A Dagger biztosít néhány alaptípust, amelyek ezt megteszik, mint például a DaggerActivity és DaggerFragment,ha nincs bonyolult osztályhierarchia. A Dagger egyDaggerApplication-t is biztosít ugyanerre a célra – csak annyit kell tennie, hogy kiterjeszti és felülírja a applicationInjector() metódust, hogy visszaadja azt a komponenst, amelyiknek be kell injektálnia a Application-t.

A következő típusokat is tartalmazza:

  • DaggerService és DaggerIntentService
  • DaggerBroadcastReceiver
  • DaggerContentProvider

Megjegyzés: A DaggerBroadcastReceiver csak akkor használható, ha aBroadcastReceiver a AndroidManifest.xml-ben van regisztrálva. Ha aBroadcastReceiver a saját kódodban jön létre, inkább a konstruktor injektálást részesítsd előnyben.

Támogató könyvtárak

Az Android támogató könyvtár felhasználói számára a párhuzamos típusok adagger.android.support csomagban léteznek.

TODO(ronshapiro): el kellene kezdeni ezt androidx csomagok szerint felosztani

Hogyan kapom meg?

Add hozzá a következőket a build.gradle-hez:

dependencies { implementation 'com.google.dagger:dagger-android:2.x' implementation 'com.google.dagger:dagger-android-support:2.x' // if you use the support libraries annotationProcessor 'com.google.dagger:dagger-android-processor:2.x' annotationProcessor 'com.google.dagger:dagger-compiler:2.x'}

Mikor kell injektálni

A konstrukciós injektálás előnyben részesül, amikor csak lehetséges, mert javacbiztosítja, hogy egyetlen mezőre se hivatkozzunk, mielőtt az be lenne állítva, ami segít elkerülni aNullPointerExceptions. Ha a tagok injektálása szükséges (mint fentebb tárgyaltuk), inkább a lehető legkorábban injektáljunk. Emiatt a DaggerActivitymeghívja a AndroidInjection.inject()-et azonnal a onCreate()-ben, mielőtt meghívná asuper.onCreate()-t, és a DaggerFragment ugyanezt teszi a onAttach()-ben, ami szintén megelőzi az inkoherenciákat, ha a Fragment újracsatolásra kerül.

Elkerülhetetlen a AndroidInjection.inject() meghívása a super.onCreate() előtt egy Activity-ben, mivel a super meghívása a konfigurációváltás során az előzőaktivitáspéldányból csatolja az Fragment-öket, ami viszont injektálja azFragment-öket. Ahhoz, hogy az Fragment injektálás sikeres legyen, a Activitynek már injektálva kell lennie. Az ErrorProne felhasználói számára akompiler hiba, ha a super.onCreate() után AndroidInjection.inject()-et hívnak.

FAQ

Scoping AndroidInjector.Factory

AndroidInjector.Factory egy állapotmentes interfésznek szánják, hogy azimplementátoroknak ne kelljen aggódniuk az objektummal kapcsolatos állapot kezelése miatt,amelyet injektálni fognak. Amikor DispatchingAndroidInjector kér egy AndroidInjector.Factory-et, ezt egy Provider-on keresztül teszi, így nem tart meg explicit módon egyetlen példányt sem a gyárból. Mivel egyes megvalósítások megtarthatják a Activity/Fragment/etc példányát, amelyet befecskendeznek, fordítási idejű hiba, ha hatóköröket alkalmazunk az azokat biztosító metódusokra. Ha biztos vagy benne, hogy a AndroidInjector.Factory nem tart meg egy példányt az injektált objektumhoz, akkor elnyomhatod ezt a hibát a@SuppressWarnings("dagger.android.ScopedInjectorFactory") alkalmazásával a modulemódusodra.