ActivityLifecycleCallbacks - um ponto cego na API pública



Desde a infância, gosto de ler as instruções. Eu cresci, mas ainda me surpreende como os adultos se importam com as instruções: muitos deles pensam que todo mundo sabe e, ao mesmo tempo, usam uma ou duas funções, enquanto há muito mais! Quantos de vocês costumavam manter a temperatura no microondas? E ela está em quase todos.

Uma vez, decidi ler a documentação para as várias classes da estrutura do Android. Passei pelas principais classes: View, Activity, Fragment, Application e fiquei muito interessado no método Application.registerActivityLifecycleCallbacks () e na interface ActivityLifecycleCallbacks . Dos exemplos de seu uso na Internet, nada foi melhor do que registrar o ciclo de vida da atividade. Então eu comecei a fazer experiências com ele mesmo e agora a Yandex.Money o usa ativamente para resolver toda uma gama de tarefas relacionadas ao impacto dos objetos Activity do lado de fora.

O que são ActivityLifecycleCallbacks?


Olhe para esta interface, eis a aparência dela quando apareceu na API 14:

public interface ActivityLifecycleCallbacks {    void onActivityCreated(Activity activity, Bundle savedInstanceState);    void onActivityStarted(Activity activity);    void onActivityResumed(Activity activity);    void onActivityPaused(Activity activity);    void onActivityStopped(Activity activity);    void onActivitySaveInstanceState(Activity activity, Bundle outState);    void onActivityDestroyed(Activity activity); } 

A partir da API 29, vários novos métodos foram adicionados a ela.
 public interface ActivityLifecycleCallbacks {   default void onActivityPreCreated( @NonNull Activity activity, @Nullable Bundle savedInstanceState) { }   void onActivityCreated( @NonNull Activity activity, @Nullable Bundle savedInstanceState);   default void onActivityPostCreated( @NonNull Activity activity, @Nullable Bundle savedInstanceState) { }   default void onActivityPreStarted(@NonNull Activity activity) { }   void onActivityStarted(@NonNull Activity activity);   default void onActivityPostStarted(@NonNull Activity activity) { }   default void onActivityPreResumed(@NonNull Activity activity) { }   void onActivityResumed(@NonNull Activity activity);   default void onActivityPostResumed(@NonNull Activity activity) { }   default void onActivityPrePaused(@NonNull Activity activity) { }   void onActivityPaused(@NonNull Activity activity);   default void onActivityPostPaused(@NonNull Activity activity) { }   default void onActivityPreStopped(@NonNull Activity activity) { }   void onActivityStopped(@NonNull Activity activity);   default void onActivityPostStopped(@NonNull Activity activity) { }   default void onActivityPreSaveInstanceState( @NonNull Activity activity, @NonNull Bundle outState) { }   void onActivitySaveInstanceState( @NonNull Activity activity, @NonNull Bundle outState);   default void onActivityPostSaveInstanceState( @NonNull Activity activity, @NonNull Bundle outState) { }   default void onActivityPreDestroyed(@NonNull Activity activity) { }   void onActivityDestroyed(@NonNull Activity activity);   default void onActivityPostDestroyed(@NonNull Activity activity) { } } 


Talvez essa interface receba tão pouca atenção porque só apareceu no Android 4.0 ICS. Mas em vão, porque permite que você faça nativamente uma coisa muito interessante: influenciar todos os objetos de Atividade de fora. Mas mais sobre isso mais tarde, e primeiro dê uma olhada mais de perto nos métodos.

Cada método exibe um método semelhante do ciclo de vida da atividade e é chamado no momento em que o método é acionado em qualquer atividade no aplicativo. Ou seja, se o aplicativo for iniciado com MainActivity, o primeiro receberá uma chamada para ActivityLifecycleCallback.onActivityCreated (MainActivity, null).

Ótimo, mas como isso funciona? Não há mágica aqui: as próprias atividades relatam em que condição estão. Aqui está um trecho de código de Activity.onCreate ():

  mFragments.restoreAllState(p, mLastNonConfigurationInstances != null           ? mLastNonConfigurationInstances.fragments : null); } mFragments.dispatchCreate(); getApplication().dispatchActivityCreated(this, savedInstanceState); if (mVoiceInteractor != null) { 

Parece que nós mesmos fizemos BaseActivity. Somente colegas do Android fizeram isso por nós e também obrigaram todos a usá-lo. E isso é muito bom!

Na API 29, esses métodos funcionam quase da mesma forma, mas suas cópias de pré e pós são chamadas honestamente antes e depois de métodos específicos. Provavelmente agora é controlado pelo ActivityManager, mas esse é apenas o meu palpite, porque não fui à fonte o suficiente para descobrir.

Como fazer o ActivityLifecycleCallbacks funcionar?


Como todos os retornos de chamada, você deve primeiro registrá-los. Registramos todos os ActivityLifecycleCallbacks em Application.onCreate (), para obtermos informações sobre todas as atividades e a capacidade de gerenciá-las.

 class MyApplication : Application() {   override fun onCreate() { super.onCreate() registerActivityLifecycleCallbacks(MyCallbacks()) } } 

Uma pequena digressão: a partir da API 29, ActivityLifecycleCallbacks também pode ser registrado a partir da Atividade. Será um retorno de chamada local que só funciona para esta atividade.

Isso é tudo. Mas você pode encontrar isso simplesmente digitando o nome ActivityLifecycleCallbacks na caixa de pesquisa. Existem muitos exemplos de registro do ciclo de vida da atividade, mas é interessante? A atividade tem muitos métodos públicos (cerca de 400), e tudo isso pode ser usado para fazer muitas coisas interessantes e úteis.

O que pode ser feito com isso?


O que voce quer Deseja alterar dinamicamente o tema em todas as atividades do aplicativo? Por favor: o método setTheme () é público, o que significa que você pode chamá-lo a partir de um ActivityLifecycleCallback:

 class ThemeCallback( @StyleRes val myTheme: Int ) : ActivityLifecycleCallbacks { override fun onActivityCreated( activity: Activity, savedInstanceState: Bundle? ) { activity.setTheme(myTheme) } } 

Repita este truque SOMENTE em casa
Algumas atividades das bibliotecas conectadas podem usar seus temas personalizados. Portanto, verifique o pacote ou qualquer outro sintoma pelo qual possa ser determinado que o tema desta Atividade pode ser alterado com segurança. Por exemplo, verificamos o pacote como este (em Kotlinovsky =)):

 class ThemeCallback( @StyleRes val myTheme: Int ) : ActivityLifecycleCallbacks { override fun onActivityCreated( activity: Activity, savedInstanceState: Bundle? ) { val myPackage = "my.cool.application" activity .takeIf { it.javaClass.name.startsWith(myPackage) } ?.setTheme(myTheme) } } 

O exemplo não funciona? Você pode ter esquecido de registrar o ThemeCallback em Aplicativo ou Aplicativo no AndroidManifest.

Quer outro exemplo interessante? Você pode mostrar caixas de diálogo em qualquer atividade no aplicativo.

 class DialogCallback( val dialogFragment: DialogFragment ) : Application.ActivityLifecycleCallbacks { override fun onActivityCreated( activity: Activity, savedInstanceState: Bundle? ) { if (savedInstanceState == null) { val tag = dialogFragment.javaClass.name (activity as? AppCompatActivity) ?.supportFragmentManager ?.also { fragmentManager -> if (fragmentManager.findFragmentByTag(tag) == null) { dialogFragment.show(fragmentManager, tag) } } } } } 

Repita este truque SOMENTE em casa
Obviamente, você não deve mostrar um diálogo em todas as telas - nossos usuários não vão nos amar por isso. Mas, às vezes, pode ser útil mostrar algo assim em algumas telas específicas.

E aqui está outro caso: e se precisarmos iniciar uma atividade Tudo é simples aqui: Activity.startActivity () - e o conduzi. Mas e se precisarmos aguardar o resultado depois de chamar Activity.startActivityForResult ()? Eu tenho uma receita:

 class StartingActivityCallback : Application.ActivityLifecycleCallbacks { override fun onActivityCreated( activity: Activity, savedInstanceState: Bundle? ) { if (savedInstanceState == null) { (activity as? AppCompatActivity) ?.supportFragmentManager ?.also { fragmentManager -> val startingFragment = findOrCreateFragment(fragmentManager) startingFragment.listener = { resultCode, data -> // handle response here } // start Activity inside StartingFragment } } } private fun findOrCreateFragment( fragmentManager: FragmentManager ): StartingFragment { val tag = StartingFragment::class.java.name return fragmentManager .findFragmentByTag(tag) as StartingFragment? ?: StartingFragment().apply { fragmentManager .beginTransaction() .add(this, tag) .commit() } } } 

No exemplo, simplesmente descartamos o fragmento, que inicia a atividade e obtém o resultado e, em seguida, delega seu processamento para nós. Cuidado: aqui verificamos que nossa Atividade é AppCompatActivity, que pode levar a um loop infinito. Use outras condições.

Vamos complicar os exemplos. Até aquele momento, usamos apenas os métodos que já estão na atividade. Que tal adicionar o seu próprio? Suponha que desejamos enviar análises sobre como abrir uma tela. Ao mesmo tempo, nossas telas têm seus próprios nomes. Como resolver este problema? Muito simples Crie uma interface de tela que possa fornecer o nome de tela:

 interface Screen { val screenName: String } 

Agora nós o implementamos na atividade desejada:

 class NamedActivity : Activity(), Screen { override val screenName: String = "First screen" } 

Depois disso, configuramos ActivityLifecycleCallbacks especiais para essa atividade:

 class AnalyticsActivityCallback( val sendAnalytics: (String) -> Unit ) : Application.ActivityLifecycleCallbacks { override fun onActivityCreated( activity: Activity, savedInstanceState: Bundle? ) { if (savedInstanceState == null) { (activity as? Screen)?.screenName?.let(sendAnalytics) } } } 

Está vendo? Apenas verificamos a interface e, se implementada, enviamos análises.

Repita para fixação. O que fazer se você precisar lançar mais alguns parâmetros? Estenda a interface:

 interface ScreenWithParameters : Screen { val parameters: Map<String, String> } 

Implementamos:

 class NamedActivity : Activity(), ScreenWithParameters { override val screenName: String = "First screen" override val parameters: Map<String, String> = mapOf("key" to "value") } 

Nós enviamos:

 class AnalyticsActivityCallback( val sendAnalytics: (String, Map<String, String>?) -> Unit ) : Application.ActivityLifecycleCallbacks { override fun onActivityCreated( activity: Activity, savedInstanceState: Bundle? ) { if (savedInstanceState == null) { (activity as? Screen)?.screenName?.let { name -> sendAnalytics( name, (activity as? ScreenWithParameters)?.parameters ) } } } } 

Mas ainda é fácil. Tudo isso foi apenas para levar você a um tópico realmente interessante: injeção de dependência nativa. Sim, temos Dagger, Koin, Guice, Kodein e muito mais. Mas em pequenos projetos, eles são redundantes. Mas eu tenho uma solução ... Adivinha qual?

Digamos que temos uma ferramenta como esta:

 class CoolToolImpl { val extraInfo = "i am dependency" } 

Feche-o com a interface, como programadores adultos:

 interface CoolTool { val extraInfo: String } class CoolToolImpl : CoolTool { override val extraInfo = "i am dependency" } 

E agora um pouco de mágica de rua do ActivityLifecycleCallbacks: criaremos uma interface para implementar essa dependência, implementá-la nas Atividades necessárias e, usando ActivityLifecycleCallbacks, encontraremos e implementaremos a implementação do CoolToolImpl.

 interface RequireCoolTool { var coolTool: CoolTool } class CoolToolActivity : Activity(), RequireCoolTool { override lateinit var coolTool: CoolTool } class InjectingLifecycleCallbacks : ActivityLifecycleCallbacks { override fun onActivityCreated( activity: Activity, savedInstanceState: Bundle? ) { (activity as? RequireCoolTool)?.coolTool = CoolToolImpl() } } 

Lembre-se de registrar InjectingLifecycleCallbacks em seu aplicativo, execute-o e ele funcionará.

E não se esqueça de testar:

 @RunWith(AndroidJUnit4::class) class DIActivityTest { @Test fun `should access extraInfo when created`() { // prepare val mockTool: CoolTool = mock() val application = getApplicationContext<android.app.Application>() application.registerActivityLifecycleCallbacks( object : Application.ActivityLifecycleCallbacks { override fun onActivityCreated( activity: Activity, savedInstanceState: Bundle? ) { (activity as? RequireCoolTool)?.coolTool = mockTool } }) // invoke launch<DIActivity>(Intent(application, DIActivity::class.java)) // assert verify(mockTool).extraInfo } } 

Mas em grandes projetos, essa abordagem não será bem dimensionada, por isso não retirarei nenhuma estrutura de DI de ninguém. Onde é melhor combinar esforços e alcançar sinergia. Vou mostrar o exemplo do Dagger2. Se você tiver alguma atividade básica no projeto que faça algo como AndroidInjection.inject (this), é hora de jogar fora. Em vez disso, faça o seguinte:
  1. de acordo com as instruções, implementamos DispatchingAndroidInjector no aplicativo;
  2. crie um ActivityLifecycleCallbacks que chama DispatchingAndroidInjector.maybeInject () em cada atividade;
  3. registre ActivityLifecycleCallbacks no aplicativo.


 class MyApplication : Application() { @Inject lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Any> override fun onCreate() { super.onCreate() DaggerYourApplicationComponent.create().inject(this); registerActivityLifecycleCallbacks( InjectingLifecycleCallbacks( dispatchingAndroidInjector ) ) } } class InjectingLifecycleCallbacks( val dispatchingAndroidInjector: DispatchingAndroidInjector<Any> ) : ActivityLifecycleCallbacks { override fun onActivityCreated( activity: Activity, savedInstanceState: Bundle? ) {       dispatchingAndroidInjector.maybeInject(activity) } } 

E o mesmo efeito pode ser alcançado com outras estruturas de DI. Tente escrever nos comentários o que aconteceu.

Resumir


ActivityLifecycleCallbacks é uma ferramenta poderosa e subestimada. Experimente um desses exemplos e deixe-os ajudá-lo em seus projetos da mesma maneira que o Yandex.Money ajuda a melhorar nossos aplicativos.

Source: https://habr.com/ru/post/pt482476/


All Articles