
Cada desenvolvedor surgiu com perguntas sobre o ciclo de vida da atividade: o que é um serviço de ligação, como salvar o estado da interface quando a tela é girada e como o Fragmento difere da Atividade.
Nós da FunCorp acumulamos uma lista de perguntas sobre tópicos semelhantes, mas com certas nuances. Eu quero compartilhar alguns deles com você.
1. Todo mundo sabe que se você abrir a segunda atividade em cima da primeira e girar a tela, a cadeia de chamadas do ciclo de vida terá a seguinte aparência:
Atividade de AberturaFirstActivity: onPause
SecondActivity: onCreate
SecondActivity: onStart
SecondActivity: onResume
FirstActivity: onSaveInstanceState
FirstActivity: onStop
TurnSecondActivity: onPause
SecondActivity: onSaveInstanceState
SecondActivity: onStop
SecondActivity: onCreate
SecondActivity: onStart
SecondActivity: onRestoreInstanceState
SecondActivity: onResume
VoltarSecondActivity: onPause
FirstActivity: onCreate
FirstActivity: onStart
FirstActivity: onRestoreInstanceState
SecondActivity: onStop
E o que acontecerá se a segunda atividade for transparente?
Solução
No caso da atividade superior transparente, em termos de lógica, tudo é um pouco diferente. Precisamente por ser transparente, após a rotação, é necessário restaurar o conteúdo e a atividade diretamente abaixo dele. Portanto, a ordem das chamadas será um pouco diferente:
Atividade de descobertaFirstActivity: onPause
SecondActivity: onCreate
SecondActivity: onStart
SecondActivity: onResume
TurnSecondActivity: onPause
SecondActivity: onSaveInstanceState
SecondActivity: onStop
SecondActivity: onCreate
SecondActivity: onStart
SecondActivity: onRestoreInstanceState
SecondActivity: onResume
FirstActivity: onSaveInstanceState
FirstActivity: onStop
FirstActivity: onCreate
FirstActivity: onStart
FirstActivity: onRestoreInstanceState
FirstActivity: onResume
FirstActivity: onPause
2. Nenhum aplicativo pode ficar sem adicionar dinamicamente uma visualização, mas às vezes você precisa mover a mesma visualização entre telas diferentes. O mesmo objeto pode ser adicionado simultaneamente a duas atividades diferentes? O que acontece se eu criá-lo com o contexto Aplicativo e quiser adicioná-lo a diferentes atividades ao mesmo tempo?
Por que isso é necessário?
Existem bibliotecas "não muito agradáveis" que mantêm importantes lógicas de negócios em visualizações personalizadas, e recriar essas visualizações em cada nova atividade é uma má decisão, porque Eu quero ter um conjunto de dados.



Solução
Nada impede a criação de uma exibição com o contexto Aplicativo. Simplesmente aplicará estilos padrão que não estão relacionados a nenhuma atividade. Você também pode mover essa visualização entre atividades diferentes sem problemas, mas precisa garantir que ela seja adicionada a apenas um pai.
private void addViewInner(View child, int index, LayoutParams params, boolean preventRequestLayout) { ... if (child.getParent() != null) { throw new IllegalStateException("The specified child already has a parent. " + "You must call removeView() on the child's parent first."); } ... }
Você pode, por exemplo, se inscrever em ActivityLifecycleCallbacks, excluir onStop (removeView) da atividade atual, adicionar onStart à próxima abertura (addView).
3. O fragmento pode ser adicionado através de adição e substituição. E qual é a diferença entre essas duas opções em termos da ordem de chamar métodos de ciclo de vida? Quais são as vantagens de cada um deles?
Solução
Mesmo se você adicionar um fragmento através da substituição, isso não significa que ele seja completamente substituído. Isso significa que, nesse ponto do contêiner, sua visualização será substituída; portanto, onDestroyView será chamado para o fragmento atual e onCreateView será chamado novamente ao retornar.
Isso praticamente muda as regras do jogo. Você precisa desanexar todos os controladores e classes associados à interface do usuário no onDestroyView. É necessário separar claramente o recebimento dos dados necessários ao fragmento e o preenchimento da visualização (listas, etc.), pois o preenchimento e a quebra da visualização ocorrerão com muito mais frequência do que o recebimento de dados (leitura de alguns dados do banco de dados).
Também há nuances na recuperação de estado: por exemplo, onSaveInstanceState às vezes vem após onDestroyView. Além disso, deve-se ter em mente que, se nulo chegou emViewStateRestored, isso significa que você não precisa restaurar nada, mas não redefinir para o estado padrão.
Se falarmos sobre a conveniência entre adicionar e substituir, substituir será mais econômico na memória se você tiver uma navegação profunda (temos uma profundidade de navegação do usuário de um dos KPIs do produto). Também é muito mais conveniente substituir a barra de ferramentas por substituir, pois no onCreateView você pode reinfundi-la. Das vantagens do add: menos problemas com o ciclo de vida, quando você volta, não recrio a exibição e não preciso preencher novamente nada.
4. Às vezes, você ainda precisa trabalhar diretamente com os serviços e até com os serviços de ligação. A atividade interage com um desses serviços (apenas uma atividade). Ele se conecta ao serviço e transfere dados para ele. Quando você liga a tela, nossa atividade é destruída e somos obrigados a recuperar deste serviço. Mas se não houver conexão, o serviço será destruído e, após a ativação do vínculo, será para um serviço completamente diferente. Como garantir que, quando você liga o serviço, permanece ativo?
Solução
Se você conhece uma solução bonita, escreva nos comentários. Apenas algo semelhante vem à mente:
@Override protected void onDestroy() { super.onDestroy(); ThreadsUtils.postOnUiThread(new Runnable() { @Override public void run() { unbindService(mConnection); } }); }
5. Recentemente, refizemos a navegação dentro de nosso aplicativo na Atividade Única (usando uma das bibliotecas disponíveis). Anteriormente, cada tela do aplicativo era uma atividade separada, agora a navegação funciona em fragmentos. O problema de retornar à atividade no meio da pilha foi resolvido por sinalizadores de intenção. Como posso retornar ao fragmento no meio da pilha?
Solução
Sim, o FragmentManager não fornece soluções prontas para uso. Cicerone faz algo semelhante dentro de si:
protected void backTo(BackTo command) { String key = command.getScreenKey(); if (key == null) { backToRoot(); } else { int index = localStackCopy.indexOf(key); int size = localStackCopy.size(); if (index != -1) { for (int i = 1; i < size - index; i++) { localStackCopy.pop(); } fragmentManager.popBackStack(key, 0); } else { backToUnexisting(command.getScreenKey()); } } }
6. Além disso, recentemente nos livramos de um componente tão ineficiente e complexo como o ViewPager, porque a lógica de interagir com ele é muito complicada e o comportamento dos fragmentos é imprevisível em certos casos. Em alguns fragmentos, usamos fragmentos internos. O que acontece ao usar fragmentos dentro dos elementos RecycleView?
Solução
Em geral, não haverá nada errado. O fragmento será adicionado e exibido sem problemas. A única coisa que enfrentamos são inconsistências com seu ciclo de vida. A implementação no ViewPager gerencia o ciclo de vida dos fragmentos por meio de setUserVisibleHint, e o RecycleView faz tudo na testa sem pensar na visibilidade e acessibilidade reais dos fragmentos.
7. Pela mesma razão pela qual mudamos do ViewPager, encontramos o problema de restaurar o estado. No caso de fragmentos, isso foi implementado pela estrutura: nos lugares certos, simplesmente redefinimos onSaveInstanceState e salvamos todos os dados necessários no Bundle. Quando você recriar o ViewPager, todos os fragmentos foram restaurados pelo FragmentManager e retornaram seu estado. O que fazer com o RecycleView e seu ViewHolder?
Solução
"Você precisa escrever tudo no banco de dados e ler sempre", você diz. Ou a lógica para salvar o estado deve estar fora e a lista é apenas uma exibição. Em um mundo ideal é. Mas, no nosso caso, cada elemento da lista é uma tela complexa com sua própria lógica. Portanto, tive que inventar minha bicicleta no estilo de "vamos fazer a mesma lógica que no ViewPager e no fragmento":
Adaptador public class RecycleViewGalleryAdapter extends RecyclerView.Adapter<GalleryItemViewHolder> implements GalleryAdapter { private static final String RV_STATE_KEY = "RV_STATE"; @Nullable private Bundle mSavedState; @Override public void onBindViewHolder(GalleryItemViewHolder holder, int position) { if (holder.isAttached()) { holder.detach(); } holder.attach(createArgs(position, getItemViewType(position))); restoreItemState(holder); } @Override public void saveState(Bundle bundle) { Bundle adapterState = new Bundle(); saveItemsState(adapterState); bundle.putBundle(RV_STATE_KEY, adapterState); } @Override public void restoreState(@Nullable Bundle bundle) { if (bundle == null) { return; } mSavedState = bundle.getBundle(RV_STATE_KEY); } private void restoreItemState(GalleryItemViewHolder holder) { if (mSavedState == null) { holder.restoreState(null); return; } String stateKey = String.valueOf(holder.getGalleryItemId()); Bundle state = mSavedState.getBundle(stateKey); if (state == null) { holder.restoreState(null); mSavedState = null; return; } holder.restoreState(state); mSavedState.remove(stateKey); } private void saveItemsState(Bundle outState) { GalleryItemHolder holder = getCurrentGalleryViewItem(); saveItemState(outState, (GalleryItemViewHolder) holder); } private void saveItemState(Bundle bundle, GalleryItemViewHolder holder) { Bundle itemState = new Bundle(); holder.saveState(itemState); bundle.putBundle(String.valueOf(holder.getGalleryItemId()), itemState); } }
Em Fragment.onSaveInstanceState, lemos o estado dos titulares de que precisamos e os colocamos no Bundle. Ao recriar os titulares, obtemos o Bundle salvo e no onBindViewHolder transferimos os estados encontrados dentro dos titulares:
8. Com o que isso nos ameaça?
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity); ViewGroup root = findViewById(R.id.default_id); ViewGroup view1 = new LinearLayout(this); view1.setId(R.id.default_id); root.addView(view1); ViewGroup view2 = new FrameLayout(this); view2.setId(R.id.default_id); view1.addView(view2); ViewGroup view3 = new RelativeLayout(this); view3.setId(R.id.default_id); view2.addView(view3); }
Solução
De fato, não há nada de errado nisso. No mesmo RecycleView, as listas de elementos com o mesmo ID são armazenadas. No entanto, ainda há uma pequena nuance:
@Override protected <T extends View> T findViewTraversal(@IdRes int id) { if (id == mID) { return (T) this; } final View[] where = mChildren; final int len = mChildrenCount; for (int i = 0; i < len; i++) { View v = where[i]; if ((v.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) { v = v.findViewById(id); if (v != null) { return (T) v; } } } return null; }
Deve ser cuidadoso se tivermos elementos com o mesmo ID na hierarquia, porque é sempre o primeiro elemento encontrado que sempre é retornado e, em diferentes níveis da chamada findViewById, podem ser objetos diferentes.
9. Você sai do TooLargeTransaction quando gira a tela (sim, aqui o nosso ViewPager ainda é indiretamente responsável). Como encontrar o culpado?
Solução
É bem simples: desligue ActivityLifecycleCallbacks no Application, pegue todos os onActivitySaveInstanceState e analise tudo o que estiver dentro do Bundle. Lá, você pode obter o status de todas as visualizações e todos os fragmentos desta atividade.
Abaixo está um exemplo de como obtemos o estado dos fragmentos do Bundle:
fun Bundle.getFragmentsStateList(): List<FragmentBundle>? { try { val fragmentManagerState: FragmentManagerState? = getParcelable("android:support:fragments") val active = fragmentManagerState?.mActive ?: return emptyList() return active.filter { it.mSavedFragmentState != null }.map { fragmentState -> FragmentBundle(fragmentState.mClassName, fragmentState.mSavedFragmentState) } } catch (throwable: Throwable) { Assert.fail(throwable) return null } } fun init() { application.registerActivityLifecycleCallbacks(object : SimpleActivityLifecycleCallback() { override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle?) { super.onActivitySaveInstanceState(activity, outState) outState?.let { ThreadsUtils.runOnMainThread { trackActivitySaveState(activity, outState) } } } }) } @MainThread private fun trackActivitySaveState(activity: Activity, outState: Bundle) { val sizeInBytes = outState.getSizeInBytes() val fragmentsInfos = outState.getFragmentsStateList() ?.map { mapFragmentsSaveInstanceSaveInfo(it) } ... }
Em seguida, basta calcular o tamanho do pacote e registrá-lo:
fun Bundle.getSizeInBytes(): Int { val parcel = Parcel.obtain() return try { parcel.writeValue(this) parcel.dataSize() } finally { parcel.recycle() } }
10. Suponha que tenhamos uma atividade e um conjunto de dependências. Sob certas condições, precisamos recriar um conjunto dessas dependências (por exemplo, clicando em uma certa experiência com outra interface do usuário). Como implementamos isso?
Solução
Obviamente, você pode mexer com bandeiras e fazer com que seja uma espécie de "muleta" de reinício da atividade por meio do lançamento de intenções. Mas, de fato, tudo é muito simples - a atividade possui um método de recriação.
Muito provavelmente, a maior parte desse conhecimento não será útil para você, uma vez que você não chega a cada um deles de uma vida boa. No entanto, alguns deles demonstram bem como uma pessoa sabe raciocinar e propor soluções. Usamos perguntas semelhantes em entrevistas. Se você tiver tarefas interessantes que foram solicitadas a resolver nas entrevistas, ou se você mesmo as definir, escreva-as nos comentários - será interessante discutir!