Dagger- 각 활동 / 조각에 대해 각 구성 요소와 모듈을 만들어야합니까?


85

나는 한동안 dagger2와 함께 일했습니다. 그리고 각 활동 / 조각에 대해 고유 한 구성 요소 / 모듈을 만드는 것이 혼란 스러웠습니다. 이것을 명확히 도와주세요.

예를 들어, 앱이 있고 앱에는 약 50 개의 화면이 있습니다. DI 용 MVP 패턴과 Dagger2에 따라 코드를 구현합니다. 50 개의 활동과 50 명의 발표자가 있다고 가정합니다.

제 생각에는 일반적으로 다음과 같이 코드를 구성해야합니다.

  1. 앱이 열려있는 동안 사용할 모든 개체를 제공하는 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, ....)
    
    }
    
  2. ActivityScope 만들기 :

    @Scope
    @Documented
    @Retention(value=RUNTIME)
    public @interface ActivityScope {
    }
    
  3. 각 활동에 대한 구성 요소 및 모듈을 만듭니다. 일반적으로 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.
    

그것들은 내가 이것을 어떻게 구현하는지 보여주는 아주 간단한 예일뿐입니다.

하지만 내 친구가 또 다른 구현을 제공했습니다.

  1. 모든 발표자를 제공 할 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.
    
    }
    
  2. 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 개의 활동 만 열려 있어도 앱은 50 명의 발표자를 생성합니다.
    • 사용자가 활동을 닫은 후에도 발표자는 계속 남아 있습니다.
  2. 하나의 활동에 대해 두 개의 인스턴스를 생성하려면 어떻게됩니까? (그는 어떻게 두 명의 발표자를 만들 수 있습니까?)

  3. 앱을 초기화하는 데 많은 시간이 걸립니다 (많은 발표자, 개체 등을 만들어야하기 때문).

긴 게시물에 대해 죄송합니다. 저와 제 친구를 위해 이것을 명확히하도록 도와주세요. 그를 설득 할 수 없습니다. 귀하의 의견은 매우 감사하겠습니다.

/ ------------------------------------------------- ---------------------- /

데모를 한 후 편집하십시오.

먼저 @pandawarrior 답변에 감사드립니다. 이 질문을하기 전에 데모를 만들어야했습니다. 여기 내 결론이 다른 사람에게 도움이되기를 바랍니다.

  1. 내 친구가 한 일은 Provides-methods에 Scope를 넣지 않는 한 메모리 누수를 일으키지 않습니다. (예 : @Singleton 또는 @UserScope, ...)
  2. 제공 방법에 범위가없는 경우 많은 발표자를 만들 수 있습니다. (그래서 두 번째 요점도 잘못되었습니다)
  3. Dagger는 필요할 때만 발표자를 만듭니다. (따라서 앱 초기화에 시간이 오래 걸리지 않을 것입니다. Lazy Injection으로 인해 혼란 스러웠습니다)

따라서 위에서 말한 모든 이유는 대부분 잘못되었습니다. 그러나 두 가지 이유로 내 친구의 생각을 따라야한다는 의미는 아닙니다.

  1. 소스의 아키텍처에 좋지 않습니다. 그가 모듈 / 구성 요소의 모든 발표자를 초기화 할 때입니다. ( 인터페이스 분리 원칙 , 단일 책임 원칙도 위반 ).

  2. 스코프 컴포넌트를 생성 할 때 생성 된 시점과 소멸 된 시점을 알 수 있으므로 메모리 누수를 방지하는 데 큰 이점이 있습니다. 따라서 각 활동에 대해 @ActivityScope를 사용하여 구성 요소를 만들어야합니다. 내 친구 구현과 함께 Provider-method => 메모리 누수에 Scope를 넣는 것을 잊었다 고 상상해 봅시다.

제 생각에는 작은 앱 (많은 종속성이 없거나 유사한 종속성이있는 몇 개의 화면)을 사용하면 친구 아이디어를 적용 할 수 있지만 물론 권장하지 않습니다.

더 읽기 선호 : Dagger 2에서 구성 요소 (개체 그래프)의 수명주기를 결정하는 요소는 무엇입니까? Dagger2 활동 범위, 얼마나 많은 모듈 / 구성 요소가 필요합니까?

그리고 한 가지 더 참고 : 객체가 언제 파괴되었는지 확인하려면 메소드의 객체를 함께 호출하면 GC가 즉시 실행됩니다.

    System.runFinalization();
    System.gc();

이러한 방법 중 하나만 사용하면 나중에 GC가 실행되어 잘못된 결과를 얻을 수 있습니다.

답변:


85

각각에 대해 별도의 모듈을 선언하는 Activity것은 전혀 좋은 생각이 아닙니다. 각각에 대해 별도의 구성 요소를 선언하는 Activity것은 더 나쁩니다. 그 이유는 매우 간단합니다.이 모든 모듈 / 컴포넌트가 실제로 필요하지는 않습니다 (이미 직접 보셨 듯이).

그러나 Application의 수명주기에 연결된 구성 요소를 하나만 가지고 모든 구성 요소 에 주입하는 Activities것도 최적의 솔루션이 아닙니다 (이는 친구의 접근 방식입니다). 다음과 같은 이유로 최적이 아닙니다.

  1. 하나의 범위 ( @Singleton또는 사용자 지정 범위)로만 제한합니다.
  2. 제한된 유일한 범위는 삽입 된 객체를 "애플리케이션 싱글 톤"으로 만듭니다. 따라서 범위 지정 오류나 범위가 지정된 객체의 잘못된 사용으로 인해 전역 메모리 누수가 쉽게 발생할 수 있습니다.
  3. 당신은에 주입하기 위해 Dagger2를 사용하는 것이 좋습니다 Services도하지만, Services다른 객체를 요구할 수 있습니다 Activities(예 : Services필요 발표자는없는없는 FragmentManager등). 단일 구성 요소를 사용하면 구성 요소마다 다른 개체 그래프를 정의하는 유연성이 떨어집니다.

따라서 구성 요소 당 Activity과잉이지만 전체 응용 프로그램에 대한 단일 구성 요소는 충분히 유연하지 않습니다. 최적의 솔루션은 이러한 극단 사이에 있습니다 (일반적으로).

다음 접근 방식을 사용합니다.

  1. "전역"개체를 제공하는 단일 "응용 프로그램"구성 요소 (예 : 응용 프로그램의 모든 구성 요소간에 공유되는 전역 상태를 유지하는 개체). 에서 인스턴스화되었습니다 Application.
  2. 모든 사용자 대면 "컨트롤러"에 필요한 객체를 제공하는 "응용 프로그램"구성 요소의 "컨트롤러"하위 구성 요소 (내 아키텍처에서는 ActivitiesFragments). 각각 ActivityFragment.
  3. 모든에 필요한 객체를 제공하는 "응용 프로그램"구성 요소의 "서비스"하위 구성 요소 Services. 각 Service.

다음은 동일한 접근 방식을 구현할 수있는 방법의 예입니다.


2017 년 7 월 수정

Android 애플리케이션에서 Dagger 종속성 주입 코드를 구성하는 방법을 보여주는 비디오 자습서를 게시했습니다. Android Dagger for Professionals Tutorial .


2018 년 2 월 편집

Android의 종속성 주입에 대한 전체 과정을 게시했습니다 .

이 과정에서는 의존성 주입의 이론을 설명하고 이것이 Android 애플리케이션에서 어떻게 자연스럽게 나타나는지 보여줍니다. 그런 다음 Dagger 구성이 일반적인 종속성 주입 체계에 어떻게 부합하는지 보여줍니다.

이 과정을 수강하면 각 Activity / Fragment에 대해 별도의 모듈 / 구성 요소 정의를 갖는 아이디어가 기본적으로 가장 근본적인 방식으로 결함이있는 이유를 이해할 수 있습니다.

이러한 접근 방식은 "기능적"클래스 집합의 표현 계층 구조가 "구성"클래스 집합의 구조로 미러링되도록하여 이들을 함께 결합합니다. 이것은 "구성"및 "기능적"클래스 집합을 분리 된 상태로 유지하는 종속성 주입의 주요 목적에 위배됩니다.


적용 범위 :

@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;
    }
}

종속성 주입에 대한 추가 정보 :

Dagger 2 스코프 이해하기

Android의 종속성 주입


1
의견을 공유해 주신 @vasiliy에게 감사드립니다. 이것이 바로 내가 사용하고 현재 전략을 따르는 방법입니다. MVP 패턴의 경우 추천 된 사람 ControllerModule이 새 항목 Presenter을 만든 다음 발표자가 Activity또는에 삽입됩니다 Fragment. 이에 찬성하거나 반대하는 확실한 의견이 있습니까?
Wahib Ul Haq

@Vasiliy, 나는 당신의 전체 기사를 읽었고 아마도 당신이 메커니즘에서 상호 작용 자발표자 를 고려하지 않았을 수도 있음을 발견했습니다 . ControllerModule은 상호 작용 자발표자 의 모든 종속성을 제공 합니까? 내가 놓친 것이 있으면 작은 힌트를주세요.
iamcrypticcoder

@ mahbub.kuet, "인터랙 터"와 "프레젠터"가 말하는 것을 이해한다면 ControllerComponent그것들을 주입해야합니다. 내부 배선 ControllerModule또는 추가 모듈 도입 여부는 귀하에게 달려 있습니다. 실제 앱에서는 모든 것을 단일 모듈에 넣는 대신 구성 요소 당 다중 모듈 접근 방식을 사용하는 것이 좋습니다. 다음은에 대한 예 ApplicationComponent이지만 컨트롤러는 동일합니다. github.com/techyourchance/idocare-android/tree/master/app/src/…
Vasiliy

2
@ Mr.Hyde, 일반적으로 예,하지만 사용할 수 있는 ApplicationComponent모든 종속성 에서 명시 적으로 선언해야 ControllerComponent합니다. 또한 생성 된 코드의 메서드 수도 더 높아집니다. 아직 종속 구성 요소를 사용할 좋은 이유를 찾지 못했습니다.
Vasiliy

1
저는 오늘 모든 프로젝트에서이 접근 방식을 사용 dagger.android하고 있으며 동기 부여가 안된 것으로 알고 있기 때문에 패키지 에서 아무것도 사용하지 않습니다 . 따라서이 예제는 여전히 최신이며 Android IMHO에서 DI를 수행하는 가장 좋은 방법입니다.
Vasiliy

15

구성 요소, 모듈 및 패키지를 구성하는 방법에 대한 가장 좋은 예는 여기 에서 Google Android 아키텍처 청사진 Github 저장소 에서 찾을 수 있습니다 .

여기에서 소스 코드를 살펴보면 앱 범위의 단일 구성 요소 (전체 앱 기간의 수명주기 포함)가있는 것을 볼 수 있으며, 그런 다음 특정 기능에 해당하는 활동 및 프래그먼트에 대해 별도의 활동 범위 구성 요소가 있음을 알 수 있습니다. 계획. 예를 들어 다음 패키지가 있습니다.

addedittask
taskdetail
tasks

각 패키지 내부에는 모듈, 구성 요소, 발표자 등이 있습니다. 예를 들어 내부 taskdetail에는 다음 클래스가 있습니다.

TaskDetailActivity.java
TaskDetailComponent.java
TaskDetailContract.java
TaskDetailFragment.java
TaskDetailPresenter.java
TaskDetailPresenterModule.java

모든 활동을 하나의 구성 요소 또는 모듈로 그룹화하는 대신 이러한 방식으로 구성하는 이점은 Java 접근성 수정자를 활용하고 효과적인 Java 항목 13을 충족 할 수 있다는 것입니다. 즉, 기능적으로 그룹화 된 클래스는 동일합니다. 패키지와 당신의 장점이 걸릴 수 있습니다 protectedpackage-private 접근성 수정을 수업의 의도하지 않은 용도를 방지 할 수 있습니다.


1
이것은 또한 내가 선호하는 접근 방식입니다. 나는 그들이하지 말아야 할 것에 접근 할 수있는 활동 / 조각을 좋아하지 않는다.
Joao Sousa

3

첫 번째 옵션은 각 활동에 대해 하위 범위 구성 요소를 만듭니다. 여기서 활동은 해당 특정 활동에 대한 종속성 (발표자) 만 제공하는 하위 범위 구성 요소를 만들 수 있습니다.

두 번째 옵션은 @Singleton발표자에게 범위가 지정되지 않은 종속성을 제공 할 수 있는 단일 구성 요소를 만듭니다. 즉, 액세스 할 때마다 발표자의 새 인스턴스를 만듭니다. (아니요, 요청할 때까지 새 인스턴스를 생성하지 않습니다).


기술적으로 어느 쪽도 다른 쪽보다 나쁘지 않습니다. 첫 번째 방법은 발표자를 기능별로 구분하지 않고 계층별로 구분하는 것입니다.

나는 둘 다 사용했고, 둘 다 작동하고 둘 다 의미가 있습니다.

첫 번째 솔루션 ( @Component(dependencies={...}대신 사용 하는 경우)의 유일한 단점은 @Subcomponent내부적으로 자체 모듈을 생성하는 활동이 아닌지 확인해야한다는 것입니다. 모듈 메서드 구현을 모의로 바꿀 수 없기 때문입니다. 그런 다음 필드 주입 대신 생성자 주입을 사용하면 생성자로 직접 클래스를 생성하여 직접 모의를 제공 할 수 있습니다.


1

Provider<"your component's name">간단한 구성 요소 구현 대신 사용 하여 메모리 누수를 방지하고 쓸모없는 구성 요소를 생성하십시오. 따라서 구성 요소의 인스턴스를 제공하지 않고 공급자 만 제공하므로 get () 메서드를 호출하면 구성 요소가 lazy에 의해 생성됩니다. 따라서 제공자의 .get ()이 호출되면 발표자가 적용됩니다. 여기에서 제공자에 대해 읽고이를 적용하십시오. ( 공식 Dagger 문서 )


그리고 다른 좋은 방법은 멀티 바인딩을 사용하는 것입니다. 이에 따라 발표자를지도에 바인딩하고 필요할 때 공급자를 통해 생성해야합니다. ( 여기에 멀티 바인딩에 대한 문서가 있습니다 )


-5

친구가 맞습니다. 모든 활동에 대해 구성 요소와 모듈을 만들 필요가 없습니다. Dagger는 복잡한 코드를 줄이고 활동의 onCreate 메소드에서 인스턴스화하는 대신 모듈에 클래스 인스턴스화를 위임하여 Android 활동을 더 깔끔하게 만드는 데 도움이됩니다.

일반적으로 우리는 이렇게 할 것입니다

public class MainActivity extends AppCompatActivity {


Presenter1 mPresenter1;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mPresenter1 = new Presenter1(); // you instantiate mPresentation1 in onCreate, imagine if there are 5, 10, 20... of objects for you to instantiate.
}

}

대신 이렇게

public class MainActivity extends AppCompatActivity {

@Inject
Presenter1 mPresenter1; // the Dagger module take cares of instantiation for your

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    injectThisActivity();
}

private void injectThisActivity() {
    MainApplication.get(this)
            .getMainComponent()
            .inject(this);
}}

그래서 너무 많은 것을 쓰면 단검의 목적이 패배하지 않습니까? 모든 활동에 대해 모듈과 구성 요소를 만들어야하는 경우에는 오히려 내 발표자를 활동에서 인스턴스화합니다.

다음에 대한 질문 :

1- 메모리 누수 :

아니요, @Singleton제공하는 발표자에게 주석을 달지 않는 한 아닙니다 . Dagger는 @Inject대상 클래스에서 할 때마다 개체를 만듭니다 . 시나리오에서 다른 발표자를 만들지 않습니다. 로그를 사용하여 생성되었는지 여부를 확인할 수 있습니다.

@Module
public class AppPresenterModule {

@Provides
@Singleton // <-- this will persists throughout the application, too many of these is not good
Activity1Presenter provideActivity1Presentor(Context context, ...some other params){
    Log.d("Activity1Presenter", "Activity1Presenter initiated");
    return new Activity1PresenterImpl(context, ...some other params);
}

@Provides // Activity2Presenter will be provided every time you @Inject into the activity
Activity2Presenter provideActivity2Presentor(Context context, ...some other params){
    Log.d("Activity2Presenter", "Activity2Presenter initiated");
    return new Activity2PresenterImpl(context, ...some other params);
}

.... Same with 48 others presenters.

}

2- 두 번 주입하고 해시 코드를 기록합니다.

//MainActivity.java
@Inject Activity1Presenter mPresentation1
@Inject Activity1Presenter mPresentation2

@Inject Activity2Presenter mPresentation3
@Inject Activity2Presenter mPresentation4
//log will show Presentation2 being initiated twice

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    injectThisActivity();
    Log.d("Activity1Presenter1", mPresentation1.hashCode());
    Log.d("Activity1Presenter2", mPresentation2.hashCode());
    //it will shows that both have same hash, it's a Singleton
    Log.d("Activity2Presenter1", mPresentation3.hashCode());
    Log.d("Activity2Presenter2", mPresentation4.hashCode());
    //it will shows that both have different hash, hence different objects

3. 아니요, 개체는 @Inject앱 초기화 대신 활동에 들어갈 때만 생성됩니다 .


1
의견을 보내 주셔서 감사합니다. 말씀하신 내용이 틀린 것은 아니지만 최선의 답변이 아니라고 생각합니다. 제 편집 게시물을 참조하십시오. 따라서 허용됨으로 표시 할 수 없습니다.
미스터 마이크

@EpicPandaForce : 어,하지만 어딘가에서 인스턴스화해야합니다. 뭔가 의존성 반전 원칙을 위반해야합니다.
David Liu
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.