Issue
I've run into this situation two or three times, where I have some code that looks like this:
ViewGroup.LayoutParams params = myView.getLayoutParams();
params.height = 240;
myView.requestLayout();
Or alternatively:
ViewGroup.LayoutParams params = myView.getLayoutParams();
params.height = 240;
myView.setLayoutParams(params);
And the view size never changes. I've tried the following, with no luck:
- Calling
forceLayout
, which ironically seems less forceful thanrequestLayout
, because it doesn't notify the View's parents that a layout is needed. - Overriding
onMeasure()
to see if it is ever called (it isn't). - Making sure that I'm not making the call to
requestLayout
during a Layout pass.
I've also tried calling requestLayout
on all of the View's parents, like so:
ViewParent parent = myView.getParent();
while (parent != null) {
parent.requestLayout();
parent = parent.getParent();
}
This works, but it seems really hacky. I'd much rather find the real solution.
What am I doing wrong?
Solution
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.
Answered By - fpsulli3
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.