Exploration of the picture rotation problem caused by View's onAttachedToWindow

Exploration of the picture rotation problem caused by View's onAttachedToWindow

insert image description here

foreword

This article is based on the in-depth thinking of all the basic theories of this article based on the postDelayed method of View . It can be said to be the practice of in-depth thinking about the knowledge points of this article for the postDelayed method of View .

One day, when a colleague, Xjin, was making a list page to add carousel Bannerneeds, he posted that occasionally the carousel interval time would be disordered.

I saw his carousel implementation: use Handle.postDelayedthe interval carousel duration to send it in a loop after each execution of the carousel;

insert image description here

The code seems to have no major problems, but it seems that it should be removeCallbacksinvalid~!

Handle#removeCallbacks

stackoverflowI found relevant information on the Internet Why to use removeCallbacks() with postDelayed()?, and then tried to postDelayedchange it to Unreliable post, and found that the problem of disordered carousel interval time was solved~!

Although it is not clear what caused the problem to no longer reappear, the follow-up investigation was not continued due to other work interruptions.

A few days later, the problem of disordered rotation intervals appeared once again.

This time we used a custom Handlersum , which solved the problem perfectly.removeCallBackspostDelayed

Let's record the thinking in the process of solving the whole problem~!

outstanding issues

  1. View.removeCallbacksIs it really reliable;
  2. View.postCompared View.postDelayedwith why the frequency of bug recurrence is lower;

View#dispatchAttachedToWindow

HandleIs the removeCallBacksremoval method for unreliable? If the current task is not running, the task must be removed.
In other words, Handle#removeCallBackswhat is removed is what is queued to be executed Message.

So what exactly is the problem, and why is the probability of recurring the problem reduced postDelayedby replacing it ?post

For some time this time, I followed the source code and found that View#postDelayedthe messages sent by the user may not be immediately placed in the message queue.

Review the postDelayed method of the previous View and think deeply about the description in this article View.postDelayed小结:

postDelayedWhen the method is called, if the current one Viewis not attached to Windowit, it will Runnablebe cached in RunQueuethe queue first. Wait until View.dispatchAttachedToWindowafter the call is ViewRootHandlermade again postDelayed. The same Runnablewill only be used postDelayonce in this process.

When we print stopTimerand startTimerexecute the method , we find that most of them are when the list slides quickly .ViewPager#getHandlerHandlernull

Well, I ignored this Bannerbeing in the sliding process before View#dispatchDetachedFromWindow. Calls to this method result in Viewan internal Handleas null.

If Viewso Handle, nullthen Messagethe implementation of the may be affected.

In-depth thinking about the impact of View's postDelayed methodmAttachInfo in this article View.postDelayedhas also been analyzed. Here we pick up the main source code and read it.

//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;
}

postAnd postDelayedin the postDelayed method of View, it is explained in this article, and it will be stored during execution when the method is Viewexecuted .dispatchAttachedToWindowRunQueueMessage

RunQueue.executeActionsis ViewRootImpl.performTraversalcalled in it;

RunQueue.executeActionshost.dispatchAttachedToWindow(mAttachInfo, 0);is called after execution ;

RunQueue.executeActionsIt is called every time it ViewRootImpl.performTraversalis executed;

RunQueue.executeActionsThe parameters are mAttachInfoin Handlerthat is ViewRootHandler;

From here, there is no problem, View#postthe messages we use will be executed when they are Viewreceived ;Attached

During the development of general programs, if the use of containers is involved, two situations of production and consumption must be considered.
In the source code above, we have seen the logic of message execution (eventually all messages will be placed in MainLooperand consumed), what if the messages involved are removed?

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;
            }
        }
    }
}

When removing the message, if the current Viewone mAttahInfois empty, then we will only remove RunQuquethe message in the cache. . .

Oh,
so it is like this~!
That's really the only way~!

To sum up, if it View#mAttachInfois not empty then hello, me, and everyone. Otherwise View#post, the message will wait to be added in the cache queue, but the removed message can only remove the RunQueuecached message. If RunQueuethe news in Zhong has been synchronized to MainLooperZhong at this time, I am sorry that no View#mAttachInfoconcubine can't remove it.

According to the previous business code, if the message removal operation is executed later, the messages already in Viewthe queue cannot be removed and if the carousel message continues to be added, it will cause frequent execution of the carousel code block.dispatchDetachedFromWindowMainLooper

The text description may not be easy to understand for a while, the following is a simple analysis diagram of an unexpected carousel (why there are multiple carousel messages):

insert image description here

Let's talk about post and postDelayed

If I only look at the relevant source code, I feel that I can't find the problem, because the method postis also executed in the end postDelayed. So the comparison between the two is just a time difference. What impact can this time difference cause?
Looking back at the article I wrote before and thinking about the Android message mechanism (Handler&Looper) for another year , there is a term called synchronization barrier .

Synchronous barrier : Ignore all synchronous messages and return asynchronous messages. In other words, the synchronization barrier Handleradds a simple priority mechanism to the message mechanism, and the priority of asynchronous messages is higher than that of synchronous messages.

The most commonly used synchronization barrier is page refresh ( ) . ViewRootImpl#mTraversalRunnableFor related articles, you can read Choreographer, the choreographer of the Android system , and the monologue of ViewRootImpl, I am not a View (layout).View#dispatchAttachedToWindow The method described in this article is ViewRootImpl#performTraversalstriggered by .

Why do we say synchronization barrier ? View#dispatchAttachedToWindowThe method call that can be seen in the flow chart of the unexpected carousel above is very important to the entire process. 移除And 添加two messages, two if postDelayedthere are other messages inserted in the middle, and the synchronization barrier is the most likely message to be inserted and this message will cause View#mAttachInfochanges.
This makes the code with some small problems worse, and the bug is easier to reproduce.

Speaking of RecycleView

Why should I mention this problem, because many times we View.posthave no problem with executing tasks (PS: I feel that this point of view is also the original source of this problem).

RecycleViewThe internal child we know Viewis just one more preload than the screen size View, and exceeding this range or entering this range will cause it Viewto be added and removed.

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);
            }
        }
        /***部分代码省略***/
    }
    /***部分代码省略***/
}

insert image description here

If we frequently slide the list back and forth, then this will be executed Bannercontinuously . This results in most of the time , which affects the execution logic sent to the main thread in the business code .dispatchAttachedToWindowdispatchDetachedToWindow
View#mAttachInfonullMessage

The article is almost here. Solving this problem has brought me a deep feeling. Before learning the relevant source code of the Android system, it was just that everyone was learning and interviewing.
There are still relatively few knowledge points that can be applied to the actual research and development process. In many cases, it is enough to solve the problem, that is, knowing it but not knowing why.
The problem solved this time can make me feel deeply fuck the source code is beatifully.

The article is all told here, if you have other needs to communicate, you can leave a message~!

In 2023, I wish you a new year with ever-changing mood, happiness as sugar as honey, friends who value love and righteousness, lovers who will never leave, success at work, and everything goes well!

Guess you like

Origin blog.csdn.net/stven_king/article/details/128676314