私はしばらくの間dagger2を使用しています。そして、アクティビティ/フラグメントごとに独自のコンポーネント/モジュールを作成するかどうか混乱しました。これを明確にするのを手伝ってください:
たとえば、アプリがあり、アプリには約50の画面があります。 MVPパターンとDI用のDagger2に従ってコードを実装します。 50のアクティビティと50のプレゼンターがいるとします。
私の意見では、通常、次のようにコードを整理する必要があります。
アプリが開いているときに使用されるすべてのオブジェクトを提供する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, ....)
}
ActivityScopeを作成します。
@Scope
@Documented
@Retention(value=RUNTIME)
public @interface ActivityScope {
}
各アクティビティのコンポーネントとモジュールを作成します。通常、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.
これらは、これをどのように実装するかを示す非常に単純な例です。
しかし、私の友人はちょうど私に別の実装を与えました:
すべてのプレゼンターを提供する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.
}
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つのインスタンスを作成する場合はどうなりますか? (どうすれば2人のプレゼンターを作成できますか)
アプリの初期化には多くの時間がかかります(多くのプレゼンター、オブジェクトを作成する必要があるため...)
長い投稿は申し訳ありませんが、私と友人のためにこれを明確にするのを手伝ってください、私は彼を納得させることができません。あなたのコメントは非常に高く評価されます。
/------------------------------------------ -----------------------------/
デモを行った後に編集します。
まず、@ pandawarriorの回答に感謝します。この質問をする前に、デモを作成する必要がありました。ここでの私の結論が他の誰かに役立つことを願っています。
それで、私が上で言ったすべての理由はほとんど間違っています。ただし、次の2つの理由により、友人の考えに従う必要があるという意味ではありません。
彼がモジュール/コンポーネントのすべてのプレゼンターを初期化するとき、それはソースのアーキテクチャにとって良くありません。 (これは インターフェイス分離の原則 に違反しているかもしれませんが、多分 単一の責任 原則にも違反しています)。
Scopeコンポーネントを作成すると、いつ作成され、いつ破棄されるかがわかります。これは、メモリリークを回避するための大きな利点です。したがって、アクティビティごとに、@ ActivityScopeを持つコンポーネントを作成する必要があります。私の友人の実装で、Providerメソッドにスコープを入れるのを忘れていたと想像してみてください=>メモリリークが発生します。
私の意見では、小さなアプリ(多くの依存関係のない、または同様の依存関係のある少数の画面)で、友人のアイデアを適用できますが、もちろんお勧めできません。
Dagger 2のコンポーネント(オブジェクトグラフ)のライフサイクルを決定するものDagger2アクティビティスコープ、必要なモジュール/コンポーネントの数
そしてもう1つの注意:オブジェクトがいつ破棄されるかを確認したい場合は、メソッドのメソッドを一緒に呼び出すと、GCがすぐに実行されます。
System.runFinalization();
System.gc();
これらの方法のいずれか1つのみを使用すると、GCは後で実行され、誤った結果が得られる場合があります。
Activity
ごとに個別のモジュールを宣言することは、まったく良い考えではありません。 Activity
ごとに個別のコンポーネントを宣言するのはさらに悪いです。この背後にある理由は非常に単純です-これらのすべてのモジュール/コンポーネントが本当に必要なわけではありません(すでに自分で見たように)。
ただし、Application
のライフサイクルに関連付けられたコンポーネントを1つだけにして、すべてのActivities
への注入に使用することも最適な解決策ではありません(これは友人のアプローチです)。次の理由により最適ではありません。
@Singleton
またはカスタムスコープ)Services
に注入することもできますが、Services
はActivities
とは異なるオブジェクトを必要とする場合があります(たとえば、Services
はプレゼンターを必要とせず、 tにはFragmentManager
などがあります)。単一のコンポーネントを使用すると、異なるコンポーネントに対して異なるオブジェクトグラフを定義する柔軟性が失われます。したがって、Activity
ごとのコンポーネントは過剰ですが、アプリケーション全体の単一のコンポーネントでは十分な柔軟性がありません。最適なソリューションは、これらの両極端の間にあります(通常どおり)。
私は次のアプローチを使用します。
Application
でインスタンス化されます。Activities
およびFragments
です)。各Activity
およびFragment
でインスタンス化されます。Services
に必要なオブジェクトを提供する「アプリケーション」コンポーネントの「サービス」サブコンポーネント。各Service
でインスタンス化されます。以下は、同じアプローチを実装する方法の例です。
2017年7月編集
AndroidアプリケーションでDagger依存性注入コードを構成する方法を示すビデオチュートリアルを公開しました: Android Dagger for Professionals Tutorial 。
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;
}
}
依存性注入に関する追加情報:
コンポーネント、モジュール、パッケージを整理する方法の最良の例は、Google Android Architecture Blueprints Github repo here にあります。
そこのソースコードを調べると、1つのアプリスコープのコンポーネント(アプリ全体のライフサイクル)が1つあり、次に、特定の機能に対応するアクティビティとフラグメントのアクティビティスコープのコンポーネントが別々にあることがわかります。事業。たとえば、次のパッケージがあります。
addedittask
taskdetail
tasks
各パッケージ内には、モジュール、コンポーネント、プレゼンターなどがあります。たとえば、taskdetail
内には次のクラスがあります。
TaskDetailActivity.Java
TaskDetailComponent.Java
TaskDetailContract.Java
TaskDetailFragment.Java
TaskDetailPresenter.Java
TaskDetailPresenterModule.Java
(1つのコンポーネントまたはモジュール内のすべてのアクティビティをグループ化するのではなく)この方法で整理することの利点は、Javaアクセシビリティ修飾子を利用して、Effective Javaアイテム13を満たすことができることです。つまり、機能的にグループ化されたクラスは同じパッケージに含まれ、protected
およびpackage-private
accessibility modifiers を利用して、クラスの意図しない使用を防ぐことができます。
最初のオプションは、アクティビティごとにサブスコープコンポーネントを作成します。アクティビティは、特定のアクティビティの依存関係(プレゼンター)のみを提供するサブスコープコンポーネントを作成できます。
2番目のオプションは、プレゼンターをスコープ外の依存関係として提供できる単一の@Singleton
コンポーネントを作成します。つまり、それらにアクセスすると、毎回プレゼンターの新しいインスタンスが作成されます。 (いいえ、リクエストするまで新しいインスタンスは作成されません)。
技術的には、どちらのアプローチも他のアプローチより悪いわけではありません。最初のアプローチでは、機能ごとにプレゼンターを分離するのではなく、レイヤーごとに分離します。
私は両方を使用しましたが、どちらも機能し、両方とも理にかなっています。
最初のソリューションの唯一の欠点(@Component(dependencies={...}
の代わりに@Subcomponent
を使用している場合)は、モジュールを内部で作成するアクティビティではないことを確認する必要があることです。モックを使用したメソッドの実装。繰り返しますが、フィールドインジェクションの代わりにコンストラクターインジェクションを使用する場合、コンストラクターでクラスを直接作成し、モックを直接作成することができます。