If you are observing this behavior, it's likely that some view in your hierarchy has requested layout on a background thread, which is a no-no.
In order to find the offending view, step into your call to requestLayout
and look for a variable called mAttachInfo.mViewRequestingLayout
. That reference will likely point to the view that made the bad call. You can then search your code for any places where requestLayout
or setLayoutParams
is being called on this view.
If you find and fix any cases where these calls are being made from a background thread, this will likely fix the problem.
Background / Explanation
Suppose you have a view hierarchy that looks something like this:
Then, suppose that NaughtyView requests a layout on a background thread.
In order for NaughtyView to actually get measured during the next layout pass, it needs to notify its parents, recursively, until the view root is notified:
Note that each view sets a flag on itself, indicating that it has requested layout. Also, note that the view root has ignored the request. But why?
There is a specific situation where the view root may ignore the layout request, and it's a bit subtle. To see that situation, let's look at the implementation of ViewRootImpl.requestLayout
:
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
A brief aside on how layout requests are handled. You're not supposed to call requestLayout
while a layout pass is in progress. If you do so anyway, the view root will try to accommodate your request by laying out your view in a second pass. If the mHandlingLayoutInLayoutRequest
member variable is true, it means that the view root is currently running that second pass. During that phase, the view root ignores incoming layout requests, and so mLayoutRequested
will not be set to true.
Assuming all calls to requestLayout
are happening on the ui thread like they're supposed to, this behavior is harmless (in fact, desired).
However, if you make a call to requestLayout
on a background thread, it can mess up the sequence of events and leave your view hierarchy in the state resembling the diagram above, with a string of ancestors who have their "layout requested" flags set, but a view root who is not aware of it.
So next, suppose that someone calls requestLayout
on NiceView. This time, the call is made properly on the ui thread:
As usual, NiceView tries to notify all of its ancestors. However, once this notification reaches NiceView's common ancestor with NaughtyView, the traversal is blocked. This happens because each view only notifies its parent if its parent hasn't already been notified (as indicated by the aforementioned flag).
Since the traversal never reaches the view root, the layout never happens.
This issue can spontaneously fix itself if some of other view in the hierarchy — any view that does not share an ancestor with NaughtyView other than the view root — happens to request a layout (properly, on the ui thread). When this happens, the view root's own "layout requested" flag will be set to true, and it will finally perform a layout on all of the views that have requested it (including NaughtyView and NiceView). However, for some user interfaces, you can't count on this happening before the user notices.