Dagger- 각 활동 / 조각에 대해 각 구성 요소와 모듈을 만들어야합니까?
나는 한동안 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 구성이 일반적인 종속성 주입 체계에 어떻게 부합하는지 보여줍니다.
이 과정을 수강하면 각 활동 / 조각에 대해 별도의 모듈 / 구성 요소 정의를 갖는 아이디어가 기본적으로 가장 근본적인 방식으로 결함이있는 이유를 이해할 수 있습니다.
이러한 접근 방식은 "기능적"클래스 집합의 표현 계층 구조가 "구성"클래스 집합의 구조로 미러링되도록하여 이들을 함께 결합합니다. 이것은 "구성"및 "기능적"클래스 집합을 분리 된 상태로 유지하는 종속성 주입의 주요 목적에 위배됩니다.
적용 범위 :
@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;
}
}
Additional information on dependency injection:
Dependency Injection in Android
Some of the best examples of how to organise your components, modules, and packages can be found in the Google Android Architecture Blueprints Github repo here.
If you examine the source code there, you can see there is one single app-scoped Component (with a lifecycle of the duration of the whole app) and then separate Activity-scoped Components for the Activity and Fragment corresponding to a given functionality in a project. For example, there are the following packages:
addedittask
taskdetail
tasks
Inside each package there is a module, component, presenter etc. For instance, inside taskdetail
there are the following classes:
TaskDetailActivity.java
TaskDetailComponent.java
TaskDetailContract.java
TaskDetailFragment.java
TaskDetailPresenter.java
TaskDetailPresenterModule.java
The advantage of organising this way (rather than grouping all of the activities in one component or module) is that you can take advantage of Java accessibility modifiers and fulfil Effective Java item 13. In other words, the functionally grouped classes will be in the same package and you can take advantage of protected
and package-private
accessibility modifiers to prevent unintended usages of your classes.
First option creates a subscoped component for each activity, where the activity is able to create subscoped components that only provide the dependency (presenter) for that particular activity.
Second option creates a single @Singleton
component that is able to provide the presenters as unscoped dependencies, meaning when you access them, you create a new instance of the presenter each time. (No, it doesn't create a new instance until you request one).
Technically, neither approach is worse than the other. The first approach doesn't separate presenters by feature, but by layer.
I've used both, they both work and both make sense.
The only disadvantage of the first solution (if you're using @Component(dependencies={...}
instead of @Subcomponent
) is that you need to make sure it's not the Activity that creates its own module internally, because then you cannot replace module method implementations with mocks. Then again, if you use constructor injection instead of field injection, you can just create the class directly with constructor, directly giving it mocks.
Your friend is correct, you don't really have to create components and modules for every activities. Dagger is supposed to help you reduce messy code and makes your Android activities cleaner by delegating class instantiations to the Modules instead of instantiates them in Activities' onCreate method.
Normally we'll do like this
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.
}
}
You do this instead
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);
}}
So writing too many things kind of defeat the purpose of dagger no? I rather instantiate my presenters in Activities if I have to create Modules and Components for every Activities.
As for your questions about:
1- Memory leak:
No, not unless you put a @Singleton
annotation to the presenters you providing. Dagger will only create the object whenever you do an @Inject
in the target class`. It won't create the other presenters in your scenario. You can try to use Log to see if they are created or not.
@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- You inject twice and log their hash code
//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. No, the objects will only be created when you @Inject
in to the activities, instead of the app init.
'IT TIP' 카테고리의 다른 글
정적 메서드 상속에 대한 올바른 대안은 무엇입니까? (0) | 2020.10.12 |
---|---|
Java BigDecimal 가능한 오버 플로우 버그 (0) | 2020.10.12 |
list ()는 list comprehension보다 약간 더 많은 메모리를 사용합니다. (0) | 2020.10.12 |
무엇을 (0) | 2020.10.12 |
Spring MVC가 404로 응답하고“DispatcherServlet에서 URI […]를 사용하는 HTTP 요청에 대한 매핑을 찾을 수 없음”을보고하는 이유는 무엇입니까? (0) | 2020.10.12 |