What is the android UI thread stack size limit and how to overcome it?
Asked Answered
E

5

26

I'm getting java.lang.StackOverflowErrors when my view hierarchy is being drawn:

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)
...

Research points to my view hierarchy being too deep for Android to handle. Indeed, using Hierarchy Viewer, I can see that my longest nesting is 19 views (!)

My app looks somewhat like the Google Play store app (with swipe tabs). Every tab is a nested fragment inside a fragment view pager - using v4 support and HoloEverywhere. Obviously, this is why my hierarchy has gotten a bit crazy.

My questions:

  1. What is the real stack size limit? I found no way to measure the stack size of the UI thread. Some rumors on the net say 8KB, but is there a way to measure this accurately on some sample devices?

  2. Does the stack size limit change with OS ver? The same hierarchy does not crash on an 4.0.3 device but does crash on a 2.3.3 device (identical hardware). Why is that?

  3. Is there any solution except optimizing the hierarchy manually? I found no way to increase the ridiculously small stack of the UI thread. Sorry, but 60-100 stack frame limit is a joke.

  4. Assuming there's no miracle solution on #3, any recommendations for where the core hierarchy optimization should be done?

  5. Crazy idea - I noticed that every view layer adds about 3 function calls (View.draw, ViewGroup.dispatchDraw, ViewGroup.drawChild). Maybe I can make my own ViewGroup implementation (custom layouts) that is less wasteful on stack during draw()?

Evers answered 30/5, 2013 at 18:24 Comment(7)
An idea that resembles my crazy idea #5: #6705925Evers
To clarify, the reasoning behind searching for the stack size is estimating the size of the market which is problematic. Since newer versions of the OS seem to have larger stacks, I wanted to find out where I'm prone to crashes and see if I can live with itEvers
You should really tackle that 19 level nested view hierarchy(which doesn't really justify itself). see if I can live with it and live with it how? You simply let the device crash(along with your app rating)? Gingerbread takes about 40% of the market and this combined with poor hardware will result in a significant percent of the market not meeting your demands(even worse you could be dealing with random device crashes).Baillie
Ok, optimizing 19 to less comes with a price of course. How low should I go? I was trying to get some sort of measurement so I can calculate exactly how safe I want to play. Just randomly saying max 15 levels is a bit out there for me..Evers
groups.google.com/forum/?fromgroups#!topic/android-developers/…Slither
Thanks Sam, but this will only work on a worker thread I'm creating manually. Unfortunately I'm not the one creating the UI threadEvers
The small stack size is a bigger issue for data serialization which use recursion.Dickinson
E
28

I believe that the main thread's stack is controlled by the JVM - in Android's case - Dalvik JVM. The relevant constant if I'm not mistaken is found in dalvik/vm/Thread.h under #define kDefaultStackSize

Browsing for stack sizes through the dalvik source history:

So how many nested views can you have:

Impossible to say accurately. Assuming the stack sizes are as described above, it all depends on how many function calls you have in your deepest nesting (and how many variables each function takes, etc). It seems that the problematic area is when the views are being drawn, starting with android.view.ViewRoot.draw(). Each view calls the draw of its children and it goes as deep as your deepest nesting.

I would perform empirical tests at least on the devices appearing in all the boundary groups above. It seems that using the emulator gives accurate results (although I've only compared the native x86 emulator to a real device).

Keep in mind that optimizations to how the actual widgets / layouts are implemented may also influence this. Having said that, I believe that most of the stack space is eaten by every layout hierarchy adding about 3 nested function calls: draw() -> dispatchDraw() -> drawChild() and this design hasn't changed much from 2.3 - 4.2.

Evers answered 4/6, 2013 at 14:16 Comment(2)
In Android 4.4 it's 32KB according to developer.android.com/guide/practices/verifying-apps-art.htmlHighland
In my experience, some device manufacturers change stack size. I suggest checking app if it works on smaller stack sizes. You can set stack size for emulator: medium.com/appunite-edu-collection/…Frick
D
2

I would give this and this a shot, it will solve many of your questions and help you to further understand why a bigger stack is not really needed in the uithread. Hope it helps!

Decury answered 10/6, 2013 at 7:32 Comment(0)
T
1

I don't know what the stack size limit is, and quite frankly I don't think searching that out is going to be of much use. As your second suggestion suggests, it could very possibly depend on what version of Android and/or the Dalvik VM is present on the device.

As for optimizing your layouts, some options include:

  1. Use RelativeLayout instead of nesting ViewGroups, particularly LinearLayouts inside LinearLayouts, to help flatten your view hierarchy. This is not an all-purpose solution; in fact, nesting RelativeLayouts can hinder performance (because RelativeLayout always measure()s twice, so nesting them has an exponential effect on the measure phase).

  2. Use custom Views/ViewGroups, as per your fifth question. I've heard of several apps that do this, and I think even some of the Google apps do this.

  3. If you find any useless children in your view Hierarchy, you can try using the <merge> tag in some of your layouts (I myself haven't found many uses for them however)
Trutko answered 30/5, 2013 at 18:35 Comment(1)
The reasoning behind searching for the stack size is estimating the size of the market which is problematic. Since newer versions of the OS seem to have larger stacks, I wanted to find out where I'm prone to crashes and see if I can live with itEvers
T
0

Crazy idea 5 - May be not so crazy. I explain you the theory and you try implementing it some how. Lets say we have 3 nested views A > B > C. Instead of C be nested in B make it nested in D(some unrelated view) and when B will go to draw him self he need to call B.draw(). Of-course the problems you may run in to is bad layout. But it's possible to find solutions for that.

Trishtrisha answered 9/6, 2013 at 10:24 Comment(4)
How exactly will this help? Do you expect things to stay the same when you move views around?Baillie
@Luksprog I did some thing similar once. But my view were full screen size. And I just used Canvas buffer, by redrawing the canvas source image on different views. This way I did not had to even add them.Trishtrisha
Interesting, I assume what @Babibu is suggesting is to break the nesting and put some views under another view D unrelated in the top level but having the same size as B. Then when B needs to draw himself, we can take the canvas contents of D. Did I understand correctly? This is interesting, but I wonder about events too.. need to forward them somehowEvers
Also added another explanation of my original crazy idea 5 (in a separate answer), tell me what you thinkEvers
E
0

A better explanation of my crazy idea 5. I'm not saying it's a good idea :) but I wanted to clarify how it can be done.

We will create our own implementations for the basic layouts (LinearLayout, FrameLayout, RelativeLayout) and use them instead of the originals.

The new layouts will extend the original ones but override the draw() function.

The original draw function calls dispatchDraw() which calls drawChild() - and only then you get to the draw() of your child. This means the draw() in each nesting adds 3 function calls. We will try to minimize it into 1 function call manually.

This is the disgusting part. Are you familiar with inline functions? We theoretically try and make the calls to dispatchDraw() and drawChild() inline. Since java doesn't really support inline, the best way would be to manually make them inline (actually copy-paste the code inside into a single disgusting function).

Where does this get a bit complicated? The implementation we would take would come from Android sources. The problem is that these sources may have changed slightly between Android versions. So one would have to map these changes and make a set of switches in the code in order to behave exactly like in the original.

Seems too much work and it's one hell of a hack..

Evers answered 9/6, 2013 at 10:53 Comment(2)
This isn't a solution, the hassle of getting things right doesn't justify itself.Baillie
I agree. It's too complicated and doesn't justify itself. Although it's fun to think of :) and I'm pretty sure it will completely solve the stack problem altogether (at least will let you have about twice as many views)Evers

© 2022 - 2024 — McMap. All rights reserved.