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.
Related
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.
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.
My app needs a display with repeating pattern. Think a chess board where pieces can move within a square but not from one square to the next. Each cell is independent from the others. Each cell has a fixed size. Larger displays accommodate more cells. A typical screen might have one or two hundred of these cells.
Each cell can be an independent Java object, so my initial idea it to make a View subclass for the cell and pack them into a ViewGroup. On the other hand, the cells don't really play the Android layout game in the sense that they have a fixed size in pixels and cannot grow or shrink their size to accommodate different layouts. From this PoV, it makes sense to make one big View that can draw all of these cells. The CellArray view can then grow and shrink as the layout engine requests.
Which approach seems more natural?
Use a CellArray. Not because it's "more natural" (I'm not sure what that means) but for technical reasons. Views in Android are pretty heavy-weight objects and having a couple of hundred views in your activity will not be easy for the system to handle. (The Android lint rule for TooManyViews will complain if it detects that your view count for a layout exceeds ANDROID_LINT_MAX_VIEW_COUNT, which defaults to 80. You can ignore lint warnings, but they usually are there for good reasons.)
I've recently noticed a significant performance issue with my app. I essentially have the following layout...
DESCRIPTION
In short, there is a ViewPager that holds 3 RelativeLayouts as pages. Inside each page there a number of TextViews. I recently noticed a lot of lag when I type into the EditText that has a TextWatcher on it to perform a quick SQL query for autocompleting. My HTC One M8 stutters and lags as I input text, but I know it isn't the querying that is slow because I measured queries taking only about 7 ms.
I used method profiling, Systrace, and plain old Log debugging and concluded that each time I typed a character in the EditText about 1,700 calls to onMeasure were being made on the TextViews within RelativeLayout A, B, and C. Cumulatively, there are only about 15 independent TextViews across the pages. I noticed that each onMeasure is being called hundreds of times instead of just once or twice like it usually might.
QUESTION
I don't know why typing in the EditText in page B would cause other TextViews in pages A and C to also get "re-measured". More importantly, does anyone have insights on why onMeasure is being called so often? And, does anyone know of a solution to significantly reduce the number of calls to onMeasure?
DETAILS
This may help: I was actually able to cut the the number of onMeasure calls to about 900, by removing RelativeLayout 2 which could suggest the propagation of onMeasure calls starts outside of the ViewPager.
While I don't have enough information to identify the specific problem, I can tell you, in general, what is happening:
A call to a root view's measure causes a call to each sub-view's measure and so on, down the tree.
Suppose that the view called "current view", after asking each of its sub views to measure themselves, decides that there is not enough space for all of the children. It will ask them each to measure themselves again, suggesting a max size. Suppose, then, that the view called "Relative Layout B" changes its size, radically, based on the specified max. Because the change is so big, "current view" must asks each child one more time. This loop could easily require a couple more iterations to resolve.
When "current view" decides on its size, it reports back to "View Pager". Suppose that "View Pager" does not have enough space for its three views, and has to ask them to re-measure, with a max. We are now up to at least 20 calls to the TextView's onMeasure. If each of the other views above ViewPager iterates a few times, pretty soon you start seeing big numbers.
The best solution, as you've already pointed out, is to reduce the depth of that tree. Alternatively, you might use layout managers that have much simpler measurement algorithms (FrameLayout, LinearLayout)
1500 is a lot though. One of those views is doing something weird. You should be able to identify it very simply, using the TreeView tool in the DDMS suite. In eclipse it is called "TreeView". In Studio, click the Android icon and switch to the Hierarchy View perspective. You can also run it from the command line, from the SDK tools directory.
Added:
If you connect Hierarchy Viewer to your app while it is running, you will see the entire view tree in one of its panes. Each node in the tree will have three colored gumdrops towards the bottom. The first gumdrop represents the measurement phase. If the gumdrop is green, the measurement phase for that view is fast. If it is yellow, that node is in the top 50 percentile of nodes in the layout. If it is red, it is the slowest in the layout.
You should be able to follow a trail of yellow right to the offending view.