この記事では、読者が既にDagger 2に精通しており、コンポーネント、モジュール、インジェクション、オブジェクトグラフとは何か、またそれらがどのように連携するかを理解していることを前提としています。 ここでは、まず、ActivityScopeの作成と、それをフラグメントにリンクする方法に集中します。
それでは、行きましょう...スコープとは何ですか?

スコープは、独自のライフサイクルを持つ特定のオブジェクトセットを保存できるDagger 2メカニズムです。 言い換えると、スコープは、開発者に依存する独自の存続期間を持つオブジェクトのグラフです。
デフォルトでは、Dagger 2はそのままでjavax.inject.Singletonスコープのサポートを提供します 。 原則として、このスコープ内のオブジェクトは、アプリケーションのインスタンスが存在する限り存在します。
さらに、独自の追加スコープを作成する可能性に制限はありません。 カスタムスコープの良い例は、ユーザーがアプリケーションで承認されている限りオブジェクトが存在するUserScopeです。 ユーザーセッションが終了するか、ユーザーがアプリケーションを明示的に終了するとすぐに、オブジェクトグラフは破棄され、次の承認時に再作成されます。 このようなスコープでは、特定のユーザーに関連付けられたオブジェクトを保存すると便利であり、他のユーザーにとっては意味がありません。 たとえば、特定のユーザーのアカウントのリストを表示できるAccountManagerなど。

図は、アプリケーションのシングルトンおよびユーザースコープのライフサイクルの例を示しています。
- 起動すると、 Singletonスコープが作成され、その有効期間はアプリケーションの有効期間と等しくなります。 つまり、システムがメモリからアプリケーションを破棄およびアンロードするまで、シングルトンスコープに属するオブジェクトが存在します。
- アプリケーションを開始すると、アプリケーションでUser1が承認されます。 この時点で、ユーザーにとって意味のあるオブジェクトを含むUserScopeが作成されます。
- しばらくして、ユーザーは「終了」してアプリケーションからログアウトすることにしました。
- これでUser2が承認され、2番目のユーザーのUserScopeオブジェクトの作成が開始されます。
- ユーザーセッションの有効期限が切れると、オブジェクトのグラフが破壊されます。
- User1はアプリケーションに戻り、 ログインし、 UserScopeオブジェクトのグラフを作成して、アプリケーションをバックグラウンドに送信します。
- しばらくして、リソース不足の状況にあるシステムは、アプリケーションのメモリーを停止してアンロードする決定を下します。 これにより、 UserScopeとSingletonScopeの両方が破壊されます。
うまくいけば、スコープが少し整理されます。
次に、 ActivityScopeの例に戻ります。 実際のAndroidアプリケーションでは、ActivityScopeは非常に便利です。 もちろんそうです! さまざまなフラグメントのかかと、アダプターの束、ヘルパー、プレゼンターなど、クラスの束で構成される複雑な画面を想像するだけで十分です。 この場合、一般的なはずのビジネスロジックのモデルおよび/またはクラスをそれらの間で「手探りする」ことが理想的です。

- 自作のシングルトン、アプリケーションクラス、または静的変数を使用して、参照を共通オブジェクトに転送します。 OOPとSOLIDの原則に違反しているため、コードが混乱し、読みにくく、サポートされていないため、このアプローチは絶対に好きではありません。
- セッターまたはコンストラクターを介して、アクティビティから必要なクラスにオブジェクトを独立して転送します。 このアプローチの欠点は、代わりに新しい機能の作成に集中できる場合に、ルーチンコードを作成するコストです。
- Dagger 2を使用して、アプリケーションの必要な場所に共有オブジェクトを注入します。 この場合、テンプレートコードを書く時間を無駄にすることなく、2番目のアプローチのすべての利点を得ることができます。 実際、ミドルウェアの記述をライブラリに移行しています。
Dagger 2を使用してActivityScopeを作成および使用する手順を見てみましょう。
したがって、カスタムスコープを作成するには、以下が必要です。
- スコープの宣言(注釈の作成)
- 少なくとも1つのコンポーネントとスコープの対応するモジュールを宣言します
- 適切なタイミングで、オブジェクトグラフをインスタンス化し、使用後に削除します
デモアプリケーションのインターフェイスは、 ActivityとActivityBの 2つの画面と、 SharedFragmentアクティビティで使用される共通のフラグメントで構成されます。


アプリケーションには、 SingletonとActivityScopeの 2つのスコープがあります。
従来、ビンはすべて3つのグループに分類できます。
- シングルトン- シングルトンビーン
- アクティビティ内部でのみ必要なアクティビティBeanスコープ-BeanAおよびBeanB
- アクティビティBeanと、アクティビティactivopoopeの他の場所(フラグメントなど)の両方からのアクセスが必要なアクティベーションBeanスコープ-SharedBean
各Beanは、作成時に一意のIDを取得します。 これにより、スコープが意図したとおりに機能するかどうかを明確に理解できるようになります。これは、各新しいBeanインスタンスのIDが以前のものと異なるためです。

したがって、アプリケーションにはオブジェクトの3つのグラフ(3つのコンポーネント)があります
- SingletonComponent-アプリケーションの実行中に存在し、システムによって強制終了されないオブジェクトのグラフ
- ComponentActivityA -ActivityAの操作に必要なオブジェクト(そのフラグメント、アダプター、プレゼンターなどを含む)のグラフで、ActivityAのインスタンスがある限り存在します。 アクティビティを破棄して再作成すると、グラフも破棄され、アクティビティの新しいインスタンスとともに再作成されます。 このグラフは、シングルトンスコープのすべてのオブジェクトを含むスーパーセットです。
- ComponentActivityBも同様のグラフですが、ActivityBの場合

実装に移りましょう。 開始するには、Dagger 2をプロジェクトに接続します。 これを行うには、ルートbuild.gradleでandroid-aptプラグインを接続します...
buildscript { //... dependencies { //... classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' } }
app / build.gradleの Dagger 2自体
dependencies { compile 'com.google.dagger:dagger:2.7' apt 'com.google.dagger:dagger-compiler:2.7' }
次に、シングルトンを提供するモジュールを宣言します
@Module public class SingletonModule { @Singleton @Provides SingletonBean provideSingletonBean() { return new SingletonBean(); } }
およびシングルトンコンポーネント:
@Singleton @Component(modules = SingletonModule.class) public interface SingletonComponent { }
インジェクターを作成します。これは、ダガー2ではなく、制御するアプリケーション内の唯一のシングルトンであり、ダガーのシングルトンスコープを保持し、インジェクションを担当します。
public final class Injector { private static final Injector INSTANCE = new Injector(); private SingletonComponent singletonComponent; private Injector() { singletonComponent = DaggerSingletonComponent.builder() .singletonModule(new SingletonModule()) .build(); } public static SingletonComponent getSingletonComponent() { return INSTANCE.singletonComponent; } }
ActivityScopeを宣言します。 スコープを宣言するには、スコープの名前で注釈を作成し、 javax.inject.Scope注釈でマークする必要があります 。
@Scope public @interface ActivityScope { }
Beanをモジュールにグループ化:共有およびアクティビティ用
@Module public class ModuleA { @ActivityScope @Provides BeanA provideBeanA() { return new BeanA(); } } @Module public class ModuleB { @ActivityScope @Provides BeanB provideBeanB() { return new BeanB(); } } @Module public class SharedModule { @ActivityScope @Provides SharedBean provideSharedBean() { return new SharedBean(); } }
アクティビティの対応するコンポーネントを宣言します。 別のコンポーネントのオブジェクトを含むコンポーネントを実装するには、 サブコンポーネントとコンポーネントの依存関係の 2つの方法があります 。 前者の場合、子コンポーネントは親コンポーネントのすべてのオブジェクトに自動的にアクセスできます。 2番目-親コンポーネントでは、子にエクスポートするオブジェクトのリストを明示的に指定する必要があります。 私の意見では、1つのアプリケーションのフレームワークでは、最初のオプションを使用する方が便利です。
@ActivityScope @Subcomponent(modules = {ModuleA.class, SharedModule.class}) public interface ComponentActivityA { void inject(ActivityA activity); void inject(SharedFragment fragment); } @ActivityScope @Subcomponent(modules = {ModuleB.class, SharedModule.class}) public interface ComponentActivityB { void inject(ActivityB activity); void inject(SharedFragment fragment); }
作成されたサブコンポーネントで、注入ポイントを宣言します。 この例では、 アクティビティとSharedFragmentという 2つのポイントがあります 。 彼らはSharedBean共有Beanを共有します 。
サブコンポーネントインスタンスは、サブコンポーネントモジュールのオブジェクトを既存のグラフに追加することにより、親コンポーネントから取得されます。 この例では、親コンポーネントはSingletonComponentで 、サブコンポーネントを作成するためのメソッドを追加します。
@Singleton @Component(modules = SingletonModule.class) public interface SingletonComponent { ComponentActivityA newComponent(ModuleA a, SharedModule shared); ComponentActivityB newComponent(ModuleB b, SharedModule shared); }
以上です。 インフラストラクチャ全体の準備が完了し、宣言されたコンポーネントをインスタンス化し、依存関係を注入します。 フラグメントから始めましょう。
フラグメントは2つの異なるアクティビティの内部ですぐに使用されるため、フラグメントが配置されているアクティビティに関する特定の詳細を知る必要はありません。 ただし、スコープのオブジェクトのグラフにアクセスするには、アクティビティコンポーネントにアクセスする必要があります。 この「問題」を解決するために、 Inversion of Controlパターンを使用して、アクティビティとの相互作用を構築する中間のInjectorProviderインターフェイスを作成します。
public class SharedFragment extends Fragment { @Inject SharedBean shared; @Inject SingletonBean singleton; //… @Override public void onAttach(Context context) { super.onAttach(context); if (context instanceof InjectorProvider) { ((InjectorProvider) context).inject(this); } else { throw new IllegalStateException("You should provide InjectorProvider"); } } public interface InjectorProvider { void inject(SharedFragment fragment); } }
各アクティビティ内のActivityScopeレベルのコンポーネントをインスタンス化し、アクティビティとその中に含まれるフラグメントを注入することは残ります
public class ActivityA extends AppCompatActivity implements SharedFragment.InjectorProvider { @Inject SharedBean shared; @Inject BeanA a; @Inject SingletonBean singleton; ComponentActivityA component = Injector.getSingletonComponent() .newComponent(new ModuleA(), new SharedModule()); //... @Override public void inject(SharedFragment fragment) { component.inject(this); component.inject(fragment); } }
主なポイントを繰り返します。
- 2つの異なるスコープを作成しました: シングルトンとActivityScope
- ActivityScopeは、コンポーネントの依存関係ではなく、 サブコンポーネントを通じて実装されるため、シングルトンスコープからすべてのBeanを明示的に公開する必要はありません。
- アクティビティは、対応するActivityScopのオブジェクトのグラフへのリンクを保存し、それ自体と、ActivityScopeからBeanを注入するすべてのクラス(SharedFragmentなど)を注入します。
- アクティビティが破壊されると、特定のアクティビティのオブジェクトのグラフも破壊されます。
- オブジェクトのシングルトングラフは、アプリケーションインスタンスが存在する限り存在します。
一見すると、このような単純なタスクを実装するには、大量の接続コードを記述する必要があるように思えるかもしれません。 デモアプリケーションでは、「作業」を実行するクラス(ビン、フラグメント、およびアクティビティ)の数は、「接続」ダガークラスの数とほぼ同等です。 ただし:
- 実際のプロジェクトでは、「作業」クラスの数ははるかに多くなります。
- 接続コードを一度書いてから、必要なコンポーネントとモジュールを追加するだけで十分です。
- DIを使用すると、テストが非常に簡単になります。 テスト中に実際のビンの代わりにmokとスタブを挿入する追加の機会があります
- ミドルウェアおよびインスタンスコードを短剣クラスに転送するため、ビジネスロジックコードはより分離され、簡潔になります。 同時に、ビジネスロジックのみがビジネスロジッククラス自体に残ります。 このようなクラスは、ユニットテストで記述、保守、およびカバーするのも簡単です。
» GitHubで利用可能なデモプロジェクト
すべての短剣と幸せなコーディング! :)