So, I've been developing on Android since about Feb 2011. One thing I've always been acutely aware of is that Nesting Linear Layouts with weight and weight sums is 'bad for performance' and the compiler (well at least eclipse did) thew a warning fit when ever you dd so.
However, it's been my experience that doing this has no noticeable impact on the user experience, or speed when switching screens. As an experiment I created the following screen which deliberately features nothing other than a mega O.T.T. use of weights and nested linear layouts. I've used contrasting colors to show all the linear layouts used. The benefits is that it looks the same on large and small screens.
I've tried it on a Galaxy S4, Galaxy Note 10.0, Galaxy note 3 7", and very small low powered Galaxy Neo. I've tried it on Gingerbread, Cream Sandwich, Kitkat and Lollupop and do not see any perceptible speed difference between this screen an a simple one with 4 buttons on a relative layout.
So what makes this such a bad idea? What is going on under the hood that makes this a less than idea solution? Will all this change the moment I add drawables or images into the mix?
The main issue here has to do with how Android positions your views on screen. Any time that a position changes with respect to a view, then a "layout" pass cascade through the view hierarchy, touching each view and performing some calculations (similar for changing "sizes" kicks off a "measure" pass).
Some layout containers, like RelativeLayout, have to force a 2nd "layout" pass to kick off; Once all the views have calculated their desired position, ReltativeLayout needs to then re-position everything again based on it's layout properties.
This causes a Double Layout Taxation trough the view hierarchy; which isn't free.
Now, if you have a shallow hierarchy, the overhead of this action is insignificant. Where this starts to run into a problem is when you've got a deep hierarchy, with multiple double-layout containers in it. For example, having a RelativeLayout at the root, which contains LinearLayouts that have "measure with largest child" set on it. The result is that the leaf-node views will have "layout" called on them ~8x times.
The performance impact of this is directly correlated to how complex your layout/measure passes are in your scene. If nothing is problematic, maybe a 12x-per-view layout pass isn't a big deal on high-end devices. However if one of the leaf nodes has a complex layout pass, well, then you're just wasting cycles.
The main topic here is to profile for things that are a problem, but keep in mind that setting things up the wrong way is just wasting performance that could be used elsewhere.
So what makes this such a bad idea?
It's not a bad idea. It is not free, either.
In particular, your scenario is fairly simple, despite your protestations to the contrary. While your layout is rather complex, it is rendered once. Hence, while you may drop a few frames while rendering it, that price is paid one time (or, more accurately, one time per activity/fragment that is using this layout).
Where per-layout expense becomes a much bigger problem is when it is magnified by having several of them that get animated around, such as rows in a ListView/RecyclerView, or pages in a ViewPager. Now, we are going through lots and lots of rendering passes, as the user swipes and causes us to redraw our content. Each individual row in the list might be significantly simpler than your layout, but we're also going to be trying to draw those many times per second. And, while a couple of dropped frames may not be noticeable in your case, dropped frames during animation is pretty much the definition of "jank" for an Android UI.
So long as you have no jank -- IOW, you are not dropping any frames, as reported by Choreographer in LogCat or as seen in the gfxinfo overlay through Developer Options -- then your layout is fine, at least as far as I and probably most users are concerned. Conversely, if you are dropping frames, trying to figure out more efficient layouts, perhaps even custom ViewGroups rather than general-purpose ones, may help alleviate that jank.
Related
I am building an application using Android Studio for an android tablet. I am fairly new, have never published an application. I have several parts I use on more then one page so I implemented them as fragments. On the page I am working on uses 4 fragments and when it renders, I get the message, Skipped 36 frames. If I change the fragment body from the page xml file to a constraints layout the messages go away. I've cut each of the fragments down to bare bones to get then number of skipped frames down to 36. If I put the components back in, the skipped frames go to 96.
Is there an alternative to fragments to share code within android?
I have run systrace for the loading of the page and the measure took 1441.6ms while the layout took 8.31ms.
There are other problems of course, but sorting out how to get this down to something reasonable would be good.
Also, Could this just be that the tablet is slow? Is there a place to see benchmarks on specific devices? Then I could maybe just use a different device.
Edit
If I flatten the xml, replace the fragment and put the xml for the fragment inline using a surrounding <ConstraintLayout it is slightly worse?
Edit II
I have worked on this issue all morning. I went back to the fragment implementation, then I went through the process of removing some ConstraintLayout sections. I used ConstraintLayout to delineate areas on the display. So I figured I could increase performance if I could remove some of them. Well, not so fast, sometimes when I removed the ConstraintLayout it actually made the application skip more frames.
Then I did an experiment on where I used margins and borders. It turns out if you use margins on a ConstraintLayout is faster, depending on the content of the Constraintlayout, then adding padding to each of the elements.
Also, a bigger cost is asking the ConstraintLayout to fill all available space using height/width="0dp" All of these attributes are part of the measure phase so they impact the frames skipped.
My consensus is that the tablet doesn't have enough power to write an application that makes any good use of the screen. Think of the tablet as the screen real estate of 4 phones. If the cpu is not way bigger it will not be able to manage that size of a display, unless all you want to do is draw images on it.
When displaying a screen with various sections ordered vertically, where each section looks different than the other (so maybe recycling items wouldn't use an actual recycle but a recreation), would be any true benefit to use a recycling view group, taking into consideration that no large bitmaps will be displayed, instead of an plain ScrollView?
I suspect there is a performance impact when using ListView for example, that might affect scrolling, and an slight increased memory usage, maybe a delay (only when layout is measured - not that often) when using ScrollView, but are these that significant for 7 sections, for example, where 3 of them are visible at a time?
Thank you!
This is mostly speculation, but I would consider it relatively well-informed speculation.
Let's assume you have 1000 sections, each of them different from the other. My understanding is that both ScrollView and ListView will have "problems" here, though the sort of problems they'll each have will be different.
ScrollView will measure and lay out all of its children up front (which will probably be quite expensive and will probably cause quite a delay in your UI). And it will have all of those views inflated and hanging around in memory (which might cause your process to crash with an OutOfMemoryError). But if the delay were acceptable and if you had enough memory for everything, at this point your app should run perfectly smoothly (i.e. no frames dropped when scrolling/flinging).
ListView, on the other hand, will only measure and lay out those children that are currently on-screen, as well as a few extras that are immediately off-screen. So initial performance should be quite fast and memory consumption should be quite low. But you mention that each section is "different", so view recycling won't "work". In practice, this would mean ignoring the convertView parameter of getView() and inflating a new view each time. If your sections are complicated, this could easily cause frame skips during scrolling/flinging.
But you mention that your app will have only 7 sections, or about 2.5 screen's worth of UI. For such a small number, I think worrying about performance before simply trying something out is silly; I suspect that a ScrollView holding a LinearLayout holding all seven sections would work perfectly well on all modern devices.
I have an XML file with about 150 views. Yes, I know it is a lot and I did get a message from Android Studio saying I can't exceed 80 views. But I can't drop views any lower than 150. I considered using list view but it works the way I wanted it to.
The question is, will this many views make the app crash/slow the device? I've tried it on my s7 and it works perfectly fine. My lowest API is 17 which is 4.2. Wouldn't 4.2 devices be able to handle this XML without any problem?
Thanks.
The problem with having an excessively large number of Views is that Android often needs to measure, layout, and draw them, and it will traverse the entire View hierarchy to do this. If the number of Views is so large that this traversal takes more time than the screen refresh rate, you will skip frames and your UI might appear to lag or be choppy.
If not all of those Views need to be on screen at once (for example, if you are using a ScrollView to hold a very large container that the user can scroll through), then you should probably switch to using RecyclerView.
If all of those views need to be on screen at once, then you might consider writing custom Views that can display your content all at once instead of having individual Views that draw individual things. This can drastically reduce the time and complexity of the measure/layout/draw traversals.
It's difficult to suggest an approach without knowing more specifics about your UI, but hopefully that explains the issue.
Some commonly used android layouts such as
RelativeLayout and LinearLayout (when weights are nonzero)
have onMeasure() implementations that measure their children
twice, resulting in exponential running time when nested.
This is easily verifiable by emitting Log entries
from a leaf View's onMeasure()... it gets called 2depth
times.
Can someone give a clear and specific description as to why this is?
Most importantly, is this exponential behavior due to an important part
of the overall contract, or is it just an implementation detail
that might be optimized away? If it is believed to be unavoidable,
please give an example that requires it.
Such an example would greatly help me and others
who are grumbling that the "keep your layouts shallow"
mandate is burdensome and who are wondering
whether this is being driven simply by
not-yet-optimal algorithms in the core libraries,
or whether there really is a fundamental difficulty
blocking a fix.
Perhaps a minimal example would consist
of a Button inside a LinearLayout inside another LinearLayout
(with match_parent and weight=1 everywhere, to trigger
the full exponential behavior),
with some additional parameters or circumstances
making it clear that all four of the calls
to Button.onMeasure() are indeed meaningful and necessary.
My first guess would be that only two linear-time
traversals are really needed-- the first traversal to gather everyone's
preferred sizes, the second traversal to distribute slack
and/or shrinkage.
Other layout engines in the world, such as those for Tex and Swing and HTML,
seem to be able to routinely handle very deep hierarchies
having lots of alignment constraints and stretches,
without any exponential blowup, and I imagine that's how they work.
Please note, I don't want answers explaining how
the exponential blow-up occurs-- I understand that,
and there have been several posts already where that has been
asked and answered:
Why are nested weights bad for performance? Alternatives?
Android layout measuring time doubles with each step up the hierarchy
Layout Weight warning Nested weight bad performance
Efficiency of Android Layout hierarchy
http://android-developers.blogspot.com/2009/02/android-layout-tricks-1.html
My question is whether the recursive double-measuring is
fundamentally necessary/justified,
and if so, I'd like a clear explanation/example showing why.
Edit 2013/8/22: I think maybe my question is still not getting across.
I'll try to clarify and explain my motivation, more boldly this time.
Layout is not an exponentially hard problem,
as evidenced by efficient layout engines in the world, such as those for Tex and Swing and HTML.
So, what happened with LinearLayout,
and how should the android developer community proceed in response?
I am asking not in the spirit of laying blame,
but rather to understand and decide how to best move forward.
I can think of 4 possibilities:
Fix the performance bug in the core library, without changing any contracts
Change contracts as needed, and fix the performance bug in the
core library
Write an alternative to LinearLayout, that has its essential
features (namely distributing extra space among children in specified proportions) but without the performance bug, and use it for new apps
Continue to micromanage our layouts to work around the
performance bug for the rest of our android development careers.
(4) isn't a serious option for me personally.
Furthermore it seems clear to me that
changing the behavior of LinearLayout at this point is impractical,
so I don't believe (2) is a serious option either.
That leaves (1) and (3).
I'm capable and willing to do either of those personally, but which one?
Obviously (1) is far preferable if it's possible-- so, is it possible?
That seems to be the crucial blocking question that needs to be answered
in order to determine how to move forward.
I have spent some time in the core code
and the doc and it's not becoming clear,
so that is why I'm asking the question here.
In terms of measuring children twice, it's my understanding that this is what happens with LinearLayouts particularly when weights are involved. The best explanation I've found for this comes from RomainGuy in one of his presentations.
He has a slide about this and briefly speaks to it at 17:45. Feel free to rewind to get a bit of context though. You can find the video I'm referencing here: Devoxx'10 - Dive Into Android
Basically what he says is that on the first pass they calculate the total width or height depending on orientation of the LinearLayout, add the weights of the children, and find out how much space is left over, then on the second pass with that information they are able to properly divvy out all the remaining space to all the children. Simple enough.
I'd also like to point out though that yes, while it's very true that shallow layout hierarchies have less of a performance hit, if you are adding just 1 or 2 extra layers, you probably aren't going to see a big performance impact for the user. Once it's laid out, it's done. Even in ListView's if you properly use the given "convertView", and set up ViewHolder's, you're going to get good performance.
I'd encourage you to use DDMS and do a layout dump of some of Google's apps. They are very complex, and often times surprisingly deep, but they still get good performance. Don't be stupid with your layouts, but if it saves you time, adding an extra layout isn't the end of the world.
From here: http://developer.android.com/guide/topics/ui/how-android-draws.html
A parent View may call measure() more than once on its children. For example, the parent may measure each child once with unspecified dimensions to find out how big they want to be, then call measure() on them again with actual numbers if the sum of all the children's unconstrained sizes is too big or too small (that is, if the children don't agree among themselves as to how much space they each get, the parent will intervene and set the rules on the second pass).
It seems they view the measurement pass as a dialog between parent and children. In other words they opted for maximum flexibility instead of optimization. It still seems like they could optimize the base layouts though.
I came here tonight to ask this. It's disappointing that nobody else even seems to understand your question. After thinking about it for a couple more hours, I think I might know the answer.
Consider this example:
The red box represents a LinearLayout with vertical orientation. The green box represents another LinearLayout with horizontal orientation. I would expect the layout to proceed like this:
1) The red LinearLayout would measure the heights of green LinearLayout and the large blue widget on the bottom, then compare their weights, divide any leftover vertical space accordingly, and measure the children again. (The important thing to know here is that "measuring" a view actually sets its size.)
2) The green LinearLayout would then measure the widths of its three children, compare their weights, divide the horizontal space, and "measure" again.
The catch is that in order for the red layout to measure the height of the green layout, the green layout needs to know the height of its children.
Now, you would think that LinearLayout would be smart enough to optimize away many of its calculations. For example, there is no logical reason for the green layout to measure the width of its children when determining its own height. And if its children all have a height of fill_parent, then it shouldn't need to perform any calculations at all.
But the API doesn't allow LinearLayout to be that smart. The fundamental problem is that there is no way to measure only one dimension of a view. You can get them separately after they've been measured, but the actual measurement is done by View#onMeasure(int, int). The arguments to that method are encoded with View.MeasureSpec, and there's no way to encode "ignore this dimension." So the green LinearLayout stupidly calculates both dimensions of all its children when the red layout measures it, then repeats the entire process again when it does its own layout. The widths will not have changed the second time around, but they must still be recalculated because, again, there's no way to tell the layout or its children to measure only one dimension.
So to answer your question... no, it doesn't necessarily need to be this way, at least for many common use cases. This is a deficiency of the Android API.
If Google were to add new methods to View to measure dimensions separately, the default implementation would have to rely on onMeasure(int, int), which could be even worse for performance. But if there are any unused bits in the View.MeasureSpec encoding, then it might be possible to add an "ignore" flag that would allow future versions of LinearLayout to be better optimized.
The problem in my opinion is the measure cache. As far as I could see the cache only works if there was a layout of the view in the middle, so when you perform two consecutives measures of a child (even with the same exact measure specs) in the same "onMeasure" all the children and their sub-children are measured again. If the measure cache worked fine, the second measure should be much faster as it would take the cached values and it wouldn't cause all the children hierarchy to be measured again.
I want to figure out the main effectivity issues of the Android layouts and views. I'm doing a research now but maybe somebody has answers already.
I have a RelativeLayout that is populated with views dynamically. Basically, the application loads a forum thread in XML and then renders the discussion tree so each message is displayed in its own group of views. I've decided not to use WebView because I want to implement some client-side functions that should be easier to do on custom views than on HTML page.
However this approach has a major flaw: it's heavy.
The first problem (which I've solved) was nesting of views. This is not an issue now, my layout is almost flat so the maximum depth is 10-12 (counting from the very top PhoneWindow$DecorView, the actual depth depends on data).
Now I've reached the next limit that is somehow connected to (or caused by) resource consumption of the views. After loading the data, the application hangs for a while to build the layout (creates the views and populates them with the data), and the hang time seems to grow linear with the data size; and if the data is large enough, the view will never appear (eventually the system suggests closing the application because it isn't responding).
Now the questions:
Does memory consumption depend significantly on the view class? In other words, is there any major difference between a Button and a TextView, or an ImageView? I can attach a click handler to any view so they don't differ much in usage.
Do background images affect the performance? If the same image is set in N views, will it make the layout N times heavier? (I understand that this question may look silly but anyway.)
Are nine-patch images significantly heavier than regular ones? What is better: to create N views where each has some background images, or to make one view that is N times wider and has a repeating background?
Given some layouts, what should be optimized first: overall number of views, nesting levels, or something else?
The most interesting. Is that possible to measure or at least estimate the resources consumed by the activity and its views? If I make some change, how could I see that I'm going the right way?
UPDATE
Thanks to User117, some questions that I asked above are now answered. I've used the Hierarchy Viewer and optimized my layout: compared to what I had before, the overall number of views is now reduced almost twice, and the nesting is also reduced.
However the application still hangs on a large forum thread.
UPDATE 2
I've connected the debugger to my device and found that the application gets out of memory.
But what is very unexpected for me is that the error occurs after I populate the layout. The sequence is as follows:
All my views are added. I can see a slight slow down as they are being added.
Almost nothing happens for a couple of seconds. During that time, a few info messages are spawned in the log, they are identical: [global] Loaded time zone names for en_US in XXXXms, the only difference is number of milliseconds.
The out of memory error message is spawned: [dalvikvm-heap] Out of memory on a N-byte allocation (the actual size varies). The long error reporting starts.
What does this mean? Looks like the rendering have its own demands that may be considerable.
UPDATE 3
At last I've found the core issue. Here is a screenshot from my application, see an explanation below the image.
Each message consists of a round button that shows or hides the replies and a red content frame to the right of the button. This is very simple and requires only 6 views including the layouts.
The problem is the indentation with these connection lines that show which message is related to which.
In my current implementation, the indentation is built of small ImageView's, each containing a square image that shows either empty space, or a vertical line, or a T-like connector, or a L-like corner. All these views are aligned to each other within the large RelativeLayout that holds the whole discussion tree.
This works fine for small and medium trees (up to few hundreds of messages), but when I try to load a large tree (2K+ messages), I get the result explained in UPDATE 2 above.
Obviously, I have two problems here. I spawn large number of views that all consume memory, and these views are ImageView's that require more memory for rendering because they render a bitmap and therefore create graphics caches (according to explanation given by User117 in the comments).
I tried disabling loading the images into the indentation views but got no effect. It seems like adding that huge number of views is quite enough to eat all available memory.
My other idea was to create an indentation image for each message that would contain all pipes and corners, so each message would have the only indentation view instead of 10 or 20. But this is even more consuming: I've got out of memory in the middle of populating the layout. (I cached the images in a map so two bitmaps with identical sequence of images weren't created, that didn't help.)
So I'm coming to conclusion that I'm in a dead end. Is it ever possible to draw all these lines at once?
Different View's are different kinds of Object. Some only draw() light weight stuff, some can hold large Bitmap Objects, and handler Objects and so on. So, yes different View's will consume different amount of RAM.
If same Bitmap object is shared among views, There's only one Object in RAM, each View will have a reference variable pointing to that object. But, not so when View draws: Drawing same Bitmap n times at n places on screen will consume n times CPU and generate n different bitmap_cache for each View.
Each side of a 9-patch image is actually bigger by 2 pixels from the original image. They are not much different as a file. When they are drawn, both can be scaled and will take almost equal space. The only difference is that 9-Patch are scaled differently.
Setting the background of the larger, parent view is better when the child views are transparent, and background will show through.
You can save a small image and set a tiled background so that it can fill a large area.
Nesting is to be optimized first, because all of the views might not be visible at a given time, let's say only a few views are visible in scrolling layout. Then you can cut down on total number of views used. Take cues from ListView: Given that user will be only seeing a sub set of total data at a time, it re-cycles the views. and saves a lot of resources.
SDK provides Hierarchy Viewer tool for this purpose. It shows a full tree structure of a running Layout, also places red flags for the sluggish parts of the layout.
A good layout is that which:
Easy to be measured (avoid complex weighted widths, heights and alignments). For
example instead of doing layout_gravity="left" for each each child, parent can have gravity="left".
Has less depth, each overlapping view adds another layer to be composited while screen is drawn. Each nested View Structure, asks for a chained layout call.
Is smart and re-cycles Views rather than create all of them.
Reuses its parts: tags like <merge> and <include>.
Update:
Answers to this question, show many approaches for a tree view in android.