web-dev-qa-db-ja.com

Dagger 2のコンポーネント(オブジェクトグラフ)のライフサイクルを決定するものは何ですか?

Dagger 2のスコープ、特にスコープ付きグラフのライフサイクルに頭を包み込もうとしています。スコープを離れるときにクリーンアップされるコンポーネントをどのように作成しますか。

Androidアプリケーションの場合、Dagger 1.xを使用すると、通常、アクティビティレベルで子スコープを作成するために拡張するアプリケーションレベルのルートスコープがあります。

public class MyActivity {

    private ObjectGraph mGraph;

    public void onCreate() {
        mGraph = ((MyApp) getApplicationContext())
            .getObjectGraph()
            .plus(new ActivityModule())
            .inject(this);
    }

    public void onDestroy() {
        mGraph = null;
    }
}

子スコープは、それへの参照を保持している限り存在しました。この場合、これはアクティビティのライフサイクルです。 onDestroyで参照を削除することにより、スコープ付きグラフがガベージコレクションされるようになりました。

[〜#〜] edit [〜#〜]

Jesse Wilsonは最近 mea culpa を投稿しました

Dagger 1.0はスコープ名をひどく台無しにしました... @Singletonアノテーションはルートグラフとカスタムグラフの両方に使用されるため、物の実際のスコープが何であるかを把握するのは困難です。

そして、私が読んだ/聞いた他のすべては、ダガー2がスコープの動作を改善することを指摘していますが、その違いを理解するのに苦労しています。以下の@Kirill Boyarshinovのコメントによると、コンポーネントまたは依存関係のライフサイクルは、通常どおり、具体的な参照によって決定されます。それでは、Dagger 1.xスコープと2.0スコープの違いは、純粋にセマンティックの明確さの問題ですか?

私の理解

ダガー1.x

依存関係は@Singleton か否か。これは、ルートグラフとサブグラフの依存関係にも同様に当てはまり、依存関係がどのグラフにバインドされているかについてあいまいになります( (アクティビティのサブグラフが構築されますか?

ダガー2.0

カスタムスコープを使用すると、意味的に明確なスコープを作成できますが、機能的には@Singleton Dagger 1.xで。

// Application level
@Singleton
@Component( modules = MyAppModule.class )
public interface MyAppComponent {
    void inject(Application app);
}

@Module
public class MyAppModule {

    @Singleton @Named("SingletonScope") @Provides
    StringBuilder provideStringBuilderSingletonScope() {
        return new StringBuilder("App");
    }
}

// Our custom scope
@Scope public @interface PerActivity {}

// Activity level
@PerActivty
@Component(
    dependencies = MyAppComponent.class,
    modules = MyActivityModule.class
)
public interface MyActivityComponent {
    void inject(Activity activity);
}

@Module
public class MyActivityModule {

    @PerActivity @Named("ActivityScope") @Provides
    StringBuilder provideStringBuilderActivityScope() {
        return new StringBuilder("Activity");
    }

    @Name("Unscoped") @Provides
    StringBuilder provideStringBuilderUnscoped() {
        return new StringBuilder("Unscoped");
    }
}

// Finally, a sample Activity which gets injected
public class MyActivity {

    private MyActivityComponent component;

    @Inject @Named("AppScope")
    StringBuilder appScope

    @Inject @Named("ActivityScope")
    StringBuilder activityScope1

    @Inject @Named("ActivityScope")
    StringBuilder activityScope2

    @Inject @Named("Unscoped")
    StringBuilder unscoped1

    @Inject @Named("Unscoped")
    StringBuilder unscoped2

    public void onCreate() {
        component = Dagger_MyActivityComponent.builder()
            .myApplicationComponent(App.getComponent())
            .build()
            .inject(this);

        appScope.append(" > Activity")
        appScope.build() // output matches "App (> Activity)+" 

        activityScope1.append("123")
        activityScope1.build() // output: "Activity123"

        activityScope2.append("456")
        activityScope1.build() // output: "Activity123456"

        unscoped1.append("123")
        unscoped1.build() // output: "Unscoped123"

        unscoped2.append("456")
        unscoped2.build() // output: "Unscoped456"

    }

    public void onDestroy() {
        component = null;
    }

}

重要なことは、@PerActivityは、このコンポーネントのライフサイクルに関する意図を伝えますが、最終的にはいつでもどこでもコンポーネントを使用できます。 Daggerの唯一の約束は、特定のコンポーネントについて、スコープ注釈付きメソッドが単一のインスタンスを返すことです。また、Dagger 2はコンポーネントのスコープアノテーションを使用して、モジュールが同じスコープ内またはスコープ外の依存関係のみを提供することを確認すると仮定します。

要約すれば

依存関係はまだシングルトンまたは非シングルトンですが、@Singletonは現在、アプリケーションレベルのシングルトンインスタンスを対象としており、カスタムスコープは、シングルトンの依存関係に短いライフサイクルで注釈を付けるための推奨される方法です。

開発者は、不要になった参照を削除することでコンポーネント/依存関係のライフサイクルを管理し、コンポーネントが意図されたスコープ内で一度だけ作成されるようにする責任がありますが、カスタムスコープアノテーションにより、そのスコープを簡単に識別できます。

$ 64kの質問*

Dagger 2のスコープとライフサイクルの理解は正しいですか?

*実際には$ 64'000の質問ではありません。

125
Enrico

ご質問は

Dagger 2のコンポーネント(オブジェクトグラフ)のライフサイクルを決定するものは何ですか?

短い答えはあなたが決めるです。コンポーネントには、次のようなスコープを与えることができます

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ApplicationScope {
}

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ActivityScope {
}

これらは、次の2つの点で役立ちます。

  • スコープの検証:コンポーネントには、スコープ外のプロバイダー、またはコンポーネントと同じスコープのスコープ付きプロバイダーのみを含めることができます。

@Component(modules={ApplicationModule.class})
@ApplicationScope
public interface ApplicationComponent {
    Something something();
    AnotherThing anotherThing();

    void inject(Whatever whatever);
}

@Module
public class ApplicationModule {
    @ApplicationScope //application-scoped provider, only one can exist per component
    @Provides
    public Something something() {
         return new Something();
    }

    @Provides //unscoped, each INJECT call creates a new instance
    public AnotherThing anotherThing() {
        return new AnotherThing();
    }
}
  • スコープされた依存関係をサブスコープできるため、「スーパースコープ」コンポーネントから提供されたインスタンスを使用する「サブスコープ」コンポーネントを作成できます。

これは、@Subcomponentアノテーション、またはコンポーネントの依存関係。私は個人的に依存関係を好みます。

@Component(modules={ApplicationModule.class})
@ApplicationScope
public interface ApplicationComponent {
    Something something();
    AnotherThing anotherThing();

    void inject(Whatever whatever);

    ActivityComponent newActivityComponent(ActivityModule activityModule); //subcomponent factory method
}

@Subcomponent(modules={ActivityModule.class})
@ActivityScope
public interface ActivityComponent {
    ThirdThingy thirdThingy();

    void inject(SomeActivity someActivity);
}

@Module
public class ActivityModule {
    private Activity activity;

    public ActivityModule(Activity activity) {
        this.activity = activity;
    }

    //...
}

ApplicationComponent applicationComponent = DaggerApplicationComponent.create();
ActivityComponent activityComponent = applicationComponent.newActivityComponent(new ActivityModule(SomeActivity.this));

または、次のようなコンポーネントの依存関係を使用できます

@Component(modules={ApplicationModule.class})
@ApplicationScope
public class ApplicationComponent {
    Something something(); 
    AnotherThing anotherThing();

    void inject(Whatever whatever);
}

@Component(dependencies={ApplicationComponent.class}, modules={ActivityModule.class})
@ActivityScope
public interface ActivityComponent extends ApplicationComponent {
    ThirdThingy thirdThingy();

    void inject(SomeActivity someActivity);
}

@Module
public class ActivityModule {
    private Activity activity;

    public ActivityModule(Activity activity) {
        this.activity = activity;
    }

    //...
}

ApplicationComponent applicationComponent = DaggerApplicationComponent.create();
ActivityComponent activityComponent = DaggerActivityComponent.builder().activityModule(new ActivityModule(SomeActivity.this)).build();

知っておくべき重要なこと:

  • スコーププロバイダーは、その特定のスコープに対して1つのインスタンスを作成します各コンポーネント。コンポーネントの意味は、それ自体のインスタンスを追跡しますが、他のコンポーネントには共有スコーププールや魔法がありません。特定のスコープ内に1つのインスタンスを保持するには、コンポーネントの1つのインスタンスが必要です。これが、独自のスコープ依存関係にアクセスするためにApplicationComponentを提供する必要がある理由です。

  • コンポーネントは、1つのスコープコンポーネントのみをサブスコープできます。複数のスコープコンポーネントの依存関係は許可されません。

64
EpicPandaForce