Exploração do problema de rotação de imagem causado por onAttachedToWindow da View

Exploração do problema de rotação de imagem causado por onAttachedToWindow da View

insira a descrição da imagem aqui

prefácio

Este artigo é baseado no pensamento aprofundado de todas as teorias básicas deste artigo com base no método postDelayed de View . Pode-se dizer que é a prática do pensamento aprofundado sobre os pontos de conhecimento deste artigo para o método postDelayed de Vista .

Um dia, quando um colega, Xjin, estava fazendo uma página de lista para adicionar as Bannernecessidades do carrossel, ele postou que ocasionalmente o tempo de intervalo do carrossel ficava desordenado.

Eu vi a implementação do carrossel dele: use Handle.postDelayeda duração do carrossel de intervalo para enviá-lo em um loop após cada execução do carrossel;

insira a descrição da imagem aqui

O código parece não ter grandes problemas, mas parece que deve ser removeCallbacksinválido~!

Lidar com #removeCallbacks

stackoverflowEncontrei informações relevantes na Internet Por que usar removeCallbacks() com postDelayed()? e, em seguida, tentei postDelayedalterá-lo para Unreliable post, e descobri que o problema do intervalo de tempo do carrossel desordenado foi resolvido~!

Embora não esteja claro o que fez com que o problema não reaparecesse, a investigação de acompanhamento não foi continuada devido a outras interrupções do trabalho.

Alguns dias depois, o problema dos intervalos de rotação desordenado apareceu novamente.

Desta vez, usamos uma Handlersoma personalizada , que resolveu o problema perfeitamente.removeCallBackspostDelayed

Vamos registrar o pensamento no processo de resolução de todo o problema~!

questões pendentes

  1. View.removeCallbacksÉ realmente confiável;
  2. View.postComparado View.postDelayedcom o motivo pelo qual a frequência de recorrência do bug é menor;

Ver#dispatchAttachedToWindow

HandleO removeCallBacksmétodo de remoção não é confiável? Se a tarefa atual não estiver em execução, ela deverá ser removida.
Em outras palavras, Handle#removeCallBackso que é removido é o que está na fila para ser executado Message.

Então, qual é exatamente o problema e por que a probabilidade de recorrência do problema é reduzida postDelayedao substituí-lo ?post

Por algum tempo, desta vez, segui o código-fonte e descobri que View#postDelayedas mensagens enviadas pelo usuário podem não ser colocadas imediatamente na fila de mensagens.

Revise o método postDelayed da View anterior e pense profundamente sobre a descrição neste artigo View.postDelayed小结:

postDelayedQuando o método é chamado, se o atual Viewnão estiver anexado a Windowele, ele será Runnablearmazenado em cache RunQueuena fila primeiro. Aguarde até View.dispatchAttachedToWindowque a chamada seja ViewRootHandlerfeita novamente postDelayed. O mesmo Runnableserá utilizado apenas postDelayuma vez neste processo.

Quando imprimimos stopTimere startTimerexecutamos o método , descobrimos que a maioria deles é quando a lista desliza rapidamente .ViewPager#getHandlerHandlernull

Bem, eu ignorei isso Bannerestando no processo de deslizamento antes View#dispatchDetachedFromWindow. As chamadas para esse método resultam em Viewum Handleas null.

Nesse caso View, Handlea implementação do pode ser afetada null.Message

O pensamento aprofundado sobre o impacto do método postDelayed do View neste artigo também foi analisado. Aqui, pegamos o código-fonte principal e o lemos.mAttachInfoView.postDelayed

//View.java
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
    
    
    mAttachInfo = info;
    /****部分代码省略*****/
    // Transfer all pending runnables.
    if (mRunQueue != null) {
    
    
        mRunQueue.executeActions(info.mHandler);
        mRunQueue = null;
    }
    performCollectViewAttributes(mAttachInfo, visibility);
    onAttachedToWindow();
    /****部分代码省略*****/
}
public boolean postDelayed(Runnable action, long delayMillis) {
    
    
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
    
    
        return attachInfo.mHandler.postDelayed(action, delayMillis);
    }
    // Postpone the runnable until we know on which thread it needs to run.
    // Assume that the runnable will be successfully placed after attach.
    getRunQueue().postDelayed(action, delayMillis);
    return true;
}
public boolean post(Runnable action) {
    
    
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
    
    
        return attachInfo.mHandler.post(action);
    }
    // Postpone the runnable until we know on which thread it needs to run.
    // Assume that the runnable will be successfully placed after attach.
    getRunQueue().post(action);
    return true;
}
public boolean removeCallbacks(Runnable action) {
    
    
    if (action != null) {
    
    
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
    
    
            attachInfo.mHandler.removeCallbacks(action);
            attachInfo.mViewRootImpl.mChoreographer.removeCallbacks(
                  Choreographer.CALLBACK_ANIMATION, action, null);
        }
        getRunQueue().removeCallbacks(action);
    }
    return true;
}

postE postDelayedno método postDelayed do View, é explicado neste artigo, e será armazenado durante a execução quando o método for Viewexecutado .dispatchAttachedToWindowRunQueueMessage

RunQueue.executeActionsé ViewRootImpl.performTraversalchamado nele;

RunQueue.executeActionshost.dispatchAttachedToWindow(mAttachInfo, 0);é chamado após a execução ;

RunQueue.executeActionsEle é chamado toda vez que ViewRootImpl.performTraversalé executado;

RunQueue.executeActionsOs parâmetros estão mAttachInfoem ;HandlerViewRootHandler

A partir daqui, não há problema, View#postas mensagens que usamos serão executadas quando forem Viewrecebidas ;Attached

No desenvolvimento de programas gerais, caso se trate da utilização de embalagens, devem ser consideradas duas situações de produção e consumo.
No código-fonte acima, vimos a lógica de execução da mensagem (eventualmente todas as mensagens serão inseridas MainLoopere consumidas), e se as mensagens envolvidas forem removidas?

public class HandlerActionQueue {
    
    
    public void removeCallbacks(Runnable action) {
    
    
        synchronized (this) {
    
    
            final int count = mCount;
            int j = 0;
            final HandlerAction[] actions = mActions;
            for (int i = 0; i < count; i++) {
    
    
                if (actions[i].matches(action)) {
    
    
                    // Remove this action by overwriting it within
                    // this loop or nulling it out later.
                    continue;
                }
                if (j != i) {
    
    
                    // At least one previous entry was removed, so
                    // this one needs to move to the "new" list.
                    actions[j] = actions[i];
                }
                j++;
            }
            // The "new" list only has j entries.
            mCount = j;
            // Null out any remaining entries.
            for (; j < count; j++) {
    
    
                actions[j] = null;
            }
        }
    }
}

Ao remover a mensagem, se a atual Viewestiver mAttahInfovazia, removeremos apenas RunQuquea mensagem do cache. . .

Oh,
então é assim~!
Essa é realmente a única maneira~!

Resumindo, se View#mAttachInfonão estiver vazio, olá, eu e todos. Caso contrário View#post, a mensagem aguardará para ser adicionada à fila de cache, mas a mensagem removida só poderá remover a RunQueuemensagem em cache. Se RunQueueas notícias em Zhong foram sincronizadas para MainLooperZhong neste momento, lamento que nenhuma View#mAttachInfoconcubina não possa removê-las.

De acordo com o código de negócio anterior, se a operação de remoção da mensagem for executada posteriormente, as mensagens que já estão na Viewfila não podem ser removidas e se a mensagem do carrossel continuar a ser adicionada, causará execução frequente do bloco de código do carrossel.dispatchDetachedFromWindowMainLooper

A descrição do texto pode não ser fácil de entender por um tempo, o seguinte é um diagrama de análise simples de um carrossel inesperado (por que existem várias mensagens do carrossel):

insira a descrição da imagem aqui

Vamos falar sobre post e postDelayed

Se eu olhar apenas o código-fonte relevante, sinto que não consigo encontrar o problema, porque o método posttambém é executado no final postDelayed. Então a comparação entre os dois é apenas uma diferença de horário, qual o impacto que essa diferença de horário pode causar?
Olhando para o artigo que escrevi antes e pensando no mecanismo de mensagens do Android (Handler&Looper) por mais um ano , existe um termo chamado barreira de sincronização .

Barreira síncrona : ignora todas as mensagens síncronas e retorna mensagens assíncronas. Em outras palavras, a barreira de sincronização Handleradiciona um mecanismo de prioridade simples ao mecanismo de mensagem, e a prioridade das mensagens assíncronas é maior que a das mensagens síncronas.

A barreira de sincronização mais usada é a atualização de página ( ) . ViewRootImpl#mTraversalRunnablePara artigos relacionados, você pode ler Choreographer, o coreógrafo do sistema Android e o monólogo de ViewRootImpl, não sou uma exibição (layout).View#dispatchAttachedToWindow O método descrito neste artigo é ViewRootImpl#performTraversalsacionado por .

Por que dizemos barreira de sincronização ? View#dispatchAttachedToWindowA chamada do método que pode ser vista no fluxograma do inesperado carrossel acima é muito importante para todo o processo. 移除E 添加duas mensagens, duas se postDelayedhouver outras mensagens inseridas no meio, e a barreira de sincronização é a mensagem mais provável de ser inserida e essa mensagem causará View#mAttachInfoalterações.
Isso piora o código com alguns pequenos problemas e o bug é mais fácil de reproduzir.

Falando em RecycleView

Por que devo mencionar esse problema, porque muitas vezes View.postnão temos problemas com a execução de tarefas (PS: acho que esse ponto de vista também é a fonte original desse problema).

RecycleViewO filho interno que conhecemos Viewé apenas um pré-carregamento a mais do que o tamanho da tela View, e exceder esse intervalo ou entrar nesse intervalo fará com que ele Viewseja adicionado e removido.

public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {
    
    
    /***部分代码省略***/
    private void initChildrenHelper() {
    
    
        this.mChildHelper = new ChildHelper(new Callback() {
    
    
            public int getChildCount() {
    
    
                return RecyclerView.this.getChildCount();
            }

            public void addView(View child, int index) {
    
    
                RecyclerView.this.addView(child, index);
                RecyclerView.this.dispatchChildAttached(child);
            }

            public int indexOfChild(View view) {
    
    
                return RecyclerView.this.indexOfChild(view);
            }

            public void removeViewAt(int index) {
    
    
                View child = RecyclerView.this.getChildAt(index);
                if (child != null) {
    
    
                    RecyclerView.this.dispatchChildDetached(child);
                    child.clearAnimation();
                }

                RecyclerView.this.removeViewAt(index);
            }
        }
        /***部分代码省略***/
    }
    /***部分代码省略***/
}

insira a descrição da imagem aqui

Se deslizarmos frequentemente a lista para frente e para trás, isso será executado Bannercontinuamente . Isso resulta na maioria das vezes , o que afeta a lógica de execução enviada ao thread principal no código comercial .dispatchAttachedToWindowdispatchDetachedToWindow
View#mAttachInfonullMessage

O artigo está quase chegando. Resolver esse problema me trouxe um sentimento profundo. Antes de aprender o código-fonte relevante do sistema Android, era só que todo mundo estava aprendendo e entrevistando.
Ainda são relativamente poucos os pontos de conhecimento que podem ser aplicados ao próprio processo de pesquisa e desenvolvimento, mas em muitos casos basta para resolver o problema, ou seja, saber mas não saber o porquê.
O problema resolvido desta vez pode me fazer sentir profundamente fuck the source code is beatifully.

O artigo está todo contado aqui, se você tiver outras necessidades de comunicação, pode deixar uma mensagem~!

Em 2023, desejo a você um ano novo com humor em constante mudança, felicidade como açúcar como mel, amigos que valorizam o amor e a retidão, amantes que nunca vão embora, sucesso no trabalho e tudo corra bem!

Acho que você gosta

Origin blog.csdn.net/stven_king/article/details/128676314
Recomendado
Clasificación