33

一些常用的android布局,如RelativeLayout和LinearLayout(当权重非零时)有onMeasure()实现,它们测量它们的孩子两次,嵌套时导致指数运行时间。这很容易通过从叶视图的 onMeasure() 发出日志条目来验证......它被称为 2深度 时间。

有人可以就为什么会这样给出一个清晰而具体的描述吗?最重要的是,这种指数行为是由于整个合约的重要组成部分,还是只是一个可能被优化掉的实现细节?如果认为这是不可避免的,请举一个需要它的例子。

这样的例子将极大地帮助我和其他抱怨“保持布局浅”的人是繁重的,并且想知道这是否仅仅是由核心库中尚未优化的算法驱动的,或者是否真的存在阻止修复的根本困难。

也许一个最小的例子包括一个在另一个 LinearLayout 中的 LinearLayout 中的 Button(到处都有 match_parent 和 weight=1,以触发完整的指数行为),以及一些额外的参数或环境清楚地表明所有四个对 Button 的调用。 onMeasure() 确实是有意义和必要的。

我的第一个猜测是,实际上只需要两次线性时间遍历——第一次遍历收集每个人的首选尺寸,第二次遍历分配松弛和/或收缩。世界上的其他布局引擎,例如 Tex 和 Swing 以及 HTML 的布局引擎,似乎能够常规处理具有大量对齐约束和拉伸的非常深的层次结构,而不会出现任何指数爆炸,我想这就是它们的工作方式。

请注意,我不想要解释指数爆炸是如何发生的答案—— 我理解这一点,并且已经有几个帖子被问及并回答了:

我的问题是递归双重测量是否从根本上是必要的/合理的,如果是这样,我想要一个清楚的解释/示例来说明原因。

编辑 2013/8/22:我想也许我的问题还没有解决。我将尝试澄清和解释我的动机,这次更大胆。

布局不是一个指数级的难题,世界上高效的布局引擎证明了这一点,例如 Tex 和 Swing 以及 HTML。

那么,LinearLayout 发生了什么,android 开发者社区应该如何应对呢?我不是本着责备的精神要求,而是要理解并决定如何最好地前进。

我能想到4种可能性:

  1. 修复核心库中的性能错误,无需更改任何合约
  2. 根据需要更改合约,并修复核心库中的性能错误
  3. 编写一个 LinearLayout 的替代方案,它具有其基本功能(即在指定比例的子项之间分配额外空间)但没有性能错误,并将其用于新应用程序
  4. 继续对我们的布局进行微观管理,以解决我们其余的 android 开发生涯中的性能错误。

(4) 对我个人来说不是一个严肃的选择。此外,我似乎很清楚,此时改变 LinearLayout 的行为是不切实际的,所以我也不认为 (2) 是一个严肃的选择。

剩下(1)和(3)。我有能力并且愿意亲自做任何一个,但是哪一个呢?显然,如果可能的话,(1)是更可取的——那么,有可能吗?这似乎是需要回答的关键阻碍问题,以确定如何前进。

我在核心代码和文档上花了一些时间,但还不清楚,所以这就是我在这里问这个问题的原因。

4

4 回答 4

5

在测量孩子两次方面,我的理解是 LinearLayouts 会发生这种情况,特别是在涉及权重时。我为此找到的最佳解释来自 RomainGuy 在他的一次演讲中。

他有一张关于此的幻灯片,并在 17:45 简要介绍了它。不过,请随意倒带以获得一些背景信息。你可以在这里找到我引用的视频:Devoxx'10 - Dive Into Android

基本上他说的是,在第一遍中,他们根据 LinearLayout 的方向计算总宽度或高度,添加孩子的权重,并找出剩余的空间,然后在第二遍中,他们使用该信息能够适当地将所有剩余空间分配给所有孩子。很简单。

我还想指出,是的,虽然浅层布局层次结构确实对性能影响较小,但如果您只添加 1 或 2 个额外层,您可能不会看到很大的性能影响为用户。一旦布置好了,就完成了。即使在 ListView 中,如果您正确使用给定的“convertView”并设置 ViewHolder,您也将获得良好的性能。

我鼓励您使用 DDMS 并对 Google 的一些应用程序进行布局转储。它们非常复杂,而且经常出人意料地深,但它们仍然可以获得良好的性能。不要对您的布局感到愚蠢,但如果它可以节省您的时间,那么添加额外的布局并不是世界末日。

于 2013-07-25T04:03:24.453 回答
2

从这里:http: //developer.android.com/guide/topics/ui/how-android-draws.html

一个父视图可以在其子视图上多次调用 measure()。例如,父母可以用未指定的尺寸测量每个孩子一次以找出他们想要多大,然后如果所有孩子的无约束尺寸的总和太大或太小,则用实际数字再次调用 measure() (也就是说,如果孩子们在他们各自获得多少空间的问题上没有达成一致,则父母将进行干预并在第二次通过时制定规则)。

他们似乎将测量过程视为父母和孩子之间的对话。换句话说,他们选择了最大的灵活性而不是优化。不过,他们似乎仍然可以优化基本布局。

于 2013-10-31T17:17:05.863 回答
2

我今晚来这里问这个。令人失望的是,似乎没有其他人能理解你的问题。在考虑了几个小时之后,我想我可能知道答案了。

考虑这个例子:

在此处输入图像描述

红色框表示LinearLayout垂直方向。绿色框代表另一个LinearLayout水平方向。我希望布局像这样进行:

1)红色将测量绿色和底部大蓝色小部件LinearLayout的高度,然后比较它们的权重,相应地划分任何剩余的垂直空间,然后再次测量孩子。LinearLayout(这里要知道的重要一点是“测量”视图实际上设置了它的大小。)

2)然后果岭LinearLayout将测量其三个孩子的宽度,比较它们的重量,划分水平空间,然后再次“测量”。

问题是,为了让红色布局测量绿色布局的高度,绿色布局需要知道其子级的高度。

现在,您会认为它LinearLayout足够聪明,可以优化许多计算。例如,在确定自己的高度时,绿色布局没有逻辑上的理由来测量其子项的宽度。如果它的孩子的身高都是fill_parent,那么它根本不需要执行任何计算。

但是 API 不允许LinearLayout这么聪明。 根本问题是没有办法只测量视图的一个维度。 您可以在测量后单独获取它们,但实际测量由View#onMeasure(int, int)完成。该方法的参数是用View.MeasureSpec编码的,没有办法编码“忽略这个维度”。所以绿LinearLayout当红色布局测量它时,愚蠢地计算其所有子级的两个维度,然后在它自己的布局时再次重复整个过程。第二次宽度不会改变,但仍然必须重新计算它们,因为再一次,没有办法告诉布局或其子级只测量一个维度。

所以回答你的问题......不,它不一定需要这样,至少对于许多常见的用例。这是 Android API 的缺陷。

如果谷歌要添加新的方法来View单独测量尺寸,默认实现将不得不依赖onMeasure(int, int),这可能会更糟糕的性能。但是,如果编码中有任何未使用的位View.MeasureSpec,则可以添加一个“忽略”标志,以便LinearLayout更好地优化未来版本。

于 2014-08-29T05:35:56.030 回答
0

我认为问题在于度量缓存。据我所见,缓存仅在中间有视图布局时才有效,因此当您在同一个“onMeasure”中对一个孩子执行两个连续测量(即使具有相同的精确测量规格)时,所有孩子和他们的子孩子再次测量。如果测量缓存工作正常,第二个测量应该更快,因为它会采用缓存的值,并且不会导致再次测量所有子层次结构。

于 2018-02-02T17:34:13.637 回答