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


85

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

たとえば、アプリがあり、そのアプリには約50の画面があります。MVPパターンとDagger2forDIに従ってコードを実装します。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. スコープコンポーネントを作成すると、いつ作成され、いつ破棄されたかがわかります。これは、メモリリークを回避するための大きなメリットです。したがって、アクティビティごとに、@ ActivityScopeを使用してコンポーネントを作成する必要があります。私の友人の実装で、プロバイダーにスコープを入れるのを忘れたと想像してみましょう-メソッド=>メモリリークが発生します。

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

詳細を読むことをお勧めします: Dagger 2のコンポーネント(オブジェクトグラフ)のライフサイクルを決定するものは何ですか? Dagger2アクティビティスコープ、いくつのモジュール/コンポーネントが必要ですか?

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

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

これらのメソッドの1つだけを使用すると、GCが後で実行され、間違った結果が得られる可能性があります。

回答:


85

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

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

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

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

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

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

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


2017年7月編集

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


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での依存性注入


1
あなたの意見を共有してくれてありがとう@vasiliy。これはまさに私がそれを使用する方法であり、現在の戦略に従っています。MVPパターンの場合、参照ControllerModuleされたものが新しいものを作成しPresenter、プレゼンターがActivityまたはに挿入されFragmentます。これに賛成または反対の確固たる意見はありますか?
Wahib Ul Haq 2017

@Vasiliy、私はあなたの記事全体を読みました、そして多分あなたがメカニズムでインタラクターとプレゼンターを考慮しなかったことがわかりました。ControllerModuleは、インタラクタープレゼンターのすべての依存関係を提供ますか?私が何かを逃した場合に備えて、ちょっとしたヒントを教えてください。
iamcrypticcoder 2017年

@ mahbub.kuetは、あなたが「インタラクター」と「プレゼンター」によって何を指しているのか理解できれば、ControllerComponentそれらを注入する必要があります。それらを内部ControllerModuleに配線するか、追加のモジュールを導入するかはあなた次第です。実際のアプリでは、すべてを1つのモジュールにまとめるのではなく、コンポーネントごとにマルチモジュールを使用することをお勧めします。の例を次に示しますがApplicationComponent、コントローラーは同じになります。github.com
Vasiliy

2
@ Mr.Hyde、一般的にはそうですが、使用できるApplicationComponentすべての依存関係で明示的に宣言する必要ControllerComponentがあります。また、生成されたコードのメソッド数も多くなります。依存コンポーネントを使用する正当な理由はまだ見つかりません。
Vasiliy 2017

1
私は今日すべてのプロジェクトでこのアプローチを使用していますdagger.androidが、やる気がないことがわかったため、パッケージからは明示的に何も使用していません。したがって、この例はまだ非常に最新であり、AndroidIMHOでDIを実行するための最良の方法です。
Vasiliy 2018年

15

コンポーネント、モジュール、パッケージを整理する方法の最良の例のいくつかは、こちらのGoogleAndroidアーキテクチャブループリントGithubリポジトリにあります。

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

addedittask
taskdetail
tasks

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

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

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


1
これも私の好みのアプローチです。私は、アクティビティ/フラグメントが想定外のものにアクセスできるのが好きではありません。
Joao Sousa

3

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

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


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

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

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


1

Provider<"your component's name">単純なコンポーネントの実装の代わりに使用して、メモリリークを回避し、無用なコンポーネントを大量に作成します。したがって、コンポーネントのインスタンスを提供せず、代わりにプロバイダーのみを提供するため、get()メソッドを呼び出すと、コンポーネントはレイジーによって作成されます。したがって、プロバイダーの.get()が呼び出された場合、プレゼンターが適用されます。ここでプロバイダーについて読んで、これを適用してください。(公式ダガードキュメント


そして、他の素晴らしい方法は、マルチバインディングを使用することです。それに応じて、プレゼンターをマップにバインドし、必要に応じてプロバイダーを介して作成する必要があります。(ここにマルチバインディングに関するドキュメントがあります


-5

あなたの友人は正しいです、あなたは本当にすべての活動のためにコンポーネントとモジュールを作成する必要はありません。Daggerは、ActivitiesのonCreateメソッドでクラスのインスタンス化をインスタンス化するのではなく、モジュールにクラスのインスタンス化を委任することで、厄介なコードを減らし、Androidアクティビティをよりクリーンにするのに役立つはずです。

通常はこのようにします

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

}

代わりにこれを行います

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

それで、あまりにも多くのことを書くことは、短剣の目的を打ち負かしますか?すべてのアクティビティのモジュールとコンポーネントを作成する必要がある場合は、アクティビティでプレゼンターをインスタンス化します。

についてのあなたの質問に関して:

1-メモリリーク:

いいえ、入れない限り @Singleton提供するプレゼンターに注釈を。Daggerは@Inject、ターゲットクラスでを実行するたびにのみオブジェクトを作成します`。シナリオ内に他のプレゼンターは作成されません。ログを使用して、作成されているかどうかを確認できます。

@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- 2回注入し、ハッシュコードをログに記録します

//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.いいえ、オブジェクトは@Inject、アプリのinitではなく、アクティビティにアクセスしたときにのみ作成されます。


1
コメントありがとうございます、あなたが言ったことは間違っていませんが、それは最善の答えではないと思います、私の編集投稿を見てください。そのため、承認済みとしてマークできませんでした。
マイク氏

@EpicPandaForce:ええ、でもどこかでインスタンス化する必要があります。何かが依存性逆転の原則に違反する必要があります。
David Liu
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.