Dolch - Sollten wir jede Komponente und jedes Modul für jede Aktivität / jedes Fragment erstellen?

85

Ich habe eine Weile mit dagger2 gearbeitet. Und ich war verwirrt, ob ich für jede Aktivität / jedes Fragment eine eigene Komponente / ein eigenes Modul erstellen sollte. Bitte helfen Sie mir, dies zu klären:

Zum Beispiel haben wir eine App und die App hat ungefähr 50 Bildschirme. Wir werden den Code nach dem MVP-Muster und Dagger2 für DI implementieren. Angenommen, wir haben 50 Aktivitäten und 50 Moderatoren.

Meiner Meinung nach sollten wir den Code normalerweise so organisieren:

  1. Erstellen Sie eine AppComponent und ein AppModule, die alle Objekte bereitstellen, die verwendet werden, während die App geöffnet ist.

    @Module
    public class AppModule {
    
        private final MyApplicationClass application;
    
        public AppModule(MyApplicationClass application) {
            this.application = application;
        }
    
        @Provides
        @Singleton
        Context provideApplicationContext() {
            return this.application;
        }
    
        //... and many other providers 
    
    }
    
    @Singleton
    @Component( modules = { AppModule.class } )
    public interface AppComponent {
    
        Context getAppContext();
    
        Activity1Component plus(Activity1Module module);
        Activity2Component plus(Activity2Module module);
    
        //... plus 48 methods for 48 other activities. Suppose that we don't have any other Scope (like UserScope after user login, ....)
    
    }
  2. Erstellen Sie ActivityScope:

    @Scope
    @Documented
    @Retention(value=RUNTIME)
    public @interface ActivityScope {
    }
  3. Erstellen Sie für jede Aktivität eine Komponente und ein Modul. Normalerweise füge ich sie als statische Klassen in die Aktivitätsklasse ein:

    @Module
    public class Activity1Module {
    
        public LoginModule() {
        }
        @Provides
        @ActivityScope
        Activity1Presenter provideActivity1Presenter(Context context, /*...some other params*/){
            return new Activity1PresenterImpl(context, /*...some other params*/);
        }
    
    }
    
    @ActivityScope
    @Subcomponent( modules = { Activity1Module.class } )
    public interface Activity1Component {
        void inject(Activity1 activity); // inject Presenter to the Activity
    }
    
    // .... Same with 49 remaining modules and components.

Dies sind nur sehr einfache Beispiele, um zu zeigen, wie ich dies implementieren würde.

Aber ein Freund von mir hat mir gerade eine andere Implementierung gegeben:

  1. Erstellen Sie PresenterModule, das alle Präsentatoren bereitstellt:

    @Module
    public class AppPresenterModule {
    
        @Provides
        Activity1Presenter provideActivity1Presentor(Context context, /*...some other params*/){
            return new Activity1PresenterImpl(context, /*...some other params*/);
        }
    
        @Provides
        Activity2Presenter provideActivity2Presentor(Context context, /*...some other params*/){
            return new Activity2PresenterImpl(context, /*...some other params*/);
        }
    
        //... same with 48 other presenters.
    
    }
  2. Erstellen Sie AppModule und AppComponent:

    @Module
    public class AppModule {
    
        private final MyApplicationClass application;
    
        public AppModule(MyApplicationClass application) {
            this.application = application;
        }
    
        @Provides
        @Singleton
        Context provideApplicationContext() {
            return this.application;
        }
    
        //... and many other provides 
    
    }
    
    @Singleton
    @Component(
            modules = { AppModule.class,  AppPresenterModule.class }
    )
    public interface AppComponent {
    
        Context getAppContext();
    
        public void inject(Activity1 activity);
        public void inject(Activity2 activity);
    
        //... and 48 other methods for 48 other activities. Suppose that we don't have any other Scope (like UserScope after user login, ....)
    
    }

Seine Erklärung lautet: Er muss nicht für jede Aktivität Komponenten und Module erstellen. Ich denke, die Idee meines Freundes ist überhaupt nicht gut, aber bitte korrigieren Sie mich, wenn ich falsch liege. Hier sind die Gründe:

  1. Viele Speicherlecks :

    • Die App erstellt 50 Präsentatoren, auch wenn der Benutzer nur 2 Aktivitäten geöffnet hat.
    • Nachdem der Benutzer eine Aktivität geschlossen hat, bleibt sein Präsentator weiterhin bestehen
  2. Was passiert, wenn ich zwei Instanzen einer Aktivität erstellen möchte? (Wie kann er zwei Moderatoren erstellen?)

  3. Die Initialisierung der App dauert sehr lange (da viele Präsentatoren, Objekte usw. erstellt werden müssen).

Entschuldigung für einen langen Beitrag, aber bitte helfen Sie mir, dies für mich und meinen Freund zu klären. Ich kann ihn nicht überzeugen. Ihre Kommentare werden sehr geschätzt.

/ ------------------------------------------------- ---------------------- /

Nach einer Demo bearbeiten.

Zunächst danke für die Antwort von @pandawarrior. Ich hätte eine Demo erstellen sollen, bevor ich diese Frage gestellt habe. Ich hoffe, meine Schlussfolgerung hier könnte jemand anderem helfen.

  1. Was mein Freund getan hat, verursacht keine Speicherlecks, es sei denn, er legt einen Bereich für die Provides-Methoden fest. (Zum Beispiel @Singleton oder @UserScope, ...)
  2. Wir können viele Präsentatoren erstellen, wenn die Provides-Methode keinen Bereich hat. (Also, mein zweiter Punkt ist auch falsch)
  3. Dolch erstellt die Moderatoren nur dann, wenn sie benötigt werden. (Die Initialisierung der App wird also nicht lange dauern. Ich war durch Lazy Injection verwirrt.)

Alle Gründe, die ich oben gesagt habe, sind größtenteils falsch. Das heißt aber nicht, dass wir meiner Freundidee aus zwei Gründen folgen sollten:

  1. Es ist nicht gut für die Architektur der Quelle, wenn er alle Präsentatoren in Modul / Komponente einbindet. (Es verstößt gegen das Prinzip der Schnittstellentrennung , möglicherweise auch gegen das Prinzip der Einzelverantwortung .)

  2. Wenn wir eine Scope-Komponente erstellen, wissen wir, wann sie erstellt und wann sie zerstört wird. Dies ist ein großer Vorteil, um Speicherlecks zu vermeiden. Daher sollten wir für jede Aktivität eine Komponente mit einem @ActivityScope erstellen. Stellen wir uns bei der Implementierung meiner Freunde vor, dass wir vergessen haben, einen Bereich in die Provider-Methode aufzunehmen => Speicherlecks werden auftreten.

Meiner Meinung nach könnten wir mit einer kleinen App (nur ein paar Bildschirme ohne viele Abhängigkeiten oder mit ähnlichen Abhängigkeiten) die Idee meiner Freunde anwenden, aber das wird natürlich nicht empfohlen.

Lesen Sie lieber weiter: Was bestimmt den Lebenszyklus einer Komponente (Objektdiagramm) in Dolch 2? Dagger2 Aktivitätsumfang, wie viele Module / Komponenten benötige ich?

Und noch ein Hinweis: Wenn Sie sehen möchten, wann das Objekt zerstört wird, können Sie die Methoden zusammen aufrufen, und der GC wird sofort ausgeführt:

    System.runFinalization();
    System.gc();

Wenn Sie nur eine dieser Methoden verwenden, wird GC später ausgeführt, und Sie erhalten möglicherweise falsche Ergebnisse.

Herr Mike
quelle

Antworten:

85

Es Activityist überhaupt keine gute Idee, für jedes Modul ein eigenes Modul zu deklarieren . ActivityNoch schlimmer ist es, für jede einzelne Komponente eine eigene zu deklarieren . Die Gründe dafür sind sehr einfach: Sie benötigen nicht wirklich alle diese Module / Komponenten (wie Sie bereits selbst gesehen haben).

ApplicationEs Activitiesist jedoch auch nicht die optimale Lösung , nur eine Komponente zu haben, die an den Lebenszyklus gebunden ist, und sie zur Injektion in alle zu verwenden (dies ist der Ansatz Ihres Freundes). Es ist nicht optimal, weil:

  1. Es beschränkt Sie auf nur einen Bereich ( @Singletonoder einen benutzerdefinierten).
  2. Der einzige Bereich, auf den Sie beschränkt sind, macht die injizierten Objekte zu "Anwendungs-Singletons". Daher können Fehler beim Gültigkeitsbereich oder die falsche Verwendung von Objekten mit Gültigkeitsbereich leicht zu globalen Speicherverlusten führen
  3. Sie möchten Dagger2 verwenden, um auch zu injizieren Services, Serviceskönnen jedoch andere Objekte als benötigen Activities(z. B. Serviceskeine Moderatoren benötigen, keine haben FragmentManagerusw.). Durch die Verwendung einer einzelnen Komponente verlieren Sie die Flexibilität, verschiedene Objektdiagramme für verschiedene Komponenten zu definieren.

Eine Komponente pro Activityist also ein Overkill, aber eine einzelne Komponente für die gesamte Anwendung ist nicht flexibel genug. Die optimale Lösung liegt zwischen diesen Extremen (wie es normalerweise der Fall ist).

Ich benutze den folgenden Ansatz:

  1. Einzelne "Anwendungs" -Komponente, die "globale" Objekte bereitstellt (z. B. Objekte mit globalem Status, der von allen Komponenten in der Anwendung gemeinsam genutzt wird). Instanziiert in Application.
  2. Unterkomponente "Controller" der Komponente "application", die Objekte bereitstellt, die von allen benutzerbezogenen "Controllern" benötigt werden (in meiner Architektur sind dies Activitiesund Fragments). Instanziiert in jedem Activityund Fragment.
  3. Unterkomponente "Service" der Komponente "application", die Objekte bereitstellt, die von allen benötigt werden Services. In jedem instanziiert Service.

Im Folgenden finden Sie ein Beispiel dafür, wie Sie denselben Ansatz implementieren können.


Bearbeiten Juli 2017

Ich habe ein Video-Tutorial veröffentlicht, das zeigt, wie Dagger-Abhängigkeitsinjektionscode in einer Android-Anwendung strukturiert wird: Android Dagger for Professionals Tutorial .


Bearbeiten Februar 2018

Ich habe einen vollständigen Kurs über Abhängigkeitsinjektion in Android veröffentlicht .

In diesem Kurs erkläre ich die Theorie der Abhängigkeitsinjektion und zeige, wie sie in Android-Anwendungen auf natürliche Weise entsteht. Dann zeige ich, wie Dolchkonstrukte in das allgemeine Abhängigkeitsinjektionsschema passen.

Wenn Sie diesen Kurs belegen, werden Sie verstehen, warum die Idee, für jede Aktivität / jedes Fragment eine separate Definition des Moduls / der Komponente zu haben, im Grunde genommen grundlegend fehlerhaft ist.

Ein solcher Ansatz bewirkt, dass die Struktur der Präsentationsschicht aus der "funktionalen" Klasse von Klassen in die Struktur der "Konstruktion" von Klassen gespiegelt wird, wodurch sie miteinander gekoppelt werden. Dies widerspricht dem Hauptziel der Abhängigkeitsinjektion, die darin besteht, die Klassensätze "Konstruktion" und "Funktional" disjunkt zu halten.


Anwendungsbereich:

@ApplicationScope
@Component(modules = ApplicationModule.class)
public interface ApplicationComponent {

    // Each subcomponent can depend on more than one module
    ControllerComponent newControllerComponent(ControllerModule module);
    ServiceComponent newServiceComponent(ServiceModule module);

}


@Module
public class ApplicationModule {

    private final Application mApplication;

    public ApplicationModule(Application application) {
        mApplication = application;
    }

    @Provides
    @ApplicationScope
    Application applicationContext() {
        return mApplication;
    }

    @Provides
    @ApplicationScope
    SharedPreferences sharedPreferences() {
        return mApplication.getSharedPreferences(Constants.PREFERENCES_FILE, Context.MODE_PRIVATE);
    }

    @Provides
    @ApplicationScope
    SettingsManager settingsManager(SharedPreferences sharedPreferences) {
        return new SettingsManager(sharedPreferences);
    }
}

Controller-Bereich:

@ControllerScope
@Subcomponent(modules = {ControllerModule.class})
public interface ControllerComponent {

    void inject(CustomActivity customActivity); // add more activities if needed

    void inject(CustomFragment customFragment); // add more fragments if needed

    void inject(CustomDialogFragment customDialogFragment); // add more dialogs if needed

}



@Module
public class ControllerModule {

    private Activity mActivity;
    private FragmentManager mFragmentManager;

    public ControllerModule(Activity activity, FragmentManager fragmentManager) {
        mActivity = activity;
        mFragmentManager = fragmentManager;
    }

    @Provides
    @ControllerScope
    Context context() {
        return mActivity;
    }

    @Provides
    @ControllerScope
    Activity activity() {
        return mActivity;
    }

    @Provides
    @ControllerScope
    DialogsManager dialogsManager(FragmentManager fragmentManager) {
        return new DialogsManager(fragmentManager);
    }

    // @Provides for presenters can be declared here, or in a standalone PresentersModule (which is better)
}

Und dann in Activity:

public class CustomActivity extends AppCompatActivity {

    @Inject DialogsManager mDialogsManager;

    private ControllerComponent mControllerComponent;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getControllerComponent().inject(this);

    }

    private ControllerComponent getControllerComponent() {
        if (mControllerComponent == null) {

            mControllerComponent = ((MyApplication)getApplication()).getApplicationComponent()
                    .newControllerComponent(new ControllerModule(this, getSupportFragmentManager()));
        }

        return mControllerComponent;
    }
}

Zusätzliche Informationen zur Abhängigkeitsinjektion:

Dolch 2 Bereiche entmystifiziert

Abhängigkeitsinjektion in Android

Vasiliy
quelle
1
Vielen Dank an @vasiliy für Ihre Meinung. Genau so würde ich es verwenden und derzeit Strategie verfolgen. Im Falle eines MVP-Musters erstellt der ControllerModulereferenzierte ein neues Presenterund dann wird der Präsentator in das Activityoder injiziert Fragment. Eine solide Meinung dafür oder dagegen?
Wahib Ul Haq
@ Vasiliy, ich habe Ihren gesamten Artikel gelesen und festgestellt, dass Sie Interaktoren und Moderatoren möglicherweise nicht als Mechanismen berücksichtigt haben . Bietet ControllerModule alle Abhängigkeiten von Interaktoren und Präsentatoren ? Bitte geben Sie einen kleinen Hinweis, falls ich etwas verpasst habe.
iamcrypticcoder
@ mahbub.kuet, wenn ich verstehe, worauf Sie sich unter "Interaktoren" und "Moderatoren" beziehen, ControllerComponentsollten Sie sie injizieren. Ob Sie sie im Inneren verdrahten ControllerModuleoder ein zusätzliches Modul einführen, liegt bei Ihnen. In echten Apps empfehle ich die Verwendung eines Ansatzes mit mehreren Modulen pro Komponente, anstatt alles in einem einzigen Modul zusammenzufassen. Hier ist ein Beispiel für ApplicationComponent, aber die Controller werden die gleichen sein: github.com/techyourchance/idocare-android/tree/master/app/src/…
Vasiliy
2
@ Mr.Hyde, im Allgemeinen ja, aber dann müssen Sie explizit in ApplicationComponentallen Abhängigkeiten deklarieren , ControllerComponentdie verwendet werden können. Auch die Methodenanzahl des generierten Codes ist höher. Ich habe noch keinen guten Grund gefunden, abhängige Komponenten zu verwenden.
Vasiliy
1
Ich verwende diesen Ansatz heute in allen meinen Projekten und verwende ausdrücklich nichts aus dem dagger.androidPaket, weil ich finde, dass es schlecht motiviert ist. Daher ist dieses Beispiel immer noch sehr aktuell und der beste Weg, DI in Android IMHO zu machen.
Vasiliy
15

Einige der besten Beispiele für die Organisation Ihrer Komponenten, Module und Pakete finden Sie im Github-Repo von Google Android Architecture Blueprints hier .

Wenn Sie den Quellcode dort untersuchen, sehen Sie, dass es eine einzelne Komponente mit App-Bereich gibt (mit einem Lebenszyklus der Dauer der gesamten App) und dann Komponenten mit Aktivitätsbereich für die Aktivität und das Fragment, die einer bestimmten Funktionalität in a entsprechen Projekt. Zum Beispiel gibt es die folgenden Pakete:

addedittask
taskdetail
tasks

In jedem Paket befindet sich ein Modul, eine Komponente, ein Präsentator usw. Im Inneren taskdetailbefinden sich beispielsweise die folgenden Klassen:

TaskDetailActivity.java
TaskDetailComponent.java
TaskDetailContract.java
TaskDetailFragment.java
TaskDetailPresenter.java
TaskDetailPresenterModule.java

Der Vorteil dieser Organisation (anstatt alle Aktivitäten in einer Komponente oder einem Modul zu gruppieren) besteht darin, dass Sie die Java-Eingabehilfen-Modifikatoren nutzen und den effektiven Java-Punkt 13 erfüllen können. Mit anderen Worten, die funktional gruppierten Klassen befinden sich in derselben verpacken und Sie können die Vorteile der protectedund package-private Zugänglichkeit Modifikatoren zu ungewollten Nutzungen Ihrer Klassen zu verhindern.

David Rawson
quelle
1
Dies ist auch mein bevorzugter Ansatz. Ich mag keine Aktivitäten / Fragmente, die Zugang zu Dingen haben, die sie nicht sollen.
Joao Sousa
3

Die erste Option erstellt eine Komponente mit Unterbereich für jede Aktivität, wobei die Aktivität Komponenten mit Unterbereich erstellen kann, die nur die Abhängigkeit (Präsentator) für diese bestimmte Aktivität bereitstellen.

Die zweite Option erstellt eine einzelne @SingletonKomponente, die die Präsentatoren als nicht abgedeckte Abhängigkeiten bereitstellen kann. Wenn Sie also auf sie zugreifen, erstellen Sie jedes Mal eine neue Instanz des Präsentators. (Nein, es wird keine neue Instanz erstellt, bis Sie eine anfordern.)


Technisch gesehen ist keiner der Ansätze schlechter als der andere. Der erste Ansatz trennt Präsentatoren nicht nach Merkmalen, sondern nach Ebenen.

Ich habe beide benutzt, beide funktionieren und beide machen Sinn.

Der einzige Nachteil der ersten Lösung (wenn Sie @Component(dependencies={...}anstelle von verwenden @Subcomponent) besteht darin, dass Sie sicherstellen müssen, dass nicht die Aktivität intern ein eigenes Modul erstellt, da Sie dann die Implementierung von Modulmethoden nicht durch Mocks ersetzen können. Wenn Sie die Konstruktorinjektion anstelle der Feldinjektion verwenden, können Sie die Klasse einfach direkt mit dem Konstruktor erstellen und sie direkt verspotten.

EpicPandaForce
quelle
1

Verwenden Sie Provider<"your component's name">anstelle der einfachen Komponentenimplementierung, um Speicherlecks zu vermeiden und Tonnen nutzloser Komponenten zu erstellen. Daher werden Ihre Komponenten beim Aufruf der Methode get () von Lazy erstellt, da Sie keine Instanz der Komponente, sondern nur den Provider angeben. Daher wird Ihr Moderator angewendet, wenn .get () des Anbieters aufgerufen wurde. Lesen Sie hier über den Anbieter und wenden Sie diesen an. ( Offizielle Dolchdokumentation )


Eine andere gute Möglichkeit ist die Verwendung von Multibinding. Dementsprechend sollten Sie Ihre Präsentatoren in eine Karte einbinden und sie bei Bedarf über Anbieter erstellen. ( Hier finden Sie Dokumente zum Thema Multibinding )

Konstantin Levitskiy
quelle
-5

Ihr Freund hat Recht, Sie müssen nicht wirklich für jede Aktivität Komponenten und Module erstellen. Dagger soll Ihnen dabei helfen, unordentlichen Code zu reduzieren und Ihre Android-Aktivitäten sauberer zu gestalten, indem Klasseninstanziierungen an die Module delegiert werden, anstatt sie in der onCreate-Methode von Activities zu instanziieren.

Normalerweise machen wir das so

public class MainActivity extends AppCompatActivity {


Presenter1 mPresenter1;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mPresenter1 = new Presenter1(); // you instantiate mPresentation1 in onCreate, imagine if there are 5, 10, 20... of objects for you to instantiate.
}

}

Sie tun dies stattdessen

public class MainActivity extends AppCompatActivity {

@Inject
Presenter1 mPresenter1; // the Dagger module take cares of instantiation for your

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    injectThisActivity();
}

private void injectThisActivity() {
    MainApplication.get(this)
            .getMainComponent()
            .inject(this);
}}

Zu viele Dinge zu schreiben, besiegt also den Zweck des Dolches, nein? Ich instanziiere meine Präsentatoren eher in Aktivitäten, wenn ich für jede Aktivität Module und Komponenten erstellen muss.

Was Ihre Fragen betrifft zu:

1- Speicherverlust:

Nein, es sei denn, Sie @Singletonfügen den von Ihnen bereitgestellten Moderatoren eine Anmerkung hinzu. Dolch erstellt das Objekt nur, wenn Sie eine @Injectin der Zielklasse ausführen. Die anderen Präsentatoren in Ihrem Szenario werden nicht erstellt. Sie können versuchen, mithilfe von Protokoll festzustellen, ob sie erstellt wurden oder nicht.

@Module
public class AppPresenterModule {

@Provides
@Singleton // <-- this will persists throughout the application, too many of these is not good
Activity1Presenter provideActivity1Presentor(Context context, ...some other params){
    Log.d("Activity1Presenter", "Activity1Presenter initiated");
    return new Activity1PresenterImpl(context, ...some other params);
}

@Provides // Activity2Presenter will be provided every time you @Inject into the activity
Activity2Presenter provideActivity2Presentor(Context context, ...some other params){
    Log.d("Activity2Presenter", "Activity2Presenter initiated");
    return new Activity2PresenterImpl(context, ...some other params);
}

.... Same with 48 others presenters.

}}

2- Sie injizieren zweimal und protokollieren ihren Hash-Code

//MainActivity.java
@Inject Activity1Presenter mPresentation1
@Inject Activity1Presenter mPresentation2

@Inject Activity2Presenter mPresentation3
@Inject Activity2Presenter mPresentation4
//log will show Presentation2 being initiated twice

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    injectThisActivity();
    Log.d("Activity1Presenter1", mPresentation1.hashCode());
    Log.d("Activity1Presenter2", mPresentation2.hashCode());
    //it will shows that both have same hash, it's a Singleton
    Log.d("Activity2Presenter1", mPresentation3.hashCode());
    Log.d("Activity2Presenter2", mPresentation4.hashCode());
    //it will shows that both have different hash, hence different objects

3. Nein, die Objekte werden nur erstellt, wenn Sie @Injectan den Aktivitäten teilnehmen, anstatt an der App init.

Liew Jun Tung
quelle
1
Vielen Dank für den Kommentar, was Sie gesagt haben, ist nicht falsch, aber ich denke, es ist nicht die beste Antwort, siehe meinen Bearbeitungsbeitrag. Konnte es also nicht als akzeptiert markieren.
Herr Mike
@EpicPandaForce: Eh, aber du musst es irgendwo instanziieren. Etwas muss das Prinzip der Abhängigkeitsinversion verletzen.
David Liu