【问题标题】:Is Android layout really exponentially hard?Android 布局真的是指数级的困难吗?
【发布时间】:2013-07-03 20:21:25
【问题描述】:

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

谁能给出一个清晰而具体的描述为什么会这样? 最重要的是,这种指数行为是否归因于重要部分 整体合同,还是只是一个实施细节 那可能会被优化掉?如果认为这是不可避免的, 请举一个需要它的例子。

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

也许一个最小的例子包括 在另一个 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)是更可取的——那么,有可能吗? 这似乎是需要回答的关键阻塞问题 以便确定如何前进。

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

【问题讨论】:

  • +1 非常清晰的问题,研究表明您没有问什么。
  • 很棒的帖子。如果可以的话,直接将其发送给 Romain Guy,以便有更好的机会获得有价值的反馈。 :)

标签: android android-layout android-linearlayout


【解决方案1】:

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

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

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

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

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

【讨论】:

  • spierce7,感谢您提供视频链接和解决方法提示。该视频很好地解释了算法的作用,但没有回答我的问题。我已经编辑了我的问题,希望能澄清我在问什么以及为什么。
【解决方案2】:

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

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

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

【讨论】:

    【解决方案3】:

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

    考虑这个例子:

    红色框代表垂直方向的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 的未来版本。

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-07-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-07-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多