みなさんこんにちは! 最近、Android用のコードの記述を大幅に促進する多くのツールとライブラリが登場しました。 すべてを追跡し、すべてを試してください。 そのようなツールの1つがDagger 2ライブラリです。
ネットワークはすでにこのライブラリの多くの異なる資料です。 しかし、私がDagger 2に精通し始めたとき、記事を読んでレポートを見ると、これらすべてに共通する欠点が1つ見つかりました。それらがどのように「提供される」か、そしてそこで何が起こっているのか。 リスナー/リーダーの場合、通常、新しい注釈を含む大量のコードがすぐに抜け落ちます。 そして、どういうわけか機能しました。 その結果、私の頭の中のレポート/記事の後、すべてを理解できる単一の写真にまとめることができませんでした。
今、振り返ってみると、その時点で、「何、どこ、どこで」を明確に示す写真が表示されていなかったことがわかります。 したがって、私の一連の記事では、このギャップを埋めようとします。 これが初心者やDagger 2をよりよく理解したいと考えているすべての人に役立ち、自分のプロジェクトで試してみることにしたいです。 私はすぐにそれが価値があると言うことができます。
ええ、最初は1つの記事を書きたかったのですが、どういうわけか多くの資料と写真があったので、読者が徐々にトピックに飛び込めるように、情報を少しずつ広げていきます。
理論
理論的な側面を簡単に見ていきましょう。
Dagger 2は、開発者がDependency Injectionパターンを実装するのを支援するライブラリーであり、これは「制御の反転の特定の形式」です。
経営の反転の原則
- 上位モジュールは下位モジュールに依存しないでください。 両方のレベルのモジュールは抽象化に依存する必要があります。
- 抽象化は詳細に依存すべきではありません。 詳細は抽象化に依存する必要があります。
制御の反転を使用することにより除去される設計上の欠陥
- 剛性。 1つのモジュールを変更すると、他のモジュールも変更されます。
- 脆弱性。 1つの部分を変更すると、プログラムの他の部分で制御不能なエラーが発生します。
- 不動。 モジュールは、再利用のためにアプリケーションの他の部分から分離するのが困難です。
依存性注入(DI)
ソフトウェアコンポーネントに外部依存関係を提供するプロセス。 これは、依存関係管理に適用された場合の制御の反転(IoC)の特定の形式です。 単一の義務の原則に完全に従って、オブジェクトは、この一般的なメカニズムのために特別に設計された外部に必要な依存関係を構築することに注意を払います。
そのため、Dagger 2はこの共通のメカニズムを作成するだけです。
IoC、DI、およびそれらの相互関係についての質問とホリバーを予想し、定義はウィキペディアから取られたものであり、詳細な議論は記事の範囲外であると付け加えます。
次に、ライブラリの主な利点をリストします。
Dagger 2の利点
- 「共有」実装への簡単なアクセス。
- 複雑な依存関係の簡単なセットアップ。 アプリケーションがあればあるほど、依存関係が増えます。 Dagger 2では、すべての依存関係を簡単に制御できます。
- 単体テストと統合テストを促進します。 この問題については、Dagger 2を使用したテストに関する記事で説明します。
- 「ローカル」シングルトーン。
- コード生成。 結果のコードは理解可能であり、デバッグに使用できます。
- 難読化に問題はありません。 5番目と6番目の段落は両方とも、ダガーの2番目のバージョンと最初のバージョンの際立った特徴です。 ダガー1は反射に取り組んだ。 したがって、パフォーマンス、難読化、不可解な問題は実行時に低下します。
- 小さいライブラリサイズ
「共有」実装への簡単なアクセスの例として、コードを示します。
public class MainActivity extends AppCompatActivity { @Inject RxUtilsAbs rxUtilsAbs; @Inject NetworkUtils networkUtils; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); App.getComponent().inject(this); } }
つまり、 App.getComponent().inject(this);
アノテーションがフィールドに追加され、 App.getComponent().inject(this);
行がonCreate
メソッドに追加されApp.getComponent().inject(this);
。 そして今、 RxUtilsAbs
とNetworkUtils
既製の実装がRxUtilsAbs
NetworkUtils
利用できます。
これらすべての利点により、Dagger 2は現時点でAndroidにDIを実装するための最適なライブラリになっています。
もちろん、ライブラリには欠点もあります。 ただし、一連の記事の最後でそれらについて説明します。 さて、私の仕事はあなたに興味を持たせ、Dagger 2を試してみることです。
Dagger 2の重要な要素(注釈):
-
@Inject
「依存関係が要求される」基本的な注釈 -
@Module
メソッドが「依存関係を提供する」クラス -
@Provide
内の@Module
、「依存関係を構築して提供する方法をDaggerに伝える」 -
@Component
と@Module
間のブリッジ -
@Scope
グローバルおよび「ローカルシングルトン」を作成する機能を提供します -
@Qualifier
同じタイプの異なるオブジェクトが必要な場合
現時点では、一般的な参照のためにこれらの注釈を確認してください。 それぞれについて詳しく説明します。
実際、理論的には、これに限定しています。 詳細については、記事の最後にあるリンクをご覧ください。
主な目標は、Dagger 2を使用して依存関係グラフ全体がどのように構築されるかを理解することです。
練習する
さらに興味深いことが始まります。
特定の例を考えてみましょう。 誰もがアプリにシングルトーンを持っています。 Androidでは、アクティビティとフラグメントのライフサイクルを考えると、それらがなければどこにもありません。
同時に、利用可能なシングルトーンを2つのカテゴリに分割します。
- アプリケーションの任意の場所で必要になる可能性のある「グローバルな」シングルトーン。 これらには、コンテキスト、ユーティリティクラス、およびアプリケーション全体の動作に影響する他のクラスが含まれます。
- 特定の1つ以上のモジュールでのみ必要な「ローカル」シングルトーン。 しかし、画面やその他の方向が変更される可能性があるため、多くの場合、ライフサイクルに依存しない場所にロジックとデータの一部を移動する必要があります。 「ローカル」シングルトンについては、次の記事でより詳細かつ概略的に説明します。
「グローバルな」シングルトーンから始めましょう。 通常はどのように使用しますか? ほとんどの場合、次のコードが実行されると思います。
SomeSingleton.getInstance().method();
一般的な慣行。 しかし、DIパターンを適用したい場合、このコードはいくつかの理由で十分ではありません。
- このような呼び出しを使用するクラスでは、
SomeSingleton
クラスへの依存関係が突然発生します。 これは暗黙的な依存関係であり、どこにも明確に示されていません(コンストラクター、フィールド、メソッドのいずれにもありません)。 したがって、特定のメソッドのコードを見るだけでこのような依存関係を確認できます。また、このSomeSingleton
がここで使用されていることをクラスインターフェイスで知ることはできません。 - 初期化プロセスは
SomeSingleton
によって処理されSomeSingleton
。 遅延初期化が使用される場合、SomeSingleton
(最初に呼び出される場所)を使用してクラスの1つを初期化するプロセスが開始されます。 つまり、クラスは、作業に加えて、シングルトンの初期化を開始する役割も果たします。 - このようなシングルトーンの数が増えると、システムは暗黙的な依存関係のネットワークで覆われます。 いくつかのシングルトーンは他のシングルトーンに依存する場合がありますが、これはそれらのさらなるメンテナンスを単純化しません。 さらに、シングルトーンはシステム全体に散らばっており、異なるパッケージに含まれている可能性があり、これにより不便が生じます。
もちろん、これらすべてで、あなたは生きることができます。 簡単ではありませんが、可能です。 しかし、コードをユニットテストでオーバーレイする場合、すべてが根本的に変化し始めます。 ここでは、これらの暗黙的な依存関係を使用して何かを行う必要があり、何らかの形でそれらを正しい「置換」にします。 コードを「テストコード」に自由に変換し始めますが、暗黙的な依存関係があるため、非現実的です。
そして今、Dagger 2について(記事の途中で、私はロシア語で単に「Dagger」と呼ぶことがあります)。 次に、Dagger 2を使用して、DIにシングルトーンを実装する方法を説明します。 同時に、依存関係グラフを作成するサイクル全体が表示されます。
「グローバルな」シングルトーンから始めましょう。
シングルトーンを作成する
思い出すと、 @Module
は、メソッドが「依存関係を提供」するクラス(「依存関係を提供する」)をマークする注釈です。 将来、このようなクラスを単にモジュールと呼びます。 また、「依存関係を提供する」または「依存関係を提供する」メソッドは、提供メソッドと呼ばれます。
たとえば、 ReceiversModule
には、タイプNetworkChannel
オブジェクトを提供するだけのprovideNetworkChannel
メソッドがあります。 このメソッドは実際には何でも呼び出すことができます。最も重要なことは、メソッドと戻り値のタイプ( NetworkChannel
)の前の@Provides
アノテーションです。
戻り値の型がインターフェイスまたは抽象クラス( RxUtilsAbs
)であり、メソッド内で目的の実装( RxUtils
)を既に初期化して返す場合、一般的な方法です。
以下の@Singleton
アノテーションについて、注意を払うまで。
また、モジュールでは、コンストラクターで、必要なオブジェクトを渡すことができます。 例はAppModule
です。
そして、 UtilsModule
すでに興味深いものです。 依存関係RxUtilsAbs
およびNetworkUtils
を提供するには、タイプContext
およびNetworkChannel
オブジェクトが必要です。 したがって、 NetworkUtils
NetworkChannel
とNetworkUtils
NetworkChannel
作成するときに、 RxUtilsAbs
とNetworkUtils
必要であることを何らかの方法でDaggerに伝える必要がありNetworkChannel
。 これを行うには、 provideNetworkUtils
メソッドとprovideNetworkUtils
メソッドに引数が追加されます。最初のContext context, NetworkChannel networkChannel
2番目のContext context, NetworkChannel networkChannel
です。
この場合、引数の名前はany、少なくともcontext
、少なくともcontextSuper
、違いはありません。 主なものは引数のタイプです。
次に、注釈付きのAppComponent
インターフェイスを作成します
@Component(modules = {AppModule.class, UtilsModule.class, ReceiversModule.class})
。
便宜上、このようなインターフェイスコンポーネントを呼び出します。
上記のように、 @Component
は本質的に@Module
と@Inject
間のブリッジです。 言い換えれば、コンポーネントは既成の依存関係グラフです。 これはどういう意味ですか? もう少し理解してください。
このアノテーションを使用して、 AppModule, UtilsModule, ReceiversModule
3つのモジュールがAppComponent
含まれていることをDaggerに伝えます。 これらの各モジュールが提供する依存関係は、 AppComponent
コンポーネントの後援の下で結合された他のすべてのモジュールで利用できます。 明確にするために、図を見てください。
この図の助けを借りて、DaggerがContext
およびNetworkChannel
をRxUtilsAbs
およびNetworkUtils
を構築するRxUtilsAbs
がより明確になるとNetworkChannel
。 たとえば、コンパイル時にコンポーネントの注釈からAppModule
モジュールを削除すると、DaggerはContext
オブジェクトを取得できる場所を誓って尋ねます。
また、インターフェイス内でvoid inject(MainActivity mainActivity)
メソッドvoid inject(MainActivity mainActivity)
を宣言しvoid inject(MainActivity mainActivity)
。 このメソッドを使用して、注入するクラスをクラスに指定します。
MainActivity
以外の別のクラス(たとえばSecondActivity
)に依存関係を注入する必要がある場合は、インターフェイスで明確に指定する必要があることを追加します。 例えば
@Component(modules = {AppModule.class, UtilsModule.class, ReceiversModule.class}) @Singleton public interface AppComponent { void inject(MainActivity mainActivity); void inject(SecondActivity secondActivity); }
引数の名前は何でもmainActivity
ん( mainActivity
をactivity
に変更するなど)。 注入するオブジェクトの最も重要なタイプ! そして、「依存関係を投げる」すべてのクラスに対して型の一般化を使用することは不可能です。
@Component(modules = {AppModule.class, UtilsModule.class, ReceiversModule.class}) @Singleton public interface AppComponent { void inject(Object object); }
Dagger 2はリフレクションではなくコード生成で機能するためです! タイプは常に明確に指定する必要があります!
さらに進んでいます。 AppComponent
では、 MainActivity
クラスをインジェクションMainActivity
ます。 このクラスでは、 AppModule, UtilsModule, ReceiversModule
モジュールによって提供される依存関係を使用できます。 これを行うには、適切なフィールドをクラスに追加し、 @Inject
アノテーションでマークし、少なくともバッチで使用可能にします(フィールドがprivate
に設定されている場合、Daggerはこのフィールドの目的の実装を置き換えることができません)。
また、 RxUtilsAbs rxUtilsAbs
RxUtils
置き換えられていることに注意してください( RxUtils
はRxUtils
の後継RxUtilsAbs
)。つまり、 UtilsModule
モジュールで設定したUtilsModule
です。
次に、 onCreate
メソッドで行を追加します
App.getComponent().inject(this);
AppComponent
作成を検討しているため、コンポーネントAppComponent
をApplication
クラスに保存することをおAppComponent
しApplication
。 この例では、 App.getComponent()
介してApp.getComponent()
アクセスできます。
inject(MainActivity mainActivity)
メソッドinject(MainActivity mainActivity)
呼び出すことにより、最終的に依存関係グラフをバインドします。 したがって、AppComponentモジュール( Context
、 NetworkChannel
、 RxUtilsAbs
、 NetworkUtils
)を提供するすべての依存関係がRxUtilsAbs
で利用可能になります。
App
クラスのbuildComponent()
メソッドに注意してbuildComponent()
。 DaggerAppComponent
は、コンパイル前には使用できません。
そのため、最初はIDEに注意を払いません。IDEには、 DaggerAppComponent
クラスDaggerAppComponent
存在DaggerAppComponent
ないと表示されます。 さて、IDEでさえ、ビルダーのビルド時にプロンプトを表示しません。 そのため、 AppComponent
したAppComponent
の初期化は、最初に「ブラインドで」記述する必要があります。
ところで、 buildComponent()
コードは短縮できます:
protected AppComponent buildComponent() { return DaggerAppComponent.builder() .appModule(new AppModule(this)) .build(); }
先ほど述べたように、Dagger 2は依存関係グラフ全体の作成を担当します。 問題が発生した場合、コンパイル時に通知されます。 たとえば、Dagger 1の場合のように、実行時に予期せぬ不可解な落下はありません。
次の図を見てください!
ふふ、息を吐くことができます! 最も飽和した部分が後ろにあります。 この図は次のことを明確に示しているようです。
- モジュールは依存関係を提供します。 つまり、提供するオブジェクトを規定するのはモジュールです。
- コンポーネントは依存関係グラフです。 モジュールを組み合わせ、必要なクラスに依存関係を提供します(
MainActivity
)
何かが明確でないか、明示的でない場合は、コメントを書き、修正して説明してください!
最後に、 @Singleton
アノテーションを検討します。 これは、Daggerが提供するスコープアノテーションです。 依存関係を提供するメソッドの前に@Singleton
を置くと、Daggerは、コンポーネントを初期化するときに、マークされた依存関係の単一のインスタンス、つまりシングルトンを作成します。 そして、この依存関係の各リクエストで、この単一のインスタンスが提供します。
言葉を減らし、写真をもっと!
各依存関係は@Singleton
アノテーションで提供されます。 これは、Daggerがこの依存関係を使用する必要があるたびに、 そのインスタンスを1つだけ使用することを意味します 。
比較のために、 provideNetworkChannel
メソッドからprovideNetworkChannel
アノテーションを削除しprovideNetworkChannel
(依存関係は「 provideNetworkChannel
なし」になります)。 これは、Daggerがこの依存関係を使用する必要がある場合、その新しいインスタンスを毎回作成することを意味します 。
また、カスタムScopeアノテーションを作成することもできます(次の記事で詳しく説明します)。
Scopeアノテーションのいくつかの機能は次のとおりです。
- 通常、スコープアノテーションはコンポーネントに設定され、メソッドを提供します。
- 少なくとも1つの提供メソッドにスコープ注釈がある場合、コンポーネントはまったく同じスコープ注釈を持つ必要があります。
- コンポーネントは、そのすべてのモジュールですべての提供メソッドも「対象範囲外」である場合にのみ、「対象範囲外」にすることができます。
- 同じコンポーネント内のすべてのスコープ注釈(つまり、コンポーネントの一部である提供メソッドを持つすべてのモジュールとコンポーネント自体) は同じでなければなりません 。
Scope注釈付きのトピックについては、次の記事で詳しく説明します。 そして、初心者にとっては、これで十分です:)
そのため、この記事では、IoC、DI、Dagger 2の理論的側面に精通しました。スコープアノテーションとその特定の実装@Singleton
に部分的に精通したDagger 2を使用して、依存関係グラフの作成を詳細に検討し@Singleton
。
読むことをお勧めする記事のリストを提供します。
カスタムスコープ、コンポーネントの依存関係、およびサブコンポーネントに関する2番目の記事はすでに待っています!
コメント、レビュー、質問を待っています!