Exploration of the picture rotation problem caused by View's onAttachedToWindow
Article directory
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 Banner
needs, he posted that occasionally the carousel interval time would be disordered.
I saw his carousel implementation: use Handle.postDelayed
the interval carousel duration to send it in a loop after each execution of the carousel;
The code seems to have no major problems, but it seems that it should be removeCallbacks
invalid~!
Handle#removeCallbacks
stackoverflow
I found relevant information on the Internet Why to use removeCallbacks() with postDelayed()?, and then tried to postDelayed
change 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
Handler
sum , which solved the problem perfectly.removeCallBacks
postDelayed
Let's record the thinking in the process of solving the whole problem~!
outstanding issues
View.removeCallbacks
Is it really reliable;View.post
ComparedView.postDelayed
with why the frequency of bug recurrence is lower;
View#dispatchAttachedToWindow
Handle
Is the removeCallBacks
removal method for unreliable? If the current task is not running, the task must be removed.
In other words, Handle#removeCallBacks
what 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
postDelayed
by replacing it ?post
For some time this time, I followed the source code and found that View#postDelayed
the 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小结
:
postDelayed
When the method is called, if the current oneView
is not attached toWindow
it, it willRunnable
be cached inRunQueue
the queue first. Wait untilView.dispatchAttachedToWindow
after the call isViewRootHandler
made againpostDelayed
. The sameRunnable
will only be usedpostDelay
once in this process.
When we print stopTimer
and startTimer
execute the method , we find that most of them are when the list slides quickly .ViewPager#getHandler
Handler
null
Well, I ignored this Banner
being in the sliding process before View#dispatchDetachedFromWindow
. Calls to this method result in View
an internal Handle
as null
.
If View
so Handle
, null
then Message
the implementation of the may be affected.
In-depth thinking about the impact of View's postDelayed methodmAttachInfo
in this article View.postDelayed
has 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;
}
post
And postDelayed
in the postDelayed method of View, it is explained in this article, and it will be stored during execution when the method is View
executed .dispatchAttachedToWindow
RunQueue
Message
RunQueue.executeActions
isViewRootImpl.performTraversal
called in it;
RunQueue.executeActions
host.dispatchAttachedToWindow(mAttachInfo, 0);
is called after execution ;
RunQueue.executeActions
It is called every time itViewRootImpl.performTraversal
is executed;
RunQueue.executeActions
The parameters aremAttachInfo
inHandler
that isViewRootHandler
;
From here, there is no problem, View#post
the messages we use will be executed when they are View
received ;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 MainLooper
and 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 View
one mAttahInfo
is empty, then we will only remove RunQuque
the message in the cache. . .
Oh,
so it is like this~!
That's really the only way~!
To sum up, if it View#mAttachInfo
is 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 RunQueue
cached message. If RunQueue
the news in Zhong has been synchronized to MainLooper
Zhong at this time, I am sorry that no View#mAttachInfo
concubine can't remove it.
According to the previous business code, if the message removal operation is executed later, the messages already in
View
the queue cannot be removed and if the carousel message continues to be added, it will cause frequent execution of the carousel code block.dispatchDetachedFromWindow
MainLooper
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):
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 post
is 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
Handler
adds 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#mTraversalRunnable
For 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#performTraversals
triggered by .
Why do we say synchronization barrier ? View#dispatchAttachedToWindow
The 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 postDelayed
there 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#mAttachInfo
changes.
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.post
have no problem with executing tasks (PS: I feel that this point of view is also the original source of this problem).
RecycleView
The internal child we know View
is just one more preload than the screen size View
, and exceeding this range or entering this range will cause it View
to 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);
}
}
/***部分代码省略***/
}
/***部分代码省略***/
}
If we frequently slide the list back and forth, then this will be executed Banner
continuously . This results in most of the time , which affects the execution logic sent to the main thread in the business code .dispatchAttachedToWindow
dispatchDetachedToWindow
View#mAttachInfo
null
Message
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!