Good morning everybody,
I have a big problem with my Android app regarding to memory usage. My app offers (at the moment) 22 card games. Each game is an activity with own specific layout. They are in res/layout folder and all contained views have width and height = 0dp. After loading a layout I calculate the max possible size of cards (view), according to screen size, preferred deck-type (7 types are available) and layout itself and I set the calculated size to each displayed cards (in the worst case there are 52 cards displayed at the same time). Card images (about 100kb for single image) are all in asset folder (assets/${deck_type}/a1.png ...assets/${deck_type}/a52.png).
So, each game has its layout with its specific card-size (known just at runtime). Now my problem is that after playing about 6/7 different games, I encounter an OutOfMemory while loading bitmaps or while loading a new game layout. If you change the deck-type, the OOM arrives about 3/4 games.
Initially I loaded all card images of preferred deck-type in a LruCache (and reload it only after user deck-type changing), and call
cardView.setImageDrawable(cardImages.get(card));
when cardView is displayed. But with this solution OOM arrives almost soon.
So I have kept this approach but using Bitmap (instead of Drawable), loaded with com.nostra13.universalimageloader.core.ImageLoader: little bit better but not well yet. So I've stopped to use a fully preloaded LruCache and sync load bitmap at needing, scaling them according to game-card-size
private void scaleImage(int newWidth) {
int width = scaled.getWidth();
int height = scaled.getHeight();
float scaleWidth = ((float) newWidth) / width;
float ratio = ((float) scaled.getWidth()) / newWidth;
int newHeight = (int) (height / ratio);
float scaleHeight = ((float) newHeight) / height;
Matrix matrix = new Matrix();
matrix.postScale(scaleWidth, scaleHeight);
System.gc();
scaled = Bitmap.createBitmap(scaled, 0, 0, width, height, matrix, true);
}
and cache them in a LruCache, evicted at the end of game and in OnDestroy of my activity game I call
System.gc()
but seems has no effects.
More little bit better but OOM is not avoided.
I have also studied all five chapters https://developer.android.com/training/displaying-bitmaps/index.html
but I wasn't able to adapt them for my need.
How can I solve this problem? Do you know an alternative approach?
Thank you very very much.
Related
I wanted to scale image in order that they kept the same ratio. Thus for example, an arrow has the same size in all images after the rescaling. So I followed this example and it works fine.
But after lost of manipulations of the listview, I can have an OutOfMemoryError error. I ckeck the heap dump in DDMS and that's right, the allocation size always goes up. I put some bitmap.recycle() but it leads to an error: "cannot draw recycled bitmaps".
I also tried the official tutorial, but I had problems, the downloaded sample is not the same as those explained, and I don't understand everything.
Please, can you explain me how to solve my mistake?
Thanks
Bitmaps always stored in the device Heap Memory , and your device heap memory can't afford that number of Bitmaps as may be the way you try works on the same resolution of the image, So, at first try to grow the heap using your app manifest, then try this way to resize your Bitmaps:
public static Bitmap scaleBitmap(Bitmap bitmapToScale, float newWidth, float newHeight) {
if(bitmapToScale == null)
return null;
//get the original width and height
int width = bitmapToScale.getWidth();
int height = bitmapToScale.getHeight();
// create a matrix for the manipulation
Matrix matrix = new Matrix();
// resize the bit map
matrix.postScale(newWidth / width, newHeight / height);
// recreate the new Bitmap and set it back
return Bitmap.createBitmap(bitmapToScale, 0, 0, bitmapToScale.getWidth(), bitmapToScale.getHeight(), matrix, true);
}
if this don't work either, so, you have to make one of the following:
assign to each image a much less width and height.
compress the image using something like ImageLoader.
use another device with much Heap Space.
limit the number of images used.
HINT: recycling the Bitmap totally removes it from the Heap memory, So, it's logical to have a "cannot draw recycled bitmaps" error, So, if you want to recycle it and then use it in your app, you have to store it at first in something like ArrayList<Bitmaps>.
I have a strange behaviour by getting Bitmaps with the methods BitmapFactory.decodeStream(Resources res,int id) and BitmapFactory.decodeFile(String pathName). I want to set a bitmap to my appwidget remoteViews, it is an png image with 319x319px and 32kb, for my understanding, it is a very small image that should be handled easily.
Now, what´s happing is, if I put this image inside my drawables folder and want to load the bitmap with BitmapFactory.decodeResource, the app crashs and I get an error-message that the remoteViews exceeds the max bitmap memory usage.
What I find out is, that there is max memory usage to the following rule (from developer.google.com) :
The total Bitmap memory used by the RemoteViews object cannot exceed that
required to fill the screen 1.5 times, ie.
(screen width x screen height x 4 x 1.5) bytes.
Now to the strange part: If I load the identical image from sd card and decode it with BitmapFactory.decodeFile(), it works. Why is this happening?
The problem I face here is, that I have to set some default images, if no other one is on my folder in sd card. For this, I put them in to my drawables folder. If I scale them down to 119x119px, then it works and I get no error message. But this is very bad quality and looks totally blurred at the widget.
Can anybody explain, what exactly is the difference of the behaviour how the bitmaps are loaded? Is there any automatic scaling function in decodeFile() that is not in decodeResource()?
A part of code or the logcat output is irrelevant, so I don´t post it here, I just can´t understand this behaviour and don´t know how to handle it. Also, I searched a lot about this and read the API, but there is nothing I can find out.
EDIT
For now I fixed this with calculating the bitmap memory usage with the above formular and check the bitmap size like this:
DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
double width = (double) metrics.widthPixels;
double height = (double) metrics.heightPixels;
double maximumUsage = width * height * 4.0 * 1.5;
double maximumImageSizeFaktor = (maximumUsage / 3) / 1.5 / 4;
double maximumImageSize = Math.sqrt(maximumImageSizeFaktor);
int maxSize = (int) maximumImageSize - 10; //just to get sure, minimize it for 10
return maxSize;
Then I know the maximal size for my bitmaps and can downsample or resize it. But this did not answer the strange behaviour.
If BitmapFactory is loading from drawables then it does also some scaling according the device screen. By loading from file that will not happen. If you put your images in assets folder it will not happen either.
I am programming an app that shows a lot of verses/poems so text wrapping is not an option for me. I would like the text to be as big as possible (doesn't have to recalculate each time a new text is shown, should just allow the biggest text to fit on the screen) without extending screen size. It should not visually scale or take longer for the text to appear.
Is this possible?
Thanks
I would suggest a simple search for the best point size using the largest text that you need to fit. This can be done once at start-up. (Well, maybe twice—once for landscape and once for portrait). The first step would be to initialize a Paint with the typeface you want to use for display. Then call this function to
public void setBestTextSize(String longestText, int targetWidth, Paint paint) {
float size = paint.getTextSize(); // initial size
float w = paint.meaasureText(longestText);
size = targetWidth * size / w;
paint.setTextSize(size);
// test if we overshot
w = paint.measureText(longestText);
while (w > targetWidth) {
--size;
paint.setTextSize(size);
w = paint.measureText(longestText);
}
A binary search in the loop might be theoretically faster, but this should do pretty well since text width does scale approximately linearly with font size and the first step before the loop should get the size pretty close.
An alternative approach, which deals nicely with view size changes, is shown in this thread.
I want to load images in Android, but if the image is just too large I wanted to resize it prior to loading . I learned that one can use the BitmapFactory to get just the size of the image, then one can figure out an appropriate scaling factor and use BitmapFactory to load the image with the required dimensions (per the very competent suggestions I found in this related thread).
So I tried that and it refused to work. I spent the last hour picking through the code trying to figure out why such a simple operation wasn't having any effect whatsoever (the scaling factor was being utterly ignored!)
Then I stumbled upon Android issue 3072... turns out this has been identified as broken for GIF files for well over two years. I realize GIF isn't exactly modern but it's still out there and in wide use (my test set has a lot of them, which is why it seemed uniformly broken until I found that bug report).
My question is, what can I use as an alternative to BitmapFactory that will correctly read and resize a GIF file when a scaling factor is employed without silently ignoring it? Memory constraints preclude loading large images directly for further processing, so this would need to be a streaming solution.
My other question is, how many others run into old, unfixed bugs like this and either don't realize it or worse, rely on the API to accurately perform the requested function and end up with memory leaks and other strange surprises?
I ended up using the solution posted here.
options.inSampleSize = 2;
bitmap = BitmapFactory.decodeStream(new URL(url).openStream(),null, options);
Bitmap scaledBitmap = scaleDown(bitmap, 1280, true);
bitmap = scaledBitmap;
Where scaleDown is:
public static Bitmap scaleDown(Bitmap realImage, float maxImageSize,
boolean filter) {
float ratio = Math.min(
(float) maxImageSize / realImage.getWidth(),
(float) maxImageSize / realImage.getHeight());
int width = Math.round((float) ratio * realImage.getWidth());
int height = Math.round((float) ratio * realImage.getHeight());
Bitmap newBitmap = Bitmap.createScaledBitmap(realImage, width,
height, filter);
return newBitmap;
}
I'm making an app widget for Android, which due to being composed of custom elements such as graphs, must be rendered as a bitmap.
However, I've run into a few snags in the process.
1) Is there any way to find the maximum available space for an app widget? (OR: Is it possible to calculate the dimensions correctly for the minimum space available in WVGA (or similar wide) cases?
I don't know how to calculate the maximum available space for an app widget. With a conventional app widget it is possible to fill_parent, and all the space will be used. However, when rendering the widget as a bitmap, and to avoid stretching, the correct dimensions must be calculated. The documentation outlines how to calculate the minimum dimensions, but for cases such as WVGA, there will be unused space in landscape mode - causing the widget to look shorter than other widgets which stretch naturally.
float density = getResources().getDisplayMetrics().density;
int cx = ((int)Math.ceil(appWidgetInfo.minWidth / density) + 2) / 74;
int cy = ((int)Math.ceil(appWidgetInfo.minHeight / density) + 2) / 74;
int portraitWidth = (int)Math.round((80.0f * cx - 2.0f) * density);
int portraitHeight = (int)Math.round((100.0f * cy - 2.0f) * density);
int landscapeWidth = (int)Math.round((106.0f * cx - 2.0f) * density);
int landscapeHeight = (int)Math.round((74.0f * cy - 2.0f) * density);
Calculating cx and cy gives the number of horizontal and vertical cells. Subtracting - 2 from the calculated dpi (e.g. 74 * cy - 2) is to avoid cases where the resulting number of pixels is rounded down. (For example in landscape mode on Nexus One, the height is 110, not 111 (74 * 1.5).
2) When assigning a bitmap to an ImageView which is used as part of the RemoteViews to view the image, there are 2 methods:
2.1) By using setImageViewUri, and saving the bitmap to a PNG file. The image is then served using an openFile() implementation in a ContentProvider:
#Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException
// Code to set up imageFileName
File file = new File(getContext().getCacheDir(), imageFileName);
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
}
This works, and it's the approach I'm currently using. However, if I set the scaleType of the ImageView to "center", which by the documentation is supposed to "Center the image in the view, but perform no scaling.", the image is incorrectly scaled. Setting the density of the bitmap to DENSITY_NONE or getResources().getDisplayMetrics().densityDpi doesn't make any difference when saving the bitmap to PNG, it seems to be ignored when the file is loaded by the ImageView. The result is that the image is scaled down, due to some dpi issue. This seems to describe the case:
http://code.google.com/p/android/issues/detail?id=6957&can=1&q=widget%20size&colspec=ID%20Type%20Status%20Owner%20Summary%20Stars
Because it is not possible to use scaleType:center, the only way I've found to work is to set the layout_width and layout_height of the ImageView statically to a given number of dpis, then rendering the bitmap to the same dpi. This requires the use of scaleType:fitXY. This approach works, but it is a very static setup - and it will not work for resizable 3.1 app widgets (I haven't tested this yet, but unless onUpdate() is called on each resize, this is true).
Is there any way to load an image to an ImageView unscaled, or is this impossible due to a bug in the framework?
2.1) By using setImageViewBitmap directly. Using this method with the Bitmap.DENSITY_NONE setting on the bitmap, the image can be shown without scaling correctly. The problem with this approach is that there is a limitation to how large images can be set through the IPC mechanism:
http://groups.google.com/group/android-developers/browse_thread/thread/e8d84920b999291f/d12eb1d0eaca93ac#01d5c89e5e7b4060
(not allowed more links)http://groups.google.com/group/android-developers/browse_thread/thread/b11550601e6b1dd3#4bef4fa8908f7e6a
I attempting a bit of a hack to get past this issue, by splitting the widget into a matrix of images which could be set in 100x100 pixel blocks. This did allow for larger widgets to work, but ended up being very heavy and failed on large widgets (4x4).
Sorry for a very long post. I've tried to explain a few of the different issues when attempting to use a bitmap rendered app widget. If anyone has attempted the same and have found any more solutions to these issues, or have any helpful comments, this will be highly appreciated.
An approach that worked for us for a similar situation was to generate our graph as a 9-patch png, with the actual graph part as the scalable central rectangle, and the caption text and indication icons (which we did not want all stretched out of shape) and border effects, placed on the outer rectangles of the image.