Optimizing GridLayout's memory usage - android

I'm simulating a tile-based map in Android using android-support-v7-gridlayout.
I implement it smoothly and fast during the debugging times until I test it on a huge data scale. The actual data would be about ~700x400 (row-column) and I just tested it in 400x100 but the application has just crashed and throws an OutOfMemoryException. I then reduced the data until it actually runs on 300x100. It's not lagging or I don't have any CPU performance issue, the only issue is inadequate memory.
This is how I add ImageViews in the grid layout:
public boolean genMap(GridLayout gl) {
gl.removeAllViews();
gl.setRowCount( mapFile.getRowCount() );
gl.setColumnCount( mapFile.getColCount() );
try {
for (int row = 0; row < mapFile.getRowCount(); ++row) {
for (int col = 0; col < mapFile.getColCount(); ++col) {
com.gridlayout.GridLayout.Spec rowspan = GridLayout.spec(row, 1);
com.gridlayout.GridLayout.Spec colspan = GridLayout.spec(col, 1);
GridLayout.LayoutParams lp = new GridLayout.LayoutParams(rowspan, colspan);
lp.width = Cell.SIZE;
lp.height = Cell.SIZE;
Cell cell = genNewCell( ctx.getApplicationContext(), row, col );
gl.addView( cell, lp );
}
}
return true;
} catch (Exception e) {
return false;
}
}
Where Cell is a subclass of ImageView
Perhaps, I also think of lazyload pattern where the only visible view will be loaded out but, I'd like to know if GridLayout already implements it.
Update 1
GridView seems is not what I'm looking for. It cannot scroll diagonally plus it can't have a scrollbar both horizontal and vertical at the same time. What I want to achieve is something like GoogleMaps' ViewGroup layout wherein you can scroll in a 2 dimensional way and each cells memory allocation/deallocation are managed automatically. I think GridLayout is my last hope as I cannot see any ViewGroup w/c implements what I wanted to.
So my question is, how can I able to recycle those cells that aren't visible yet in the screen while keeping layout as they are present?

GridLayout does not do any dynamic memory management and simply creates everything regardless if it is actually on the screen or not. You need to use something like the GridView. With a GridView, you could theoretically support an infinite amount of items. Now because you want to do horizontal scrolling you will need a custom GridView implementation. Something like https://github.com/jess-anders/two-way-gridview.
If you take away nothing from my post, DO NOT DO THIS WITH A GRID LAYOUT. You could test all day but every phones different and some have more or less memory.
EDIT 1:
This also might be easier with the release of the new RecyclerView in android L, but I haven’t look into it that much.

In my opinion you should use Webview for this. Has scroll, zoom, memory issues already addressed.

Related

Optimise a complex layout for an item displayed inside a RecyclerView

I have to display posts from a json feed inside a RecyclerView and I have a layout for how a single row looks inside the RecyclerView as follows
I have not yet implemented the bottom part in my actual layout which contains the red and green boxes from the figure above and my layout looks like this
I am also implementing Swipe to delete with undo which as you know requires a FrameLayout as the root , and 2 nodes under it, one showing the normal area and one showing the layout which is revealed on swipe. In my case, when I swipe the item, this is what you will see.
Now the problem is, there are 13 views per row and I don't like the odds of that, I will be displaying a maximum of 100 items in the RecyclerView at a given time and as you see it would lead to a large number of Views.
I have certain approaches in mind to make a custom View to reduce the number of Views in the row. What would be the best way according to you to optimise this View or should I say, reduce the number of Views per row in the RecyclerView. I get all the data from JSON and the central text area needs to be expandable in nature with a Read More or Read Less depending on its state.
Approach 1
Slight simplification
In this approach, I will combine the person's profile picture at the top left, the TextView with the name and updated time into a single Custom View, in other words, it will extend from View, have its own canvas for drawing the Bitmap, the Strings but I'll have to manually code RTL and LTR and other items using a StaticLayout in Android. I need the central text area to be expandable in nature so I will stick with one of the Expandable Views everyone keeps mentioning on stackoverflow.
Approach 2
Highly modular Custom UI component.
The entire diagram composed of the user's image, text with name, time, central text and image can be made into a single CustomView, I am not sure how I can make this expandable yet because a StaticLayout once initialised in Android cannot be modified. What do you think? Is this the correct approach to go? I will end up having only 4 children per row if I make the entire thing a single View. Is that a valid use case for custom Views?
Well, I found the answer myself. There are two types of post items I am dealing with, ones that contain an ImageView to display a 16:9 post image and the ones that don't. My RecyclerView was lagging heavily in the scroll operation till now since I had a single layout file which had an ImageView with its width set to match_parent and height set to wrap_content. When I was scrolling, the posts with Images were using Glide to load images and were calling requestLayout() since the size of those items were changing. This was resulting in too many requestLayout() calls causing the heavy lag while scrolling.
How did I fix this?
I made an Adapter which was sectioned having 2 types of rows.
#Override
public int getItemViewType(int position) {
if (mResults != null) {
return mResults.get(position).getPicture() != null ? IMAGE : NO_IMAGE;
}
return NO_IMAGE;
}
Based on whether the result at the current position contains an Image in the post or not, I had my onCreateViewHolder method modified to set a predefined size for the ImageView incase my item had a post image.
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view;
if (viewType == IMAGE) {
view = mLayoutInflater.inflate(R.layout.row_post_image, parent, false);
RowImageHolder holder = new RowImageHolder(view);
//To set the width and height of the ImageView of our image in the post, we get its LayoutParams and adjust its width and height to maintain 16:9 ratio
ViewGroup.LayoutParams params = holder.mPostPicture.getLayoutParams();
params.width = mPostImageWidth;
params.height = mPostImageHeight;
holder.mPostPicture.setLayoutParams(params);
return holder;
} else {
view = mLayoutInflater.inflate(R.layout.row_post_image, parent, false);
RowNoImageHolder holder = new RowNoImageHolder(view);
return holder;
}
}
And thats all which needs to be done. The onBindViewHolder sets an image if the type of the item supports an image. The benefit is that space is reserved in advance for those items that have images and the performance of the RecyclerView on scroll improves tremendously. I achieved this without using any custom views so far :)

Big grid of tiny colored squares causes high CPU usage

I have a method creating a large grid of tiny little colored squares, and I noticed that this has a big impact on CPU usage.
Please note that this method is called only once, and that the problem continues and never ends even when the method has finished. If this method is not executed, cpu usage remains low.
// regionLayout is a vertical LinearLayout
// gridHorizontalSize and gridVerticalSize determines the size of the grid
regionLayout.removeAllViews();
regionLayout.setWeightSum(gridVerticalSize);
ImageView cell = null;
LinearLayout row;
int color;
for(int i=0; i<gridVerticalSize; i++) {
row = new LinearLayout(context);
row.setWeightSum(gridHorizontalSize);
regionLayout.addView(row, new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1f));
for(int j=0; j<gridHorizontalSize; j++) {
cell = new ImageView(context);
color = /* color is chosen somehow */
cell.setBackgroundColor(color);
row.addView(cell, new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 1f));
}
}
I used two LinearLayout to make a grid because I'm unfamiliar with GridLayout and didn't really know how to set a weightSum for both horizontal and vertical sizes.
I used ImageViews for no particular reason, I just need something I can set a color on.
Grids are usually around 30x30 squares, so that makes 900 little colored squares and I know it's a lot, but there's no way around it, I need to have 900 of them on screen at the same time.
What can I change to have an improvement? Thanks.
You should use a custom view, not nested Linear Layouts. You've created 900 view objects on one screen. That is a huge performance hit.
I have a grid board view that I made for a project that you can look at to reference
https://github.com/shmuelr/ConwaysGame/blob/master/app/src/main/java/com/shmuelrosansky/conwaysgame/views/Board.java

Smooth scrolling of expandablelistview containing listviews

My app has a screen with an expandablelistview. Within the expandablelistview are collapsible rows that contain listviews. These listviews contain a dynamic number of items and with dynamic heights on each item. As listviews are meant to be set to a hard-coded height and then to have scrolling within them, and I don't want to have scrolling within scrolling, I've had to get around that by calculating the actual height of the listview's items then set the height of the listview to that height so that all of its contents will fit within the exandablelistview row.
Here is my extension method that calculates the listview's height:
public static void SetHeightBasedOnChildren(this ListView listView) {
var listAdapter = listView.Adapter;
var totalHeight = listView.PaddingTop + listView.PaddingBottom;
if (listView.DividerHeight > 0)
totalHeight += (listView.DividerHeight * (listAdapter.Count - 1));
var desiredWidth = DeviceHelper.GetDimensions ().X;// Add parameter if listview doesn't always span entire width of screen
var listViewWidth = MeasureSpec.MakeMeasureSpec (desiredWidth, MeasureSpecMode.AtMost);
View listItem;
for (int i = 0; i < listAdapter.Count; i++) {
listItem = listAdapter.GetView (i, null, listView);
if (listItem is ViewGroup && listItem.LayoutParameters == null)
listItem.LayoutParameters = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WrapContent, ViewGroup.LayoutParams.WrapContent);
listItem.Measure (listViewWidth, (int)MeasureSpecMode.Unspecified);
totalHeight += listItem.MeasuredHeight;
}
var layoutParams = listView.LayoutParameters;
layoutParams.Height = totalHeight;
listView.LayoutParameters = layoutParams;
}
The issue with this is that when scrolling down the screen and reaching each listview, as it calculates the dynamic height of the listview's items and sets its height to that, it causes the scrolling to get really choppy.
As I mentioned that listviews are meant to be a hard-coded height, I tried swapping out our listviews for linearlayouts with vertical orientation, which are meant to have an adjustable height that fit their content so don't need any fancy height calculations on the fly. Unfortunately this approach didn't work, as linearlayouts don't cache their rows like listviews do, so the screen was completely unusable from trying to manage hundreds -> thousands of rows without any sort of caching. Note: the linearlayouts performed fine on the emulator but not on devices.
I'm wondering if anyone has ideas of how to get smooth scrolling in this situation. Some ideas I had...
Keep using listviews but set a hard-coded height on each item in the listview. Then I can calculate the height of the listview much faster, as I know the height based on the total number of rows. I would still have to set the height on each listview though, which makes it still not smooth but better than before. The downside is that I'd either need to make all rows tall enough to fit longer content (waste of vertical space), or truncate content that doesn't fit.
Look into if anyone has developed a hybrid widget, that is a linearlayout with vertical orientation that utilizes row caching.
Figure out how to force the expandablelistview to load the listview sections immediately on screen load rather than waiting until they're just about to come into view. The downside is we're holding a lot more in memory so this might render the screen unusable.
Thoughts??
EDIT: I should probably mention that the listviews are also within viewpagers, as the user can swipe between lists from different years (think tax records or mortgage records for a property). I bring this up so someone won't say, just don't have listviews at all, just only use the expandablelistview.

How to speed up for loop

I dynamically add textviews to a relative layout based on user response to create a coloured grid pattern. Typically this can contain 5000+ textviews which have different background colors based on the value in textview tag.
I have this method where I iterate through all the textviews to show only those that have the same color and set the rest to gray. This works well when there are say 500 textviews but when the number is higher, say 5000 it take 13 seconds to complete.
if (code.equals("all")) {
for (int i = 0; i < textViewIDs.size(); i++) {
TextView tv = (TextView) findViewById(textViewIDs.get(i));
if (!tv.getTag().toString().equals("header")) {
tv.setBackgroundColor(Color.parseColor("#" + tv.getTag().toString()));
}
}
} else {
for (int i = 0; i < textViewIDs.size(); i++) {
TextView tv = (TextView) findViewById(textViewIDs.get(i));
if (!tv.getTag().equals(code)) {
if (!tv.getTag().toString().equals("header")) {
tv.setBackgroundColor(Color.LTGRAY);
}
}else{
tv.setBackgroundColor(Color.parseColor("#" + tv.getTag().toString()));
}
}
}
textViewIDs is the array holding all the textview ids.
What if anything can be done to speed this up?
Update:
I understand that having that number of widgets is not ideal however I could not come up with a better solution.
As well as each grid cells, in this case each texview, having different colors I also need to be able to manage onclick event for the cell so that I can add text. That was the reasoning for the textviews. Prior to using textviews I drew all the elements but that's when I couldn't find a way to add onclick event to each cell.
I'd better detail the concept to help you guys with what I'm trying to achieve and if I've gone down the wrong road.
This is part of a much larger app where I'm converting images into stitch charts.
Based on user input a grid of colored cells is drawn where each cell is a solid color that has been calculated from the original image most dominant color.
The grid will be larger than the screen so my views are placed in both horizontal and scroll views so they can be panned and zoomed. (This is all working well).
The grid cells have to click-able so I can turn on or off the background colors and also add a single text "X" character to mark stitch (cell) as completed.(This is to slow when the number of textview (cells) are > 500)
Hope there is enough detail there...
findViewById() seems to be your pressure point.
Instead of keeping a list of the ids, I'd a keep a list of references to the Views themselves (WeakReferences if leaks are a possibility)!
1 - for (int i = 0; i < textViewIDs.size(); i++) { ...
It's not optimized: precalculate your limit in a variable before starting the cycle:
int len = textViewIDs.size(); and use len in your cycle.
2 - i-- (I call it "reverse loop") seems to be faster than i++. See a nice loop comparison here
It's a bad practice to have that many TextView's, low-end devices will not be able to load it.
Try making one TextView with multiple styles in it, you can use HTML tags for background colors.
Or even better is to create just one ListView, this will recycle the views for you.

Android: FrameLayout not respecting draw order

I have a FrameLayout with four SurfaceViews arranged in a 2x2 grid. The user can resize each of the views.
I'd like the views to be drawn in order of their area, with the largest view drawn first and so on. Each time a view is resized, I order the views by their area, and update the FrameLayout:
public void reorderViews() {
PlotView child1;
PlotView child2;
boolean swap = false;
for(int i = 0; i < layout.getChildCount(); i++) {
child1 = (PlotView) layout.getChildAt(i);
for(int j = i + 1; j < layout.getChildCount(); j++) {
child2 = (PlotView) layout.getChildAt(j);
if(child1.area < child2.area) {
layout.removeViewAt(j);
layout.addView(child2, i);
}
}
}
}
This works, in the sense that the FrameLayout's children array holds the views in the correct order. But after re-ordering, the views continue to draw in their original order (i.e. the order in which they were originally added).
I've tried requesting layout on the FrameLayout, on the individual child views, on the FrameLayout's parent view. I've also tried invalidating everything. I've overridden the FrameLayout's onDraw hoping to force the correct draw order. No go.
A few points: I'm using FrameLayout rather than Grid because I'd like larger views to obscure smaller views, instead of having a larger view push a smaller view aside, as is the behaviour in GridView.
In order to position the child views properly, I adjust their margins and set their gravity to 'top' (it doesn't seem to matter what value I use for gravity, just so long as gravity is set to something, margins work). Might the gravity be the issue?
Also, I'm sure there's a more efficient way to reorder the views, but there's only four total, so this works ok.
SurfaceView widgets are not drawn using the normal Android layout mechanism. Instead, each SurfaceView gets its own, separate window. I suspect those windows are not being reordered when the views are reordered. What you're trying to do would probably work for any other kind of widget.

Categories

Resources