While looking at performance problems in my app, I discovered that each button press was triggering a call to the full onMeasure()/layout() cycle. There\'s no reason that I can
Changing the content of a TextView (its text) will trigger a relayout. This will however only cause a measure/layout of part of the tree. If this happens only from time to time (when the user clicks a button for instance), don't worry about it.
Does anybody have any experience with this? Is there any way to determine why a layout cycle was triggered?
There is a way to determine exactly why the layout cycle is triggered.
Go to the layout of the screen you want to debug and replace the topmost container with a custom container that overrides just one method: requestLayout().
So, for instance, if you layout looks like this:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- omitted for brevity -->
</android.support.v4.widget.DrawerLayout>
You'll first need to create a new custom class that's a subclass of your container class:
public class RootView extends DrawerLayout {
public RootView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void requestLayout() {
super.requestLayout();
}
}
And then you'll need to modify your xml:
<?xml version="1.0" encoding="utf-8"?>
<com.example.RootView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- omitted for brevity -->
</com.example.RootView>
Now, the way android works is that when any viewgroup recieves layout request, it'll also call requestLayout() of it's direct parent. Which means that whenever any requestLayout() call is made inside your screen, your RootView's requestLayout() will also be called.
So start a debugging session, place a breakpoint inside the RootView.requestLayout() method and perform an action that you think is causing the layout cycle. Look at the stack trace. It'll always look like this:
RootView.requestLayout() line: 15
RelativeLayout(View).requestLayout() line: 17364
RelativeLayout.requestLayout() line: 360
FrameLayout(View).requestLayout() line: 17364
... a dozen of other calls to requestLayout() ...
TimeCell(View).requestLayout() line: 17364
TextViewPlus(View).requestLayout() line: 17364
TextViewPlus(TextView).setTypeface(Typeface) line: 2713
... more methods ...
The first method that is not requestLayout() is what is causing the layout cycle to happen.
In the example above, it is TextViewPlus.setTypeface(Typeface).
By resuming the program and waiting until the breakpoint triggers again you can quickly determine all the methods that trigger relayout in your particular case.
However, please note that interface lags are almost always caused by multiple measure calls, not by a multiple layout cycles!
In a complicated layout (scroll views, linear layouts with weights, etc.) single layout pass can require onMeasure to be called multiple times on a single view, sometimes up to 500 times per layout cycle and more. To check if this is your problem, override onMeasure and onLayout methods of one of the bottom views (one of the views that's further away from rootView; note that onLayout to onMeasure ratio will be different for different views on you screen). It might be hard to determine exactly which view has the worst onLayout to onMeasure ratio, but any views that's inside LinearLayout inside LinearLayout inside LinearLayout inside LinearLayout... is a good place to start.
If onMeasure is called more than 16 times per onLayout then you have a problem. Try making your view hierarchy more flat, remove LinearLayouts with weigthSum attribute and ScrollViews.
According to Edward's comment he managed to prevent re-layout of the whole layout tree by isolating the TextViews in their own LinearLayout. This did NOT work for me. I have lined out below what worked for me for anyone having the same problem.
I encountered a similar re-layout issue when I added a TextClock (which is derived from TextView) to my Layout: this would cause an update of the whole layout every minute on the minute.
Isolating the TextClock by putting it in its own LinearLayout or RelativeLayout with both fixed layout_width/height and fixed positioning using layout_alignParent set on this Layout didn't change the complete re-layout every minute. What did help was to simply make the size of the TextClock fixed i.e. independent from its actual contents:
<TextClock
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:ems="4"
android:lines="1"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
/>
Where ems="x" and lines="y" fix width and height to x "widest" characters and y lines, when layout_width/height are set to "wrap_content". Of course you could use fixed dimensions in dp (or sp, I would assume) too.