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

Edw*_*alk 5 android android-layout

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

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

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

如果屏幕上没有任何TextFields被修改,似乎不会触发布局(请参阅在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) line:99 Looper.loop()line:123 ActivityThread.main(String [])line:4627
Method.invokeNative(Object,Object [],Class,Class [],Class,int,boolean)line:not available [native方法]
Method.invoke(Object,Object ...)行:521
ZygoteInit $ MethodAndArgsCaller.run()行:858
ZygoteInit.main(String [])行:616 NativeStart.main(String [])行:不可用[本地方法]

Ale*_*xey 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>
Run Code Online (Sandbox Code Playgroud)

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

public class RootView extends DrawerLayout {

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

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

}
Run Code Online (Sandbox Code Playgroud)

然后你需要修改你的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>
Run Code Online (Sandbox Code Playgroud)

现在,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 ...
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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


Rom*_*Guy 3

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

  • 谢谢罗曼,你的建议引导我找到了解决方案。我使用一个全局布局小部件来保存所有内容。这就是为什么框架无法阻止 onMeasure() 传递测量应用程序中的每个小部件。我通过将 TextFields 隔离在它们自己的 LinearLayout 中并将所有按钮放入不同的容器中来修复它。这使得框架将布局传递限制为仅包含文本字段的容器。这是一个巨大的性能改进。 (4认同)