26

绘制视图层次结构时出现java.lang.StackOverflowErrors

at android.view.View.draw(View.java:6880)
at android.view.ViewGroup.drawChild(ViewGroup.java:1646)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1373)
at android.view.View.draw(View.java:6883)
at android.view.ViewGroup.drawChild(ViewGroup.java:1646)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1373)
...

研究表明我的视图层次结构太深,Android 无法处理。事实上,使用Hierarchy Viewer,我可以看到我最长的嵌套是19 个视图(!)

我的应用看起来有点像 Google Play 商店应用(带有滑动标签)。每个选项卡都是片段视图分页器内的嵌套片段 - 使用 v4 支持和 HoloEverywhere。显然,这就是为什么我的等级制度变得有点疯狂的原因。

我的问题:

  1. 真正的堆栈大小限制是多少?我发现无法测量 UI 线程的堆栈大小。网上有传言说是 8KB,但有没有办法在某些样机上准确测量?

  2. 堆栈大小限制是否随操作系统版本而变化?相同的层次结构不会4.0.3 设备上崩溃,但会在 2.3.3 设备(相同硬件)上崩溃。这是为什么?

  3. 除了手动优化层次结构外,还有其他解决方案吗?我发现没有办法增加 UI 线程的小得可笑的堆栈。抱歉,60-100 堆栈帧限制是个笑话。

  4. 假设在#3 上没有奇迹解决方案,对于应该在哪里进行核心层次优化的任何建议?

  5. 疯狂的想法 - 我注意到每个视图层都添加了大约 3 个函数调用(View.draw、ViewGroup.dispatchDraw、ViewGroup.drawChild)。也许我可以制作自己的 ViewGroup 实现(自定义布局),在 draw() 期间减少堆栈上的浪费?

4

5 回答 5

27

我相信主线程的堆栈由 JVM 控制 - 在 Android 的情况下 - Dalvik JVM。如果我没记错的话,相关常数在dalvik/vm/Thread.h下面找到#define kDefaultStackSize

通过 dalvik 源历史浏览堆栈大小:

那么您可以拥有多少个嵌套视图:

无法准确的说。假设堆栈大小如上所述,这完全取决于您在最深的嵌套中有多少函数调用(以及每个函数需要多少变量等)。似乎有问题的区域是在绘制视图时,从android.view.ViewRoot.draw(). 每个视图都调用其子视图的绘制,并且它与您最深的嵌套一样深。

我会至少对出现在上述所有边界组中的设备进行经验测试。使用模拟器似乎可以提供准确的结果(尽管我只将本机 x86 模拟器与真实设备进行了比较)。

请记住,对实际小部件/布局实现方式的优化也可能会影响这一点。话虽如此,我相信大部分堆栈空间都被每个布局层次结构吃掉了,添加了大约 3 个嵌套函数调用:draw()-> dispatchDraw()->drawChild()并且这种设计从 2.3 到 4.2 并没有太大变化。

于 2013-06-04T14:16:53.833 回答
2

我会试一试,它将解决您的许多问题并帮助您进一步理解为什么 uithread 中不需要更大的堆栈。希望能帮助到你!

于 2013-06-10T07:32:04.360 回答
1

我不知道堆栈大小限制是多少,坦率地说,我认为搜索它并没有多大用处。正如您的第二个建议所暗示的那样,它很可能取决于设备上存在的 Android 版本和/或 Dalvik VM。

至于优化布局,一些选项包括:

  1. 使用 RelativeLayout 而不是嵌套 ViewGroups,尤其是 LinearLayouts 内的 LinearLayouts,以帮助扁平化视图层次结构。这不是一个万能的解决方案。事实上,嵌套RelativeLayouts 会影响性能(因为RelativeLayout 总是measure()s 两次,所以嵌套它们对测量阶段有指数影响)。

  2. 根据您的第五个问题,使用自定义视图/视图组。我听说有几个应用程序可以做到这一点,我认为甚至一些谷歌应用程序也可以做到这一点。

  3. 如果您在视图层次结构中发现任何无用的孩子,您可以尝试<merge>在您的一些布局中使用标签(但是我自己并没有发现它们有很多用途)
于 2013-05-30T18:35:13.300 回答
0

更好地解释我的疯狂想法 5. 我并不是说这是一个好主意 :) 但我想澄清如何做到这一点。

我们将为基本布局(LinearLayout、FrameLayout、RelativeLayout)创建自己的实现,并使用它们代替原始布局。

新布局将扩展原始布局,但会覆盖该draw()功能。

原始的绘图函数调用dispatchDraw()which 调用drawChild()- 只有这样你才能得到draw()你孩子的。这意味着draw()在每个嵌套中添加 3 个函数调用。我们将尝试手动将其最小化为 1 个函数调用。

这是令人作呕的部分。你熟悉inline函数吗?理论上,我们会尝试调用dispatchDraw()drawChild()内联。由于 java 并不真正支持内联,最好的方法是手动将它们内联(实际上是将代码复制粘贴到一个令人作呕的函数中)。

这在哪里变得有点复杂?我们将采用的实现将来自 Android 资源。问题是这些来源可能在 Android 版本之间略有不同。因此,必须映射这些更改并在代码中进行一组切换,以便其行为与原始代码完全相同。

似乎工作量太大,这是一个非常糟糕的黑客..

于 2013-06-09T10:53:18.407 回答
0

疯狂的想法 5 - 可能没那么疯狂。我向您解释了这个理论,然后您尝试以某种方式实现它。假设我们有 3 个嵌套视图 A > B > C。不是将 C 嵌套在 B 中,而是将其嵌套在 D 中(一些不相关的视图),当 B 将自己绘制时,他需要调用 B.draw()。当然,您可能遇到的问题是布局错误。但有可能找到解决方案。

于 2013-06-09T10:24:25.527 回答