나는 한동안 dagger2와 함께 일했습니다. 그리고 각 활동 / 조각에 대해 고유 한 구성 요소 / 모듈을 만드는 것이 혼란 스러웠습니다. 이것을 명확히 도와주세요.
예를 들어, 앱이 있고 앱에는 약 50 개의 화면이 있습니다. DI 용 MVP 패턴과 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, ....) }
그의 설명은 다음과 같습니다. 그는 각 활동에 대해 구성 요소와 모듈을 만들 필요가 없습니다.
제 친구들의 생각이 전혀 좋지 않다고 생각 합니다만, 제가 틀렸다면 정정 해주세요. 이유는 다음과 같습니다.
-
많은 메모리 누수 :
- 사용자에게 2 개의 활동 만 열려 있어도 앱은 50 명의 발표자를 생성합니다.
- 사용자가 활동을 닫은 후에도 발표자는 계속 남아 있습니다.
-
하나의 활동에 대해 두 개의 인스턴스를 생성하려면 어떻게됩니까? (그는 어떻게 두 명의 발표자를 만들 수 있습니까?)
-
앱을 초기화하는 데 많은 시간이 걸립니다 (많은 발표자, 개체 등을 만들어야하기 때문).
긴 게시물에 대해 죄송합니다. 저와 제 친구를 위해 이것을 명확히하도록 도와주세요. 그를 설득 할 수 없습니다.
귀하의 의견은 매우 감사하겠습니다.
/ ————————————————- ———————- /
데모를 한 후 편집하십시오.
먼저 @pandawarrior 답변에 감사드립니다. 이 질문을하기 전에 데모를 만들어야했습니다. 여기 내 결론이 다른 사람에게 도움이되기를 바랍니다.
- 내 친구가 한 일은 Provides-methods에 Scope를 넣지 않는 한 메모리 누수를 일으키지 않습니다. (예 : @Singleton 또는 @UserScope, …)
- 제공 방법에 범위가없는 경우 많은 발표자를 만들 수 있습니다. (그래서 두 번째 요점도 잘못되었습니다)
- Dagger는 필요할 때만 발표자를 만듭니다. (따라서 앱 초기화에 시간이 오래 걸리지 않을 것입니다. Lazy Injection으로 인해 혼란 스러웠습니다)
따라서 위에서 말한 모든 이유는 대부분 잘못되었습니다. 그러나 두 가지 이유로 내 친구의 생각을 따라야한다는 의미는 아닙니다.
-
소스의 아키텍처에 좋지 않습니다. 그가 모듈 / 구성 요소의 모든 발표자를 초기화 할 때입니다. ( 인터페이스 분리 원칙 , 단일 책임 원칙도 위반 ).
-
스코프 컴포넌트를 생성 할 때 생성 된 시점과 소멸 된 시점을 알 수 있으므로 메모리 누수를 방지하는 데 큰 이점이 있습니다. 따라서 각 활동에 대해 @ActivityScope를 사용하여 구성 요소를 만들어야합니다. 내 친구 구현과 함께 Provider-method => 메모리 누수에 Scope를 넣는 것을 잊었다 고 상상해 봅시다.
제 생각에는 작은 앱 (많은 종속성이 없거나 유사한 종속성이있는 몇 개의 화면)을 사용하면 친구 아이디어를 적용 할 수 있지만 물론 권장하지 않습니다.
더 읽기 선호 :
Dagger 2에서 구성 요소 (개체 그래프)의 수명주기를 결정하는 요소는 무엇입니까?
Dagger2 활동 범위, 얼마나 많은 모듈 / 구성 요소가 필요합니까?
그리고 한 가지 더 참고 : 객체가 언제 파괴되었는지 확인하려면 메소드의 객체를 함께 호출하면 GC가 즉시 실행됩니다.
System.runFinalization();
System.gc();
이러한 방법 중 하나만 사용하면 나중에 GC가 실행되어 잘못된 결과를 얻을 수 있습니다.
답변
각각에 대해 별도의 모듈을 선언하는 Activity
것은 전혀 좋은 생각이 아닙니다. 각각에 대해 별도의 구성 요소를 선언하는 Activity
것은 더 나쁩니다. 그 이유는 매우 간단합니다.이 모든 모듈 / 컴포넌트가 실제로 필요하지는 않습니다 (이미 직접 보셨 듯이).
그러나 Application
의 수명주기에 연결된 구성 요소를 하나만 가지고 모든 구성 요소 에 주입하는 Activities
것도 최적의 솔루션이 아닙니다 (이는 친구의 접근 방식입니다). 다음과 같은 이유로 최적이 아닙니다.
- 하나의 범위 (
@Singleton
또는 사용자 지정 범위)로만 제한합니다. - 제한된 유일한 범위는 삽입 된 객체를 “애플리케이션 싱글 톤”으로 만듭니다. 따라서 범위 지정 오류나 범위가 지정된 객체의 잘못된 사용으로 인해 전역 메모리 누수가 쉽게 발생할 수 있습니다.
- 당신은에 주입하기 위해 Dagger2를 사용하는 것이 좋습니다
Services
도하지만,Services
다른 객체를 요구할 수 있습니다Activities
(예 :Services
필요 발표자는없는없는FragmentManager
등). 단일 구성 요소를 사용하면 구성 요소마다 다른 개체 그래프를 정의하는 유연성이 떨어집니다.
따라서 구성 요소 당 Activity
과잉이지만 전체 응용 프로그램에 대한 단일 구성 요소는 충분히 유연하지 않습니다. 최적의 솔루션은 이러한 극단 사이에 있습니다 (일반적으로).
다음 접근 방식을 사용합니다.
- “전역”개체를 제공하는 단일 “응용 프로그램”구성 요소 (예 : 응용 프로그램의 모든 구성 요소간에 공유되는 전역 상태를 유지하는 개체). 에서 인스턴스화되었습니다
Application
. - 모든 사용자 대면 “컨트롤러”에 필요한 객체를 제공하는 “응용 프로그램”구성 요소의 “컨트롤러”하위 구성 요소 (내 아키텍처에서는
Activities
및Fragments
). 각각Activity
및Fragment
. - 모든에 필요한 객체를 제공하는 “응용 프로그램”구성 요소의 “서비스”하위 구성 요소
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;
}
}
종속성 주입에 대한 추가 정보 :
답변
구성 요소, 모듈 및 패키지를 구성하는 방법에 대한 가장 좋은 예는 여기 에서 Google Android 아키텍처 청사진 Github 저장소 에서 찾을 수 있습니다 .
여기에서 소스 코드를 살펴보면 앱 범위의 단일 구성 요소 (전체 앱 기간의 수명주기 포함)가있는 것을 볼 수 있으며, 그런 다음 특정 기능에 해당하는 활동 및 프래그먼트에 대해 별도의 활동 범위 구성 요소가 있음을 알 수 있습니다. 계획. 예를 들어 다음 패키지가 있습니다.
addedittask
taskdetail
tasks
각 패키지 내부에는 모듈, 구성 요소, 발표자 등이 있습니다. 예를 들어 내부 taskdetail
에는 다음 클래스가 있습니다.
TaskDetailActivity.java
TaskDetailComponent.java
TaskDetailContract.java
TaskDetailFragment.java
TaskDetailPresenter.java
TaskDetailPresenterModule.java
모든 활동을 하나의 구성 요소 또는 모듈로 그룹화하는 대신 이러한 방식으로 구성하는 이점은 Java 접근성 수정자를 활용하고 효과적인 Java 항목 13을 충족 할 수 있다는 것입니다. 즉, 기능적으로 그룹화 된 클래스는 동일합니다. 패키지와 당신의 장점이 걸릴 수 있습니다 protected
및 package-private
접근성 수정을 수업의 의도하지 않은 용도를 방지 할 수 있습니다.
답변
첫 번째 옵션은 각 활동에 대해 하위 범위 구성 요소를 만듭니다. 여기서 활동은 해당 특정 활동에 대한 종속성 (발표자) 만 제공하는 하위 범위 구성 요소를 만들 수 있습니다.
두 번째 옵션은 @Singleton
발표자에게 범위가 지정되지 않은 종속성을 제공 할 수 있는 단일 구성 요소를 만듭니다. 즉, 액세스 할 때마다 발표자의 새 인스턴스를 만듭니다. (아니요, 요청할 때까지 새 인스턴스를 생성하지 않습니다).
기술적으로 어느 쪽도 다른 쪽보다 나쁘지 않습니다. 첫 번째 방법은 발표자를 기능별로 구분하지 않고 계층별로 구분하는 것입니다.
나는 둘 다 사용했고, 둘 다 작동하고 둘 다 의미가 있습니다.
첫 번째 솔루션 ( @Component(dependencies={...}
대신 사용 하는 경우)의 유일한 단점은 @Subcomponent
내부적으로 자체 모듈을 생성하는 활동이 아닌지 확인해야한다는 것입니다. 모듈 메서드 구현을 모의로 바꿀 수 없기 때문입니다. 그런 다음 필드 주입 대신 생성자 주입을 사용하면 생성자로 직접 클래스를 생성하여 직접 모의를 제공 할 수 있습니다.
답변
Provider<"your component's name">
간단한 구성 요소 구현 대신 사용 하여 메모리 누수를 방지하고 쓸모없는 구성 요소를 생성하십시오. 따라서 구성 요소의 인스턴스를 제공하지 않고 공급자 만 제공하므로 get () 메서드를 호출하면 구성 요소가 lazy에 의해 생성됩니다. 따라서 제공자의 .get ()이 호출되면 발표자가 적용됩니다. 여기에서 제공자에 대해 읽고이를 적용하십시오. ( 공식 Dagger 문서 )
그리고 다른 좋은 방법은 멀티 바인딩을 사용하는 것입니다. 이에 따라 발표자를지도에 바인딩하고 필요할 때 공급자를 통해 생성해야합니다. ( 여기에 멀티 바인딩에 대한 문서가 있습니다 )
답변
친구가 맞습니다. 모든 활동에 대해 구성 요소와 모듈을 만들 필요가 없습니다. 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
앱 초기화 대신 활동에 들어갈 때만 생성됩니다 .