web-dev-qa-db-ja.com

ダガー-各アクティビティ/フラグメントの各コンポーネントとモジュールを作成する必要があります

私はしばらくの間dagger2を使用しています。そして、アクティビティ/フラグメントごとに独自のコンポーネント/モジュールを作成するかどうか混乱しました。これを明確にするのを手伝ってください:

たとえば、アプリがあり、アプリには約50の画面があります。 MVPパターンとDI用のDagger2に従ってコードを実装します。 50のアクティビティと50のプレゼンターがいるとします。

私の意見では、通常、次のようにコードを整理する必要があります。

  1. アプリが開いているときに使用されるすべてのオブジェクトを提供するAppComponentとAppModuleを作成します。

    @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. ActivityScopeを作成します。

    @Scope
    @Documented
    @Retention(value=RUNTIME)
    public @interface ActivityScope {
    }
    
  3. 各アクティビティのコンポーネントとモジュールを作成します。通常、Activityクラス内に静的クラスとして配置します。

    @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.
    

これらは、これをどのように実装するかを示す非常に単純な例です。

しかし、私の友人はちょうど私に別の実装を与えました:

  1. すべてのプレゼンターを提供するPresenterModuleを作成します。

    @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. AppModuleと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, ....)
    
    }
    

彼の説明は次のとおりです:彼は各アクティビティのコンポーネントとモジュールを作成する必要はありません。私の友達のアイデアは絶対に良くないと思いますが、お願いします私が間違っている場合は修正してください。その理由は次のとおりです。

  1. 多くのメモリリーク

    • ユーザーが開いているアクティビティが2つしかない場合でも、アプリは50人のプレゼンターを作成します。
    • ユーザーがアクティビティを閉じた後も、そのプレゼンターは残ります
  2. 1つのアクティビティの2つのインスタンスを作成する場合はどうなりますか? (どうすれば2人のプレゼンターを作成できますか)

  3. アプリの初期化には多くの時間がかかります(多くのプレゼンター、オブジェクトを作成する必要があるため...)

長い投稿は申し訳ありませんが、私と友人のためにこれを明確にするのを手伝ってください、私は彼を納得させることができません。あなたのコメントは非常に高く評価されます。

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

デモを行った後に編集します。

まず、@ pandawarriorの回答に感謝します。この質問をする前に、デモを作成する必要がありました。ここでの私の結論が他の誰かに役立つことを願っています。

  1. 私の友人がやったことは、彼が提供メソッドにスコープを置かない限り、メモリリークを引き起こしません。 (たとえば、@ Singleton、または@UserScope、...)
  2. Providesメソッドにスコープがない場合、多くのプレゼンターを作成できます。 (つまり、私の2番目のポイントも間違っています)
  3. Daggerは、必要な場合にのみプレゼンターを作成します。 (したがって、アプリの初期化に長い時間がかかりません、私はレイジーインジェクションに混乱しました)

それで、私が上で言ったすべての理由はほとんど間違っています。ただし、次の2つの理由により、友人の考えに従う必要があるという意味ではありません。

  1. 彼がモジュール/コンポーネントのすべてのプレゼンターを初期化するとき、それはソースのアーキテクチャにとって良くありません。 (これは インターフェイス分離の原則 に違反しているかもしれませんが、多分 単一の責任 原則にも違反しています)。

  2. Scopeコンポーネントを作成すると、いつ作成され、いつ破棄されるかがわかります。これは、メモリリークを回避するための大きな利点です。したがって、アクティビティごとに、@ ActivityScopeを持つコンポーネントを作成する必要があります。私の友人の実装で、Providerメソッドにスコープを入れるのを忘れていたと想像してみてください=>メモリリークが発生します。

私の意見では、小さなアプリ(多くの依存関係のない、または同様の依存関係のある少数の画面)で、友人のアイデアを適用できますが、もちろんお勧めできません。

Dagger 2のコンポーネント(オブジェクトグラフ)のライフサイクルを決定するものDagger2アクティビティスコープ、必要なモジュール/コンポーネントの数

そしてもう1つの注意:オブジェクトがいつ破棄されるかを確認したい場合は、メソッドのメソッドを一緒に呼び出すと、GCがすぐに実行されます。

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

これらの方法のいずれか1つのみを使用すると、GCは後で実行され、誤った結果が得られる場合があります。

78
Mr Mike

Activityごとに個別のモジュールを宣言することは、まったく良い考えではありません。 Activityごとに個別のコンポーネントを宣言するのはさらに悪いです。この背後にある理由は非常に単純です-これらのすべてのモジュール/コンポーネントが本当に必要なわけではありません(すでに自分で見たように)。

ただし、Applicationのライフサイクルに関連付けられたコンポーネントを1つだけにして、すべてのActivitiesへの注入に使用することも最適な解決策ではありません(これは友人のアプローチです)。次の理由により最適ではありません。

  1. スコープを1つだけに制限します(@Singletonまたはカスタムスコープ)
  2. 制限されている唯一のスコープは、注入されたオブジェクトを「アプリケーションシングルトン」にするため、スコープ設定されたオブジェクトのスコープ設定の誤りまたは誤った使用は、グローバルメモリリークを容易に引き起こす可能性があります
  3. Dagger2を使用してServicesに注入することもできますが、ServicesActivitiesとは異なるオブジェクトを必要とする場合があります(たとえば、Servicesはプレゼンターを必要とせず、 tにはFragmentManagerなどがあります)。単一のコンポーネントを使用すると、異なるコンポーネントに対して異なるオブジェクトグラフを定義する柔軟性が失われます。

したがって、Activityごとのコンポーネントは過剰ですが、アプリケーション全体の単一のコンポーネントでは十分な柔軟性がありません。最適なソリューションは、これらの両極端の間にあります(通常どおり)。

私は次のアプローチを使用します。

  1. 「グローバル」オブジェクト(アプリケーション内のすべてのコンポーネント間で共有されるグローバル状態を保持するオブジェクトなど)を提供する単一の「アプリケーション」コンポーネント。 Applicationでインスタンス化されます。
  2. すべてのユーザー向けの「コントローラー」に必要なオブジェクトを提供する「アプリケーション」コンポーネントの「コントローラー」サブコンポーネント(私のアーキテクチャでは、これらはActivitiesおよびFragmentsです)。各ActivityおよびFragmentでインスタンス化されます。
  3. すべてのServicesに必要なオブジェクトを提供する「アプリケーション」コンポーネントの「サービス」サブコンポーネント。各Serviceでインスタンス化されます。

以下は、同じアプローチを実装する方法の例です。


2017年7月編集

AndroidアプリケーションでDagger依存性注入コードを構成する方法を示すビデオチュートリアルを公開しました: Android Dagger for Professionals Tutorial


2018年2月編集

Androidでの依存性注入に関する完全なコース を公開しました。

このコースでは、依存性注入の理論を説明し、Androidアプリケーションでどのように自然に現れるかを示します。次に、Daggerコンストラクトが一般的な依存関係注入スキームにどのように適合するかを示します。

このコースを受講すると、アクティビティ/フラグメントごとにモジュール/コンポーネントを個別に定義するという考え方が、基本的に最も根本的な欠陥がある理由を理解できます。

このようなアプローチにより、クラスの「機能」セットのプレゼンテーション層の構造が、クラスの「構築」セットの構造にミラーリングされ、それらが結合されます。これは、依存関係の注入の主な目的に反します。これは、クラスの「構築」と「機能」のセットを分離することです。


適用範囲:

@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);
    }
}

コントローラーのスコープ:

@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)
}

そして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;
    }
}

依存性注入に関する追加情報:

ダガー2スコープの分かりやすい説明

Androidでの依存性注入

74
Vasiliy

コンポーネント、モジュール、パッケージを整理する方法の最良の例は、Google Android Architecture Blueprints Github repo here にあります。

そこのソースコードを調べると、1つのアプリスコープのコンポーネント(アプリ全体のライフサイクル)が1つあり、次に、特定の機能に対応するアクティビティとフラグメントのアクティビティスコープのコンポーネントが別々にあることがわかります。事業。たとえば、次のパッケージがあります。

addedittask
taskdetail
tasks

各パッケージ内には、モジュール、コンポーネント、プレゼンターなどがあります。たとえば、taskdetail内には次のクラスがあります。

TaskDetailActivity.Java
TaskDetailComponent.Java
TaskDetailContract.Java
TaskDetailFragment.Java
TaskDetailPresenter.Java
TaskDetailPresenterModule.Java

(1つのコンポーネントまたはモジュール内のすべてのアクティビティをグループ化するのではなく)この方法で整理することの利点は、Javaアクセシビリティ修飾子を利用して、Effective Javaアイテム13を満たすことができることです。つまり、機能的にグループ化されたクラスは同じパッケージに含まれ、protectedおよびpackage-privateaccessibility modifiers を利用して、クラスの意図しない使用を防ぐことができます。

14
David Rawson

最初のオプションは、アクティビティごとにサブスコープコンポーネントを作成します。アクティビティは、特定のアクティビティの依存関係(プレゼンター)のみを提供するサブスコープコンポーネントを作成できます。

2番目のオプションは、プレゼンターをスコープ外の依存関係として提供できる単一の@Singletonコンポーネントを作成します。つまり、それらにアクセスすると、毎回プレゼンターの新しいインスタンスが作成されます。 (いいえ、リクエストするまで新しいインスタンスは作成されません)。


技術的には、どちらのアプローチも他のアプローチより悪いわけではありません。最初のアプローチでは、機能ごとにプレゼンターを分離するのではなく、レイヤーごとに分離します。

私は両方を使用しましたが、どちらも機能し、両方とも理にかなっています。

最初のソリューションの唯一の欠点(@Component(dependencies={...}の代わりに@Subcomponentを使用している場合)は、モジュールを内部で作成するアクティビティではないことを確認する必要があることです。モックを使用したメソッドの実装。繰り返しますが、フィールドインジェクションの代わりにコンストラクターインジェクションを使用する場合、コンストラクターでクラスを直接作成し、モックを直接作成することができます。

3
EpicPandaForce