Android布局真的是指数级难吗?

时间:2013-07-05 17:07:49

标签: android android-layout android-linearlayout

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

有人可以清楚具体地说明为什么会这样吗? 最重要的是,这是指数行为的重要组成部分 整体合同,还是仅仅是一个实施细节 那可能会被优化掉?如果认为这是不可避免的, 请举一个需要它的例子。

这样的例子对我和其他人有很大的帮助 谁抱怨“保持你的布局浅薄” 任务是繁重的,谁在想 这是否仅仅是由于 核心库中尚未优化的算法, 或者是否确实存在根本性的困难 阻止修复。

也许最小的例子就是 在另一个LinearLayout内的LinearLayout内的Button (在任何地方使用match_parent和weight = 1来触发 完全指数行为), 带有一些额外的参数或情况 明确表示所有四个电话 到Button.onMeasure()确实是有意义和必要的。

我的第一个猜测是只有两个线性时间 真的需要遍历 - 第一次收集每个人的遍历 首选尺寸,第二次遍历分配松弛 和/或收缩。 世界上其他布局引擎,如Tex和Swing 似乎能够经常处理非常深层次的层次结构 有很多对齐约束和延伸, 没有任何指数爆炸,我想这就是他们的工作方式。

请注意,我不希望答案解释如何 指数爆炸发生 - 我理解, 已经有过几个帖子 问道并回答:

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

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

布局不是一个指数级的难题, 正如Tex和Swing等世界上有效的布局引擎所证明的那样。

那么,LinearLayout发生了什么, android开发者社区应如何进行响应? 我不是本着责备的精神问, 而是要了解并决定如何最好地向前发展。

我可以想到4种可能性:

  1. 修复核心库中的性能错误,而不更改任何合同
  2. 根据需要更改合同,并修复性能错误 核心库
  3. 写一个LinearLayout的替代品,它有必要 功能(即以指定比例在子项之间分配额外空间)但没有性能错误,并将其用于新应用程序
  4. 继续微观管理我们的布局以解决问题 我们其他Android开发职业的性能错误。
  5. (4)对我个人来说不是一个严肃的选择。 此外,我似乎很清楚 此时更改LinearLayout的行为是不切实际的, 所以我不相信(2)也是一个严肃的选择。

    离开(1)和(3)。 我有能力并愿意亲自去做其中任何一个,但哪一个? 显然(1)如果可能的话,这是可取的 - 所以,有可能吗? 这似乎是需要回答的关键阻塞问题 为了确定如何前进。

    我在核心代码中花了一些时间 和文件,它并没有变得清晰, 所以这就是我在这里问这个问题的原因。

4 个答案:

答案 0 :(得分:5)

就两次测量孩子而言,我的理解是这就是LinearLayouts所发生的情况,特别是涉及权重时。我发现的最好的解释来自RomainGuy在他的一个演讲中。

他对此有一个幻灯片,并在17:45简要地说出来。尽管如此,请随意回放以获得一些背景信息。您可以在此处找到我引用的视频:Devoxx'10 - Dive Into Android

基本上他说的是,在第一遍时,他们根据LinearLayout的方向计算总宽度或高度,添加子项的权重,并找出剩余多少空间,然后在第二遍他们能够正确地将所有剩余空间分配给所有孩子。很简单。

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

我鼓励您使用DDMS并对Google的某些应用进行布局转储。它们非常复杂,而且往往非常复杂,但它们仍然可以获得良好的性能。不要对你的布局愚蠢,但如果它节省你的时间,增加一个额外的布局不是世界的尽头。

答案 1 :(得分:2)

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

  

父视图可能会对其子项多次调用measure()。例如,父母可以用未指定的维度测量每个孩子一次以找出他们想要的大小,然后如果所有孩子的无约束大小的总和太大或太小,则用实际数字再次对他们调用measure()(也就是说,如果孩子们不同意他们每人得到多少空间,那么父母就会在第二次通过时进行干预和制定规则。

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

答案 2 :(得分:2)

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

考虑这个例子:

enter image description here

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

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

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

要注意的是,为了使红色布局测量绿色布局的高度,绿色布局需要知道其子项的高度。

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

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

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

如果Google要向View添加新方法以分别衡量维度,则默认实施必须依赖onMeasure(int, int),这可能会更糟糕。但如果View.MeasureSpec编码中有任何未使用的位,则可能会添加“忽略”标记,以便更好地优化LinearLayout的未来版本。

答案 3 :(得分:0)

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