Search code examples
androidandroid-layouttextviewandroid-viewpageronmeasure

onMeasure is called 1,500+ times


I've recently noticed a significant performance issue with my app. I essentially have the following layout... enter image description here

DESCRIPTION
In short, there is a ViewPager that holds 3 RelativeLayouts as pages. Inside each page there a number of TextViews. I recently noticed a lot of lag when I type into the EditText that has a TextWatcher on it to perform a quick SQL query for autocompleting. My HTC One M8 stutters and lags as I input text, but I know it isn't the querying that is slow because I measured queries taking only about 7 ms.

I used method profiling, Systrace, and plain old Log debugging and concluded that each time I typed a character in the EditText about 1,700 calls to onMeasure were being made on the TextViews within RelativeLayout A, B, and C. Cumulatively, there are only about 15 independent TextViews across the pages. I noticed that each onMeasure is being called hundreds of times instead of just once or twice like it usually might.

QUESTION
I don't know why typing in the EditText in page B would cause other TextViews in pages A and C to also get "re-measured". More importantly, does anyone have insights on why onMeasure is being called so often? And, does anyone know of a solution to significantly reduce the number of calls to onMeasure?

DETAILS
This may help: I was actually able to cut the the number of onMeasure calls to about 900, by removing RelativeLayout 2 which could suggest the propagation of onMeasure calls starts outside of the ViewPager.


Solution

  • While I don't have enough information to identify the specific problem, I can tell you, in general, what is happening:

    A call to a root view's measure causes a call to each sub-view's measure and so on, down the tree.

    Suppose that the view called "current view", after asking each of its sub views to measure themselves, decides that there is not enough space for all of the children. It will ask them each to measure themselves again, suggesting a max size. Suppose, then, that the view called "Relative Layout B" changes its size, radically, based on the specified max. Because the change is so big, "current view" must asks each child one more time. This loop could easily require a couple more iterations to resolve.

    When "current view" decides on its size, it reports back to "View Pager". Suppose that "View Pager" does not have enough space for its three views, and has to ask them to re-measure, with a max. We are now up to at least 20 calls to the TextView's onMeasure. If each of the other views above ViewPager iterates a few times, pretty soon you start seeing big numbers.

    The best solution, as you've already pointed out, is to reduce the depth of that tree. Alternatively, you might use layout managers that have much simpler measurement algorithms (FrameLayout, LinearLayout)

    1500 is a lot though. One of those views is doing something weird. You should be able to identify it very simply, using the TreeView tool in the DDMS suite. In eclipse it is called "TreeView". In Studio, click the Android icon and switch to the Hierarchy View perspective. You can also run it from the command line, from the SDK tools directory.

    Added: If you connect Hierarchy Viewer to your app while it is running, you will see the entire view tree in one of its panes. Each node in the tree will have three colored gumdrops towards the bottom. The first gumdrop represents the measurement phase. If the gumdrop is green, the measurement phase for that view is fast. If it is yellow, that node is in the top 50 percentile of nodes in the layout. If it is red, it is the slowest in the layout.

    You should be able to follow a trail of yellow right to the offending view.