Estou trabalhando com o dagger2 há algum tempo. E fiquei confuso se deveria criar um componente / módulo próprio para cada atividade / fragmento. Por favor me ajude a esclarecer isso:
Por exemplo, temos um aplicativo, e o aplicativo tem cerca de 50 telas. Implementaremos o código seguindo o padrão MVP e Dagger2 para DI. Suponha que temos 50 atividades e 50 apresentadores.
Na minha opinião, normalmente devemos organizar o código assim:
Crie um AppComponent e AppModule que fornecerá todos os objetos que serão usados enquanto o aplicativo estiver aberto.
@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, ....) }
Criar ActivityScope:
@Scope @Documented @Retention(value=RUNTIME) public @interface ActivityScope { }
Crie um componente e um módulo para cada atividade. Normalmente, vou colocá-los como classes estáticas dentro da classe 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.
Esses são apenas exemplos muito simples para mostrar como eu implementaria isso.
Mas um amigo meu acabou de me dar outra implementação:
Crie PresenterModule que fornecerá a todos os apresentadores:
@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. }
Crie AppModule e 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, ....) }
Sua explicação é: ele não precisa criar componentes e módulos para cada atividade. Acho que a ideia dos meus amigos não é nada boa, mas corrija-me se eu estiver errado. Aqui estão as razões:
Muitos vazamentos de memória :
- O aplicativo criará 50 apresentadores, mesmo se o usuário tiver apenas 2 atividades abertas.
- Depois que o usuário fecha uma atividade, seu apresentador ainda permanecerá
O que acontece se eu quiser criar duas instâncias de uma atividade? (como ele pode criar dois apresentadores)
Levará muito tempo para o aplicativo inicializar (porque ele precisa criar muitos apresentadores, objetos ...)
Desculpe pelo longo post, mas por favor me ajude a esclarecer isso para mim e para meu amigo, não consigo convencê-lo. Seus comentários serão muito apreciados.
/ ------------------------------------------------- ---------------------- /
Edite depois de fazer uma demonstração.
Primeiramente, obrigado pela resposta @pandawarrior. Eu deveria ter criado uma demonstração antes de fazer esta pergunta. Espero que minha conclusão aqui possa ajudar outra pessoa.
- O que meu amigo fez não causa vazamentos de memória, a menos que ele coloque qualquer Scope nos métodos Provides. (Por exemplo @Singleton ou @UserScope, ...)
- Podemos criar muitos apresentadores, se o método Provides não tiver nenhum Scope. (Então, meu segundo ponto também está errado)
- Dagger criará os apresentadores somente quando eles forem necessários. (Portanto, o aplicativo não demorará muito para inicializar, fiquei confuso com a injeção lenta)
Portanto, todas as razões que disse acima estão erradas. Mas isso não significa que devemos seguir a ideia do meu amigo, por dois motivos:
Não é bom para a arquitetura do código-fonte, quando ele inicia todos os apresentadores no módulo / componente. (Isso viola o princípio de segregação de interface , talvez o princípio de responsabilidade única também).
Quando criamos um componente de escopo, saberemos quando ele é criado e quando é destruído, o que é um grande benefício para evitar vazamentos de memória. Portanto, para cada Activity devemos criar um Component com um @ActivityScope. Vamos imaginar, com a implementação de meus amigos, que esquecemos de colocar algum escopo no método Provider => vazamentos de memória irão ocorrer.
Na minha opinião, com um aplicativo pequeno (apenas algumas telas sem muitas dependências ou com dependências semelhantes), poderíamos aplicar a ideia do meu amigo, mas é claro que não é recomendável.
Prefira ler mais sobre: O que determina o ciclo de vida de um componente (gráfico de objeto) no Dagger 2? Escopo da atividade Dagger2, quantos módulos / componentes eu preciso?
E mais uma observação: se você quiser ver quando os objetos são destruídos, você pode chamar aqueles do método juntos e o GC será executado imediatamente:
System.runFinalization();
System.gc();
Se você usar apenas um desses métodos, o GC será executado mais tarde e você poderá obter resultados errados.
ControllerModule
irá criar um novoPresenter
e então o apresentador é injetado noActivity
ouFragment
. Alguma opinião sólida a favor ou contra isso?ControllerComponent
deveria injetá-los. Se você os conecta internamenteControllerModule
ou introduz um módulo adicional, depende de você. Em aplicativos reais, aconselho o uso de vários módulos por abordagem de componente em vez de colocar tudo em um único módulo. Aqui está um exemplo deApplicationComponent
, mas o controlador será o mesmo: github.com/techyourchance/idocare-android/tree/master/app/src/…ApplicationComponent
todas as dependências queControllerComponent
pode usar. Além disso, a contagem de métodos do código gerado será maior. Ainda não encontrei um bom motivo para usar componentes dependentes.dagger.android
pacote porque acho que está mal motivado. Portanto, este exemplo ainda está muito atualizado e ainda é a melhor maneira de fazer DI no Android IMHO.Alguns dos melhores exemplos de como organizar seus componentes, módulos e pacotes podem ser encontrados no repositório Google Android Architecture Blueprints Github aqui .
Se você examinar o código-fonte lá, poderá ver que há um único componente com escopo de aplicativo (com um ciclo de vida da duração de todo o aplicativo) e, em seguida, componentes com escopo de atividade separados para a atividade e fragmento correspondente a uma determinada funcionalidade em um projeto. Por exemplo, existem os seguintes pacotes:
Dentro de cada pacote existe um módulo, componente, apresentador etc. Por exemplo, dentro
taskdetail
existem as seguintes classes:TaskDetailActivity.java TaskDetailComponent.java TaskDetailContract.java TaskDetailFragment.java TaskDetailPresenter.java TaskDetailPresenterModule.java
A vantagem de organizar dessa forma (em vez de agrupar todas as atividades em um componente ou módulo) é que você pode aproveitar os modificadores de acessibilidade Java e cumprir o item 13. Efetivo de Java. Em outras palavras, as classes agrupadas funcionalmente serão as mesmas pacote e você pode tirar proveito de
protected
epackage-private
modificadores de acessibilidade para prevenir usos não intencionais de suas classes.fonte
A primeira opção cria um componente de subescopo para cada atividade, em que a atividade é capaz de criar componentes de subescopo que fornecem apenas a dependência (apresentador) para aquela atividade específica.
A segunda opção cria um único
@Singleton
componente que é capaz de fornecer aos apresentadores como dependências sem escopo, ou seja, quando você os acessa, cria uma nova instância do apresentador a cada vez. (Não, ele não cria uma nova instância até que você solicite uma).Tecnicamente, nenhuma das abordagens é pior que a outra. A primeira abordagem não separa os apresentadores por recurso, mas por camada.
Eu usei os dois, ambos funcionam e fazem sentido.
A única desvantagem da primeira solução (se você estiver usando em
@Component(dependencies={...}
vez de@Subcomponent
) é que você precisa ter certeza de que não é a Activity que cria seu próprio módulo internamente, porque então você não pode substituir implementações de método de módulo por simulações. Então, novamente, se você usar injeção de construtor em vez de injeção de campo, você pode apenas criar a classe diretamente com o construtor, fornecendo diretamente mocks.fonte
Use em
Provider<"your component's name">
vez da implementação de componentes simples para evitar vazamentos de memória e criar toneladas de componentes inúteis. Portanto, seus componentes serão criados por preguiçoso quando você chamar o método get (), já que você não fornece uma instância do componente, mas apenas o provedor. Portanto, seu apresentador será aplicado se .get () do provedor for chamado. Leia sobre o Provedor aqui e aplique-o. ( Documentação oficial do punhal )E outra ótima maneira é usar multibinding. De acordo com ele, você deve vincular seus apresentadores ao mapa e criá-los por meio de provedores quando necessário. ( aqui estão os documentos sobre multibinding )
fonte
Seu amigo está correto, você realmente não precisa criar componentes e módulos para todas as atividades. Supõe-se que o Dagger ajuda a reduzir o código confuso e torna suas atividades Android mais limpas, delegando instanciações de classe aos Módulos em vez de instanciá-las no método onCreate de Activities.
Normalmente faremos assim
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. } }
Você faz isso ao invés
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); }}
Então, escrever muitas coisas derrota o propósito da adaga, não? Prefiro instanciar meus apresentadores em Atividades se tiver que criar Módulos e Componentes para todas as Atividades.
Quanto às suas perguntas sobre:
1- Vazamento de memória:
Não, a menos que você coloque uma
@Singleton
anotação para os apresentadores que fornecer. Dagger só criará o objeto sempre que você fizer um@Inject
na classe de destino. Isso não criará os outros apresentadores em seu cenário. Você pode tentar usar o Log para ver se eles foram criados ou não.@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- Você injeta duas vezes e registra o código hash
//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. Não, os objetos só serão criados quando você entrar
@Inject
nas atividades, ao invés do app init.fonte