TextField更改会触发完整的布局循环

时间:2011-06-28 16:36:16

标签: android android-layout

在查看我的应用程序中的性能问题时,我发现每次按下按钮都会触发对完整onMeasure()/ layout()循环的调用。没有理由我可以尝试重新布置整个应用程序;没有添加或删除任何内容,并且没有任何内容改变我可以看到的大小。

当布局非常拥挤时,问题往往会发生,并且底部的按钮行可能超出屏幕边缘一两个像素。

有没有人有这方面的经验?有没有办法确定为什么触发了布局周期?

如果屏幕上没有任何TextFields被修改,似乎不会触发布局(参见Finding the cause of a layout request in a ViewGroup)。修改TextField是否总是触发重新布局?我可以以某种方式锁定它以防止这种情况吗?令人沮丧的是,认为在屏幕上的任何地方更改任何TextField都会导致整个测量/布局周期级联整个应用程序;这是在扼杀我的表现。

容器是一个自定义ViewGroup类,但我不认为这是问题所在。我不知道我可以采取哪些不同的方式来阻止它被召唤。

我正在考虑在我的小部件中添加“锁定”方法,以防止在初始布局后进行任何进一步的布局更改。这将提高性能,但我宁愿解决潜在的问题。

这是我调用onMeasure()方法时的堆栈:

Gridbox.onMeasure(int,int)行:217
Gridbox(View).measure(int,int)行:8171
FrameLayout(ViewGroup).measureChildWithMargins(View,int,int,int,int)行:3132 FrameLayout.onMeasure(int,int)行:245
FrameLayout(View).measure(int,int)行:8171
PhoneWindow $ DecorView(ViewGroup).measureChildWithMargins(View,int,int,int,int)行:3132
PhoneWindow $ DecorView(FrameLayout).onMeasure(int,int)行:245
PhoneWindow $ DecorView(View).measure(int,int)行:8171
ViewRoot.performTraversals()行:801
ViewRoot.handleMessage(消息)行:1727
ViewRoot(Handler).dispatchMessage(Message)行:99 Looper.loop()行:123 ActivityThread.main(String [])行:4627
Method.invokeNative(Object,Object [],Class,Class [],Class,int,boolean)line:not available [native method]
Method.invoke(Object,Object ...)行:521
ZygoteInit $ MethodAndArgsCaller.run()行:858
ZygoteInit.main(String [])行:616 NativeStart.main(String [])行:不可用[本机方法]

3 个答案:

答案 0 :(得分:5)

  

有没有人有这方面的经验?有没有办法确定触发布局周期的原因?

有一种方法可以确定触发布局周期的确切原因 转到要调试的屏幕布局,并使用仅覆盖一个方法的自定义容器替换最顶层的容器:requestLayout()。

因此,例如,如果您的布局如下所示:

<?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>

您首先需要创建一个新的自定义类,它是容器类的子类:

public class RootView extends DrawerLayout {

    public RootView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public void requestLayout() {
        super.requestLayout();
    }

}

然后你需要修改你的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>

现在,android的工作方式是当任何一个视图组收到布局请求时,它还会调用它的直接父级的requestLayout()。这意味着无论何时在屏幕内进行任何requestLayout()调用,您的RootView的requestLayout()也将被调用。

因此,启动调试会话,在RootView.requestLayout()方法中放置一个断点,并执行您认为导致布局循环的操作。查看堆栈跟踪。它总是看起来像这样:

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 ...

第一个不是requestLayout()的方法是导致布局周期发生的原因 在上面的示例中,它是TextViewPlus.setTypeface(字体)。

通过恢复程序并等待断点再次触发,您可以快速确定在特定情况下触发重新布局的所有方法。

但请注意,界面滞后几乎总是由多次测量调用引起,而不是多个布局周期!

在复杂的布局(滚动视图,带权重的线性布局等)中,单个布局传递可能需要在单个视图上多次调用onMeasure,有时每个布局周期多达500次。要检查这是否是您的问题,请覆盖其中一个底部视图的onMeasure和onLayout方法(其中一个视图离rootView更远;请注意onLayout to onMeasure ratio对于您屏幕上的不同视图会有所不同) 。可能很难确切地确定哪个视图具有最差的onLayout to onMeasure比率,但是在LinearLayout内的LinearLayout内的LinearLayout内的LinearLayout内部的任何视图都是一个好的起点。

如果onMeasure每次onLayout被调用超过16次,那么你就有问题了。尝试使视图层次结构更加平坦,使用weigthSum属性和ScrollViews删除LinearLayouts。

答案 1 :(得分:3)

更改TextView(其文本)的内容将触发重新布局。但是,这只会导致树的一部分的度量/布局。如果这种情况不时发生(例如当用户点击按钮时),请不要担心。

答案 2 :(得分:1)

根据爱德华的评论,他设法通过隔离自己的LinearLayout中的TextView来阻止整个布局树的重新布局。这对我来说不起作用。我已经在下面列出了对于那些有同样问题的人有用的东西。

当我将TextClock(从TextView派生)添加到我的布局时,我遇到了类似的重新布局问题:这将导致每分钟更新整个布局。

通过将TextClock置于其自身的LinearLayout或RelativeLayout中并使用固定的 layout_width / height 并使用此布局上设置的 layout_alignParent 进行固定定位来隔离TextClock并没有改变每分钟完全重新布局。 简单地使TextClock的大小固定,即与其实际内容无关,有什么帮助:

    <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"
    />

ems =&#34; x&#34; lines =&#34; y&#34; 将宽度和高度固定为 x &#34;最宽的&#34;当 layout_width / height 设置为&#34; wrap_content&#34; 时,字符和 y 行。当然你也可以在dp(或sp,我会假设)中使用固定尺寸。