在查看应用程序中的性能问题时,我发现每次按下按钮都会触发对整个onMeasure()/layout()循环的调用。我看不出有什么理由试图重新布局整个应用程序;没有添加或删除任何内容,也没有改变我所看到的大小。
当布局非常拥挤时,问题往往会发生,并且底部的一排按钮可能超出了屏幕边缘一两个像素。
有人有这方面的经验吗?是否有任何方法可以确定触发布局周期的原因?
如果屏幕上的TextFields都没有被修改,布局似乎不会被触发(请参阅在ViewGroup中查找布局请求的原因)。修改TextField是否总是触发重新布局?我能把它锁起来防止这种情况发生吗?令人沮丧的是,在屏幕上的任何地方更改任何TextField都会导致整个测量/布局循环在整个应用程序中级联;这毁了我的表演。
容器是一个自定义ViewGroup类,但我认为这不是问题所在。我看不出我能做些什么不同的事情来阻止它被召唤。
我正在考虑在我的小部件中添加一个"锁定"方法,以防止在初始布局之后发生任何进一步的布局更改。这将提高性能,但我宁愿解决根本问题。
以下是调用onMeasure()方法时的堆栈:
Gridbox.onMeasure(int,int)行:217
网格框(视图).measure(int,int)行:8171
FrameLayout(ViewGroup).measureChildWithMargins(View,int,int,整型)行:3132FrameLayout.onMeasure(int,int)行:245
FrameLayout(View).measure(int,int)line:8171
PhoneWindow$DecorView(ViewGroup).measureChildWithMargins(View,int,int,整型)行:3132
PhoneWindow$DecorView(FrameLayout).onMeasure(int,int)行:245
PhoneWindow$DecorView(View).measure(int,int)line:8171
ViewRoot.performTraversals()行:801
ViewRoot.handleMessage(消息)行:1727
ViewRoot(处理程序).dispatchMessage(消息)行:99Looper.loop()行:123ActivityThread.main(String[])行:4627
Method.invokeNative(Object,Object[],Class,Class[],Class、int,boolean)行:不可用[native Method]
方法.调用(Object,Object…)行:521
ZygoteInit$MethodAndArgsCaller.run()行:858
ZygoteInit.main(String[])行:616NativeStart.main(String[])行:不可用[本地方法]
有人有这方面的经验吗?是否有任何方法可以确定触发布局周期的原因?
有一种方法可以确定触发布局循环的确切原因
转到要调试的屏幕布局,用只覆盖一个方法的自定义容器替换最上面的容器: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(Typeface).
通过恢复程序并等待断点再次触发,您可以快速确定在特定情况下触发重新连接的所有方法。
但是,请注意,接口滞后几乎总是由多个度量值调用引起的,而不是由多个布局周期引起的
在复杂的布局(滚动视图、带权重的线性布局等)中,单个布局过程可能需要在单个视图上多次调用onMeasure,有时每个布局周期最多调用500次,甚至更多。若要检查这是否是您的问题,请覆盖其中一个底部视图(离rootView更远的视图之一;请注意,屏幕上不同视图的onLayout与onMeasure的比例不同)的onMeasure和onLayout方法。可能很难准确确定哪个视图的onLayout与onMeasure之比最差,但任何位于LinearLayout内部的视图。。。是一个很好的起点。
如果每个onLayout调用onMeasure超过16次,那么您就有问题了。尝试使视图层次结构更加平坦,删除具有weightSum属性的LinearLayouts和ScrollViews。
更改TextView(其文本)的内容将触发重新发布。然而,这只会导致对树的一部分进行测量/布局。如果这种情况只是偶尔发生(例如,当用户单击按钮时),请不要担心。
当我在布局中添加TextClock(源自TextView)时,我遇到了类似的重新布局问题:这将导致每分钟更新一次整个布局。
通过将TextClock放在自己的LinearLayout或RelativeLayout中并同时设置固定的layout_width/height和使用此layout上设置的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="x"和lines="y"fix width and height tox"width"characters andylines,当layout_width/height设置为"wrap_content"