ダガー2。パート3。 可能性の新しい側面

みなさんこんにちは! 最後に、Dagger 2に関する一連の記事の第3部が届きました!







さらに読む前に、 最初2番目の部分をよく理解することを強くお勧めします。







フィードバックとコメントをありがとう。 私の記事が、開発者がDaggerの世界に飛び込むのに本当に役立つことを非常に嬉しく思います。 これはあなたのためにさらに作成する力を与えるものです。

第3部では、ライブラリのさまざまな興味深い重要な機能を検討します。これは非常に便利です。







一般的に、このライブラリはすでにかなりの時間でしたが、ドキュメントは依然として非常に不快です。 ダガーと知り合いになったばかりの開発者には、このタフで不公平な世界にがっかりしないように、最初は公式文書を調べないことをお勧めします。







もちろん、多かれ少なかれ描かれている瞬間があります。 しかし、ここでは、すべての種類の新機能を、試行錯誤して生成されたコードを取得し、すべてがどのように機能するかを理解する必要があるような方法で説明します。 幸いなことに、善良な人々は良い記事を書いていますが、時には明確で正確な答えをすぐに出せないことさえあります。







だから、暴言するのに十分であり、新しい知識に進む!







修飾子の注釈



コメントの前の記事で、彼らはこの問題をカバーするように頼みました。 長い箱には入れません。







同じタイプの複数のオブジェクトを提供する必要があることがよくあります。 たとえば、システムには2つのExecutor



が必要です。1つはシングルスレッド、もう1つはCachedThreadPool



ます。 この場合、「修飾子の注釈」が役立ちます。 これは、 @Qualifier



アノテーションが含まれるカスタムアノテーションです。 バターのように聞こえますが、この例ではすべてがはるかに単純です。







一般に、Dagger2は既製の「修飾子アノテーション」を1つ提供します。これは、おそらく日常生活で十分です。







 @Qualifier @Documented @Retention(RUNTIME) public @interface Named { /** The name. */ String value() default ""; }
      
      





それでは、戦闘でどのように見えるか見てみましょう。







修飾子の注釈の例
 @Module public class AppModule { @Provides @Singleton @Named("SingleThread") public Executor provideSingleThreadExecutor() { return Executors.newSingleThreadExecutor(); } @Provides @Singleton @Named("MultiThread") public Executor provideMultiThreadExecutor() { return Executors.newCachedThreadPool(); } } public class MainActivity extends AppCompatActivity { @Inject @Named("SingleThread") Executor singleExecutor; @Inject @Named("MultiThread") Executor multiExecutor; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); MyApplication.getInstance().getAppComponent().inject(this); setContentView(R.layout.activity_main); } }
      
      





その結果、同じクラス( Executor



)の2つの異なるインスタンス( singleExecutor



multiExecutor



)があります。 それが私たちに必要なものです! @Named



アノテーションを持つ同じクラスのオブジェクトは、完全に異なる独立したコンポーネントからも、相互に依存してからも@Named



できることに注意してください。







遅延初期化



開発に関する一般的な問題の1つは、アプリケーションの長時間起動です。 通常、理由は1つです-読み込みが多すぎて、起動時に初期化されます。 さらに、Dagger2はメインスレッドで依存関係グラフを作成します。 また、多くの場合、ダガーによって構築されたすべてのオブジェクトがすぐに必要になるわけではありません。 したがって、ライブラリは、 Provider<>



およびLazy<>



インターフェイスを使用した最初の呼び出しまで、オブジェクトの初期化を遅らせる機会をProvider<>



ます。







すぐに例に目を向けてください。







遅延初期化の例
 @Module public class AppModule { @Provides @Named("SingleThread") public Executor provideSingleThreadExecutor() { return Executors.newSingleThreadExecutor(); } @Provides @Named("MultiThread") public Executor provideMultiThreadExecutor() { return Executors.newCachedThreadPool(); } } public class MainActivity extends AppCompatActivity { @Inject @Named("SingleThread") Provider<Executor> singleExecutorProvider; @Inject @Named("MultiThread") Lazy<Executor> multiExecutorLazy; @Inject @Named("MultiThread") Lazy<Executor> multiExecutorLazyCopy; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); MyApplication.getInstance().getAppComponent().inject(this); setContentView(R.layout.activity_main); // Executor singleExecutor = singleExecutorProvider.get(); Executor singleExecutor2 = singleExecutorProvider.get(); // Executor multiExecutor = multiExecutorLazy.get(); Executor multiExecutor2 = multiExecutorLazy.get(); Executor multiExecutor3 = multiExecutorLazyCopy.get(); } }
      
      





Provider<Executor> singleExecutorProvider



から始めましょう。 singleExecutorProvider.get()



最初の呼び出しまで、Daggerは対応するExecutor



初期化しません。 しかし、 その後 singleExecutorProvider.get()



呼び出すたびに、新しいインスタンスが作成されます。 したがって、 singleExecutor



singleExecutor2



は2つの異なるオブジェクトです。 この動作は、 スコープ外のオブジェクトの動作と本質的に同じです。







Provider



はどのような状況で適切ですか? 時間の経過とともに状態が変化する可変中毒を提供するときに役立ちます。また、アピールするたびに現在の状態を取得する必要があります。 「どのような曲線アーキテクチャですか?」 -あなたが言う、と私はあなたに同意します。 ただし、レガシーコードを使用する場合は、それも表示されません。







ライブラリの作成者は、通常のunscopeで十分な場所でProvider



インターフェイスを悪用することもお勧めしないことに注意してください。これは、前述の「曲がったアーキテクチャ」とキャッチしにくいバグが多いためです。







Lazy<Executor> multiExecutorLazy



およびLazy<Executor> multiExecutorLazyCopy



。 Dagger2 multiExecutorLazy.get()



およびmultiExecutorLazyCopy.get()



最初に multiExecutorLazy.get()



ときにのみ、対応するExecutor



初期化します。 次に、Daggerは Lazy<>



の初期化された値をキャッシュし、 multiExecutorLazy.get()



およびmultiExecutorLazyCopy.get()



2回目にmultiExecutorLazy.get()



multiExecutorLazyCopy.get()



キャッシュされたオブジェクトをmultiExecutorLazyCopy.get()



ます。







したがって、 multiExecutor



multiExecutor2



は同じオブジェクトを参照し、 multiExecutor3



は2番目のオブジェクトを参照します。







ただし、 provideMultiThreadExecutor()



メソッドに@Singleton



注釈を追加すると、オブジェクトは依存関係ツリー全体に対してキャッシュされ、 multiExecutor



multiExecutor2



multiExecutor3



1つのオブジェクトを参照します。







注意してください。







非同期ロード



私たちは非常に重要なタスクを思いつきました。 しかし、ディペンデンシーグラフの構築をバックグラウンドで実行したい場合はどうでしょうか? それは有望ですか? はい、はい、私はプロデューサーについて話しています。







正直なところ、このトピックは別の考慮に値します。 多くの機能とニュアンスがあります。 その上に十分な材料があります。 ここで、 プロデューサーの長所と短所のみに触れます。







長所 さて、最も重要なプラスは、バックグラウンドでの読み込みと、この読み込みプロセスを制御する機能です。







短所 プロデューサーはグアバと一緒に「ドラッグ」します。これには、apkに15,000のメソッドがプラスされます。 しかし、最悪の部分は、 プロデューサーを使用するとアーキテクチャ全体が少し損なわれ、コードがより混乱することです。 すでにDaggerがあり、オブジェクトの初期化をバックグラウンドに転送することにした場合は、一生懸命試す必要があります。







公式ドキュメントでは、このトピックは特別なセクションで強調されています。 しかし、 Miroslaw Stanekの記事を強くお勧めます。 彼は一般的に非常に優れたブログを持ち、Dagger2に関する多くの記事があります。 実際、私は彼からの過去の記事から写真のレイアウトをいくつか借りました。

彼はこの記事で プロデューサーについて書いています







しかし、 次の例では、バックグラウンドで依存関係ツリーをロードするための非常に興味深い代替手段を提供します。 ネイティブのRxJavaが助けになります。 Producersを使用することの欠点がまったくないため、彼のソリューションはとても気に入っていますが、同時に非同期ロードの問題を解決します。







マイナス1つのみ:MiroslavはObservable.create(...)



正しく適用しません。 しかし、私はこのことについての解説でこれについて書いたので、注意を払ってください。







そしてオブジェクトのスコープのコードがどのように見えるかを見てみましょう(「正しい」RxJavaを使用):







スコープの例
 @Module public class AppModule { @Provides @Singleton // or custom scope for "local" singletons HeavyExternalLibrary provideHeavyExternalLibrary() { HeavyExternalLibrary heavyExternalLibrary = new HeavyExternalLibrary(); heavyExternalLibrary.init(); //This method takes about 500ms return heavyExternalLibrary; } @Provides @Singleton // or custom scope for "local" singletons Observable<HeavyExternalLibrary> provideHeavyExternalLibraryObservable( final Lazy<HeavyExternalLibrary> heavyExternalLibraryLazy) { return Observable.fromCallable(heavyExternalLibraryLazy::get) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()); } } public class MainActivity extends AppCompatActivity { @Inject Observable<HeavyExternalLibrary> heavyExternalLibraryObservable; //This will be injected asynchronously HeavyExternalLibrary heavyExternalLibrary; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); MyApplication.getInstance().getAppComponent().inject(this); setContentView(R.layout.activity_main); // init HeavyExternalLibrary in background thread! heavyExternalLibraryObservable.subscribe( heavyExternalLibrary1 -> heavyExternalLibrary = heavyExternalLibrary1, throwable -> {} ); } }
      
      





@Singleton



Lazy



インターフェースを確認してください。 Lazy



は、要求時に重いオブジェクトが初期化され、キャッシュされることを保証するだけです。







しかし、この「重い」オブジェクトの新しいインスタンスを毎回取得したい場合はどうすればよいでしょうか? その後、 AppModule



変更する価値がAppModule









スコープ解除の例
 @Module public class AppModule { @Provides // No scope! HeavyExternalLibrary provideHeavyExternalLibrary() { HeavyExternalLibrary heavyExternalLibrary = new HeavyExternalLibrary(); heavyExternalLibrary.init(); //This method takes about 500ms return heavyExternalLibrary; } @Provides @Singleton // or custom scope for "local" singletons Observable<HeavyExternalLibrary> provideHeavyExternalLibraryObservable( final Provider<HeavyExternalLibrary> heavyExternalLibraryLazy) { return Observable.fromCallable(heavyExternalLibraryLazy::get) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()); } } public class MainActivity extends AppCompatActivity { @Inject Observable<HeavyExternalLibrary> heavyExternalLibraryObservable; //This will be injected asynchronously HeavyExternalLibrary heavyExternalLibrary; HeavyExternalLibrary heavyExternalLibraryCopy; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); MyApplication.getInstance().getAppComponent().inject(this); setContentView(R.layout.activity_main); // init HeavyExternalLibrary and heavyExternalLibraryCopy in background thread! heavyExternalLibraryObservable.subscribe( heavyExternalLibrary1 -> heavyExternalLibrary = heavyExternalLibrary1, throwable -> {} ); heavyExternalLibraryObservable.subscribe( heavyExternalLibrary1 -> heavyExternalLibraryCopy = heavyExternalLibrary1, throwable -> {} ); } }
      
      





provideHeavyExternalLibrary()



メソッドの場合、 スコープを削除し、 provideHeavyExternalLibraryObservable(final Provider<HeavyExternalLibrary> heavyExternalLibraryLazy)



Lazy



代わりにProvider



provideHeavyExternalLibraryObservable(final Provider<HeavyExternalLibrary> heavyExternalLibraryLazy)



使用しProvider



。 したがって、 heavyExternalLibrary



heavyExternalLibraryCopy



は異なるオブジェクトです。







また、依存関係ツリーをバックグラウンドに初期化するプロセス全体を引き続き実行できます。 どうして? とても簡単です。 最初に、それがどうだったか見てみましょう:







Miroslavの記事のSplashActivity
 public class SplashActivity extends BaseActivity { @Inject SplashActivityPresenter presenter; @Inject AnalyticsManager analyticsManager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setupActivityComponent(); } @Override protected void setupActivityComponent() { final SplashActivityComponent splashActivityComponent = GithubClientApplication.get(SplashActivity.this) .getAppComponent() .plus(new SplashActivityModule(SplashActivity.this)); splashActivityComponent.inject(SplashActivity.this); } }
      
      





次に、更新されたvoid setupActivityComponent()



メソッド(RxJavaで編集したもの)を見てください。







void setupActivityComponent()
 @Override protected void setupActivityComponent() { Completable.fromAction(() -> { final SplashActivityComponent splashActivityComponent = GithubClientApplication.get(SplashActivity.this) .getAppComponent() .plus(new SplashActivityModule(SplashActivity.this)); splashActivityComponent.inject(SplashActivity.this); }) .doOnCompleted(() -> { //Here is the moment when injection is done. analyticsManager.logScreenView(getClass().getName()); presenter.callAnyMethod(); }) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(() -> {}, throwable -> {}); }
      
      





測定



前のセクションでは、アプリケーションの起動時のパフォーマンスについて説明しました。 しかし、パフォーマンスと速度に関しては、測定する必要があることを知っています! 直感と「一見速くなった」という感覚に頼ることは不可能です。 これにより、Miroslavはこの記事とこの記事で再び役立ちます。 彼なしで私たちが何をするか、私にはわかりません。







新しい興味深い機能



Daggerには、人生を楽にするための新しい興味深い機能があります。 しかし、すべてがどのように機能し、これが私たちに与えるものを理解することは簡単な作業ではありませんでした。 さあ、始めましょう!







@再利用可能なスコープ



興味深いアノテーション。 メモリを節約できますが、同時にscope



によって本質的に制限されないため、コンポーネントの依存関係を再利用するのに非常に便利です。 つまり、 scope



unscope



クロスunscope









非常に重要な点はドックに書かれており、最初は何とか目を引くことはありません。「 @Reusable



依存関係を使用する各コンポーネントについて、この依存関係は個別にキャッシュされ
ます。」 さらに、「 オブジェクトが作成時にキャッシュされ、そのインスタンスが子コンポーネントと依存コンポーネントによって使用されるscope



アノテーションとは異なります
。」







そして今すぐすべてを理解するための例:







説明付きの長い例

主なコンポーネント。







 @Component(modules = {AppModule.class, UtilsModule.class}) @Singleton public interface AppComponent { FirstComponent.Builder firstComponentBuilder(); SecondComponent.Builder secondComponentBuilder(); }
      
      





AppComponent



は2つのSubcomponent



ます。 この設計FirstComponent.Builder



気づきFirstComponent.Builder



か? 彼女についてはもう少し後です。

それでは、 UtilsModule



見てみましょう。







 @Module public class UtilsModule { @Provides @NonNull @Reusable public NumberUtils provideNumberUtils() { return new NumberUtils(); } @Provides @NonNull public StringUtils provideStringUtils() { return new StringUtils(); } }
      
      





NumberUtils



アノテーション付きのNumberUtils



NumberUtils



unscoped



ままにしunscoped





次に、2つのSubcomponents



ます。







 @FirstScope @Subcomponent(modules = FirstModule.class) public interface FirstComponent { @Subcomponent.Builder interface Builder { FirstComponent.Builder firstModule(FirstModule firstModule); FirstComponent build(); } void inject(MainActivity mainActivity); } @SecondScope @Subcomponent(modules = {SecondModule.class}) public interface SecondComponent { @Subcomponent.Builder interface Builder { SecondComponent.Builder secondModule(SecondModule secondModule); SecondComponent build(); } void inject(SecondActivity secondActivity); void inject(ThirdActivity thirdActivity); }
      
      





ご覧のとおり、 FirstComponent



のみ注入し、 FirstComponent



MainActivity



およびSecondComponent



します。

コードを見てみましょう。







 public class MainActivity extends AppCompatActivity { @Inject NumberUtils numberUtils; @Inject StringUtils stringUtils; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); MyApplication.getInstance().getFirstComponent() .inject(this); // other... } } public class SecondActivity extends AppCompatActivity { @Inject NumberUtils numberUtils; @Inject StringUtils stringUtils; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_second); MyApplication.getInstance().getSecondComponent() .inject(this); // other... } } public class ThirdActivity extends AppCompatActivity { @Inject NumberUtils numberUtils; @Inject StringUtils stringUtils; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_third); MyApplication.getInstance().getSecondComponent() .inject(this); // other... } }
      
      





ナビゲーションについて簡単に説明します。 MainActivity



からMainActivity



に到達し、次にThirdActivity



ます。 そして今、質問。 すでに3番目の画面にいる場合、いくつのNumberUtils



およびStringUtils



が作成されますか?







StringUtils



は対象unscoped



であるため、3つのインスタンスが作成されます。つまり、注入ごとに新しいオブジェクトが作成されます。 これはわかっています。







ただし、2つのNumberUtils



オブジェクトがあります。1つはFirstComponent



、もう1つはFirstComponent



SecondComponent



。 そしてここで、 @Reusable



についての主なアイデアを@Reusable



します。「 @Reusable



依存関係を使用する各コンポーネントについて、この依存関係は個別にキャッシュされます!
」 。







ただし、グーラー自身は、一意のオブジェクトが必要な場合、これも可変である可能性がある場合は、 scoped



アノテーションのみをscoped



するよう警告しています。







また、 @Singleton



@Reusable



をSOと比較することに関する質問へのリンクも提供します。







@ Subcomponent.Builder



コードをより美しくする機能。 以前は、 @Subcomponent



を作成するには、次のように記述する必要がありました。







そうだった
 @Component(modules = {AppModule.class, UtilsModule.class}) @Singleton public interface AppComponent { FirstComponent plusFirstComponent(FirstModule firstModule, SpecialModule specialModule); } @FirstScope @Subcomponent(modules = {FirstModule.class, SpecialModule.class}) public interface FirstComponent { void inject(MainActivity mainActivity); }
      
      





FirstComponent



作成:







 appComponent .plusFirstComponent(new FirstModule(), new SpecialModule());
      
      





このアプローチでは、子サブコンポーネントを使用するモジュールに関する不必要な知識が親コンポーネントにロードされることを好みませんでした。 さて、それに加えて、多数の引数の転送はあまり見栄えがよくありません。これにはBuilderパターンがあるからです。 今ではもっと美しいです:







どうでしたか
 @Component(modules = {AppModule.class, UtilsModule.class}) @Singleton public interface AppComponent { FirstComponent.Builder firstComponentBuilder(); } @FirstScope @Subcomponent(modules = {FirstModule.class, SpecialModule.class}) public interface FirstComponent { @Subcomponent.Builder interface Builder { FirstComponent.Builder firstModule(FirstModule firstModule); FirstComponent.Builder specialModule(SpecialModule specialModule); FirstComponent build(); } void inject(MainActivity mainActivity); }
      
      





FirstComponent



作成は次のようになります。







 appComponent .firstComponentBuilder() .firstModule(new FirstModule()) .specialModule(new SpecialModule()) .build();
      
      





もう一つ=)







静的



これで、次のようなことができます。







 @Provides static User currentUser(AuthManager authManager) { return authManager.currentUser(); }
      
      





つまり、モジュールの依存関係を静的に提供するメソッドを作成できます。 最初はあまり理解していませんでしたが、なぜこれが必要なのでしょうか? しかし、そのような機能に対する要求は長い間存在しており、それが有益である状況があることが判明しました。







彼らは、このトピックに関してSOで良い質問をしたと言いますが、 @Singleton



@Provide static



違い@Singleton



@Provide static



この違いをよく理解するには、生成されたコードを試して見ながら、質問に対する答えを読む必要があります。







だから、私たちは入門的なものを持っています。 モジュールには同じメソッドの3つのバリアントがあります。







 @Provides User currentUser(AuthManager authManager) { return authManager.currentUser(); } @Provides @Singleton User currentUser(AuthManager authManager) { return authManager.currentUser(); } @Provides static User currentUser(AuthManager authManager) { return authManager.currentUser(); }
      
      





同時に、 authManager.currentUser()



は、異なる時点で異なるインスタンスを提供できます。

論理的な質問は、これらの方法はどのように違うのですか?







最初のケースでは、古典的なunscope



ます。 各リクエストには、 authManager.currentUser()



新しいインスタンス(より正確には、 currentUser



への新しいリンクauthManager.currentUser()



が与えられます。







2番目の場合、最初のリクエストはcurrentUser



へのリンクをキャッシュし、新しいリクエストごとにこのリンクが返されます。 つまり、 AuthManager



currentUser



変更された場合、すでに無効なインスタンスへの古いリンクがAuthManager



ます。







3番目のケースはすでに興味深いものです。 このメソッドの動作はunscope



似ています。 unscope



、リクエストごとに新しいリンクが提供されます。 これは、オブジェクトをキャッシュする@Singleton



との最初の違いです。 したがって、 @Provide static



メソッドにオブジェクトの初期化を配置することは完全に適切ではありません。







しかし、 unscope



@Provide static



unscope



違いは何ですか? このようなモジュールがあるとしましょう:







 @Module public class AuthModule { @Provides User currentUser(AuthManager authManager) { return authManager.currentUser(); } }
      
      





AuthManager



は、 Singleton



として別のモジュールからAuthManager



れます。 ここで、生成されたコードAuthModule_CurrentUserFactory



に見てみましょう(スタジオではcurrentUser



カーソルを置いてCtrl + Bを押します)。







スコープ解除
 @Generated( value = "dagger.internal.codegen.ComponentProcessor", comments = "https://google.github.io/dagger" ) public final class AuthModule_CurrentUserFactory implements Factory<User> { private final AuthModule module; private final Provider<AuthManager> authManagerProvider; public AuthModule_CurrentUserFactory( AuthModule module, Provider<AuthManager> authManagerProvider) { assert module != null; this.module = module; assert authManagerProvider != null; this.authManagerProvider = authManagerProvider; } @Override public User get() { return Preconditions.checkNotNull( module.currentUser(authManagerProvider.get()), "Cannot return null from a non-@Nullable @Provides method"); } public static Factory<User> create(AuthModule module, Provider<AuthManager> authManagerProvider) { return new AuthModule_CurrentUserFactory(module, authManagerProvider); } /** Proxies {@link AuthModule#currentUser(AuthManager)}. */ public static User proxyCurrentUser(AuthModule instance, AuthManager authManager) { return instance.currentUser(authManager); } }
      
      





そして、 currentUser



にstaticを追加する場合:







 @Module public class AuthModule { @Provides static User currentUser(AuthManager authManager) { return authManager.currentUser(); } }
      
      





それから私達は得る:







静的
 @Generated( value = "dagger.internal.codegen.ComponentProcessor", comments = "https://google.github.io/dagger" ) public final class AuthModule_CurrentUserFactory implements Factory<User> { private final Provider<AuthManager> authManagerProvider; public AuthModule_CurrentUserFactory(Provider<AuthManager> authManagerProvider) { assert authManagerProvider != null; this.authManagerProvider = authManagerProvider; } @Override public User get() { return Preconditions.checkNotNull( AuthModule.currentUser(authManagerProvider.get()), "Cannot return null from a non-@Nullable @Provides method"); } public static Factory<User> create(Provider<AuthManager> authManagerProvider) { return new AuthModule_CurrentUserFactory(authManagerProvider); } /** Proxies {@link AuthModule#currentUser(AuthManager)}. */ public static User proxyCurrentUser(AuthManager authManager) { return AuthModule.currentUser(authManager); } }
      
      





static



バージョンにはAuthModule



がないことに注意してください。 したがって、静的メソッドは、モジュールをバイパスしてコンポーネントを直接ひきつけます 。 また、モジュール内に静的メソッドが1つしかない場合、モジュールインスタンスは作成されません。







余分な呼び出しを保存してマイナスします。 実際、パフォーマンスが向上しています。 また、静的メソッドの呼び出しは、同様の非静的メソッドの呼び出しよりも15〜20%高速であると書いています。 私が間違っている場合、 iamironzは修正します。 彼は確実に知っており、必要であれば、それを測定します。







@Binds + Inject constructor



定型コードを大幅に削減する非常に便利なバンドル。 ダガーの研究の夜明けには、コンストラクター注入が必要な理由がわかりませんでした。 何から、どこから来たのか。 そして、@ Bindsが登場しました。 しかし、実際にはすべてが非常に簡単です。 ウラジミール・タガコフとこの記事を手伝ってくれてありがとう。







典型的な状況を考慮してください。 Presenterインターフェースとその実装があります:







 public interface IFirstPresenter { void foo(); } public class FirstPresenter implements IFirstPresenter { public FirstPresenter() {} @Override public void foo() {} }
      
      





私たちは白人として、このすべてのビジネスをモジュールで提供し、プレゼンターのインターフェイスをアクティビティに挿入します。







 @Module public class FirstModule { @Provides @FirstScope public IFirstPresenter provideFirstPresenter() { return new FirstPresenter(); } } public class MainActivity extends AppCompatActivity { @Inject IFirstPresenter firstPresenter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); MyApplication.getInstance().getFirstComponent() .inject(this); // others } }
      
      





FirstPresenter



は、作業の一部を委任するFirstPresenter



クラスが必要だとしましょう。 これを行うには、新しいクラスを提供するモジュール内にさらに2つのメソッドを作成し、 FirstPresenter



のコンストラクターを変更してから、モジュール内の対応するメソッドを更新する必要があります。







モジュールは次のようになります。







 @Module public class FirstModule { @Provides @FirstScope public HelperClass1 provideHelperClass1() { return new HelperClass1(); } @Provides @FirstScope public HelperClass2 provideHelperClass2() { return new HelperClass2(); } @Provides @FirstScope public IFirstPresenter provideFirstPresenter( HelperClass1 helperClass1, HelperClass2 helperClass2) { return new FirstPresenter(helperClass1, helperClass2); } }
      
      





毎回、クラスを追加して他のクラスと「共有」する必要がある場合。 モジュールはすぐに汚れます。 どういうわけか、コードが多すぎますよね? しかし、コードを大幅に削減するソリューションがあります。







まず、インターフェイス( HelperClass1



およびHelperClass2



)ではなく、依存関係を作成して完成したクラスをレンダリングする必要がある場合、コンストラクターインジェクションに頼ることができます。 次のようになります。







 @FirstScope public class HelperClass1 { @Inject public HelperClass1() { } } @FirstScope public class HelperClass2{ @Inject public HelperClass2() { } }
      
      





, @FirstScope



, , .







HelperClass1



HelperClass2



:







 @Module public class FirstModule { @Provides @FirstScope public IFirstPresenter provideFirstPresenter( HelperClass1 helperClass1, HelperClass2 helperClass2) { return new FirstPresenter(helperClass1, helperClass2); } }
      
      





? @Binds



:







 @Module public abstract class FirstModule { @FirstScope @Binds public abstract IFirstPresenter provideFirstPresenter(FirstPresenter firstPresenter); }
      
      





FirstPresenter



:







 @FirstScope public class FirstPresenter implements IFirstPresenter { private HelperClass1 helperClass1; private HelperClass2 helperClass2; @Inject public FirstPresenter(HelperClass1 helperClass1, HelperClass2 helperClass2) { this.helperClass1 = helperClass1; this.helperClass2 = helperClass2; } @Override public void foo() {} }
      
      





? FirstModule



, provideFirstPresenter



. provideFirstPresenter



@Provide



, @Binds



. , !

FirstPresenter



scope



@FirstScope



, , . @Inject



. , !







Mujahit .

, FirstModule



FirstComponent



, AppComponent



. FirstComponent



:







 appComponent .firstComponentBuilder() .firstModule(new FirstModule()) .specialModule(new SpecialModule()) .build();
      
      





FirstModule



, ? , , , :







 appComponent .firstComponentBuilder() .build();
      
      





, .







, , :







 @Module public interface FirstModule { @FirstScope @Binds IFirstPresenter provideFirstPresenter(FirstPresenter firstPresenter); }
      
      





. "" :







 @Module public abstract class FirstModule { @FirstScope @Binds public abstract IFirstPresenter provideFirstPresenter(FirstPresenter firstPresenter); @FirstScope @Provides public HelperClass3 provideHelperClass3() { // <- Incorrect! return new HelperClass3(); } @FirstScope @Provides public static HelperClass3 provideHelperClass3() { // <- Ok! return new HelperClass3(); } }
      
      







:







  1. Muitibindings . "" ( Set



    Map



    ). ("plugin architecture"). . Muitibindings . . .







  2. Releasable references . . , . .

    ( Releasable references ) , .







  3. テスト , Unit- . , UI . Artem_zin . . - , . . ( ) . , , , Application



    . ?







  4. @BindsOptionalOf . Optional



    Java 8 Guava, . , .







  5. @BindsInstance . , dagger 2.8 . , - . , AppComponent



    Context



    . . .


! . - , ! 修正します。 Dagger2 , .







, . . , AndroidDevPodcast , . ニュースをフォローしてください!








All Articles