For an app I'm developing, I am trying to populate a GridView with a lot of images. To avoid OutOfMemoryExceptions, I check the amount of available memory and when a certain threshold is reached, I try to free up memory like so:
private void freeUpMemory() {
// Clear ImageViews up to current position
for (int i = 0; i < mCurrentPosition; i++) {
RelativeLayout gridViewElement = (RelativeLayout) mGridView.getChildAt(i);
if (gridViewElement != null) {
ImageView imageView = (ImageView) gridViewElement.findViewById(R.id.image);
imageView.getDrawable().setCallback(null);
imageView = null;
}
}
}
I noticed that this does not actually free up memory. What I don't know is why. Am I missing something?
When your ImageAdapter gets the "getView()" callback with convertView not null, it is telling you that this view previously supplied by the ImageAdapter is no longer visible on the screen. That's a good time to recover the resources used by the view. Something along the lines of:
ImageView iv = (ImageView)convertView.findViewById(R.id.image_view_in_grid_item);
iv.setDrawable(null);
should remove the reference to the Drawable that is stored in the ImageView. If there are no other references in your code to that Drawable it should be available for garbage collection.
Better yet, if you have another image to be displayed.
iv.setDrawable(newImage);
Then returning convertView as the new view to be used by the grid
will replace the old Drawable with a new one, removing the reference and potentially garbage collecting the image.
You should have a look to the BitmapFactory.Options class of Android. It offers many controls on the Bitmap, and two are very interesting when dealing with a lot of images.
The best solution, I think, is to set inSampleSize to a value like 2 or 4. This will reduce the quality of the image, but will save a lot of memory. Try different values until you find a good ratio.
Sample from Android doc (http://developer.android.com/training/displaying-bitmaps/load-bitmap.html) :
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
There's also inPurgeable, allowing the system to use space from existing Bitmap, but you must be careful as it can leads to crash or invalid bitmaps.
Related
I created a clock widget that is updated from service (via broadcast receiver) every minute but after some hours it takes about 600mb of RAM.
The widget draws a bitmap every minute with some features and shows it by a simple ImageView.
At the beginning the widget occupies only a few kb of ram, but after a few minutes it takes hundreds of mb. There is a way to clear RAM before create the new bitmap?
This is a part of widget code:
public class Widget_01_Clock extends AppWidgetProvider {
#Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
Bitmap clock = WidgetPaint.getClockBitmap();
RemoteViews updateViews = new RemoteViews(context.getPackageName(),R.layout.w_01_clock);
updateViews.setImageViewBitmap(R.id.w_01_clock, clock);
}
}
The reason for the is that every minute you load a new bitmap into your memory.
So your solution should be one of the following:
as already suggested recycle your used bitmap, for future use of this block of memory for next bitmaps.
work with really small images (thumbnails) that wont take so much space, and wont cause you an OutOfMemory exception.
Personally I think you should do both, here is a code to create a thumbnail version of your image file:
public static Bitmap decodeSampledBitmapFromFile(String path,
int reqWidth, int reqHeight) { // BEST QUALITY MATCH
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, options);
// Calculate inSampleSize
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
options.inPreferredConfig = Bitmap.Config.RGB_565;
int inSampleSize = 1;
if (height > reqHeight) {
inSampleSize = Math.round((float)height / (float)reqHeight);
}
int expectedWidth = width / inSampleSize;
if (expectedWidth > reqWidth) {
//if(Math.round((float)width / (float)reqWidth) > inSampleSize) // If bigger SampSize..
inSampleSize = Math.round((float)width / (float)reqWidth);
}
options.inSampleSize = inSampleSize;
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(path, options);
}
I'm in the same boat that you are in, and I MAY have found a solution to keep it somewhat low. After lots of searching and failed results, I can get my clock widget (which has a background image up to 800x800, an overflow-menu image, time, and date -- all bitmaps) to normally go no higher than 45mb; if the user doesn't mess with the widget's settings and customization, it can sometimes stay below 20mb -- as I'm writing this, it's dropped to ~11MB. (then after messing with the customization, jumped to 41MB, and is now sitting at less than 10MB for about an hour now)
Your question says your clock has some features; I'm assuming this is an image of some sort? Well, I noticed with mine that, every minute, on every update, I redrew everything (background, menu, time, and date). I managed to break that in to three parts: menu (has to be separate for the pending intent), background + date (only gets updated when I have UPDATE intent or TIME CHANGED), and the time. I use a SparseArray in my Static class to hold the background + date bitmap, then when I'm only updating the time, I call that bitmap so I don't have to recreate it. Then when I set my remote views, I have something similar to this.
remoteViews.setImageViewBitmap(R.id.widget_consolidated, sparseArrayOfBitmaps.get(widgetNumber);
remoteViews.setImageViewBitmap(R.id.widget_time, functionToReturnTimeBitmap(arguments));
remoteViews.setImageViewBitmap(R.id.widget_overflow, functionToReturnMenuBitmap(arguments));
You might need to do something similar. It won't always keep the memory low (at least for me), but it does seem help.
I have a viewPager that have 4 images that need to resize. I implemented different options but no options run. I write the different options:
In getView put:
image.setImageBitmap(bitmap);
The bitmap obtain:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(bitmapOriginal, null, options);
int sampleSize = 1;
while ((options.outHeight > 600 * sampleSize) &&(options.outWidth > 400 * sampleSize)) {
sampleSize *= 2;
}
options.inJustDecodeBounds = false;
options.inMutable = false;
options.inSampleSize = sampleSize;
Bitmap bm=BitmapFactory.decodeStream(bimapOriginal, null, options);
But it's very slowly.
I put the last code in AsyncTask() but I can't scroll quickly.
I override the
onPageScrollStateChange(int state)
I only show the image when the state is SCROLL_STATE_IDLE but the effect is the same as in the other cases.
I use LruCache, I save the images in LruCache in Asynctask.
In instantiateItem I put
for(int i=position *4;i<(position +1)*4 +4;i++) {
//NameImage is an Array with the name of image. Every time that calling instantiateItem save 4 images in LruCache
new BitmapWorkerTask(nameImage[i).executorOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, null);
But the problem is the same as last cases, I can't scroll quickly and the app are so slowly.
I have images with a big quality and size. How can I do to scroll quickly in viewPager?
The problem is that even though your work is being done in AsyncTask but the ViewPager is waiting for you to return the image(in getView(), instanciateItem(),newView() etc whatever you are using). What you need to do is create several instances of ImageViews and return one of them almost immediately(this speeds things up). Let the AsyncTask do its loading and when it returns(to the corrct instance identified by position), the image is set to the instance which is already in the ViewPager by now.
Create an Array of imageviews to use as buffer and identify them using the position attribute of list item.
ImageView[] bufferView=new ImageView[4]; //global
//use Async task something like this
#Override
protected void onPostExecute(Bitmap result) {
virtualPosition = position % 4; //Real position % (number of ImageViews)
bufferView[virtualPosition].setImageBitmap(result);
}
return this bufferView[virtualPosition] as ImageView for this item
EDIT: Don't forget to add it to parent ViewGroup too
container.addView(pageview[position]);
I wrote an Android Image Manager that handles caching transparently (memory and disk). The code is on Github.
It uses a Handler instead of AsyncTasks. I've been using it for an image heavy application that contains grids and lists all around, the scrolling and load performance has improved significantly. Check it out: https://github.com/felipecsl/Android-ImageManager
I have looked all over for a solution to my problem and just can't seem to figure it out. I'm sure it is probably 1 or 2 simple lines and hopefully someone can steer me in the right direction.
In my app, a user can click a button that will open the gallery. Once they select an image, it will display that image in an ImageView within my app. That part is working perfectly fine. Originally, I just had it return a uri from the gallery and I would directly display that with this:
imageView1.setImageURI(myUri);
Well, obviously I am now running into the dreaded "Out Of Memory" error if the user reloads that page several times in a row so I'm having to clean up my code to scale down the image. I have done this by implementing a bitmap class that turns the image into a bitmap and scales it down for me. Now, my ImageView display code looks like this:
imageView1.setImageBitmap(bitmap1);
That part is working fine as well. HERE IS THE PROBLEM:
I convert the uri path to a string and then save that in a SharedPreference. This is so that when the user exits the application and the comes back later, the image that they set automatically displays. I convert the uri like this:
...
selectedImageUri = data.getData();
String selectedImagePath;
selectedImagePath = getPath(selectedImageUri);
...
The old method to retrieve the SharedPreference String, convert it to uri, then display it was working fine. (except for the Out Of Memory error of course) It looked like this:
Uri myUri = Uri.parse(selectedImagePath);
imageView1 = setImageURI(myUri);
"selectedImagePath" is obviously the String that I retrieved from the SharedPreference. Again, this worked fine but would throw the error if reloaded too many times.
The part that IS NOT WORKING now is when I try to implement the new Bitmap conversion so that I can scale the bitmap and not get the memory error. Here is the code for that:
Uri myUri = Uri.parse(selectedImagePath)
Bitmap bitmap = getThumbnail(myUri);
imageView1.setImageBitmap(bitmap);
This displays nothing. The original image choosing displays the image fine but when I return to this screen and it tries to parse the string from the SharedPreference and then convert it to the bitmap, nothing ever displays. The code for the "getThumbnail" method was taken directly from THIS POST --->
How to get Bitmap from an Uri?
It is the 3rd answer down.
Anyone have any ideas? Sorry for the super long post but I'd rather over explain my problem than not give enough info. Sorry if this was answered somewhere else. I've been hunting through other questions for hours and have just not found anything that solves my problem.
Thanks.
I figured it out so here is what I did for anyone else having this unique problem. After the image is chosen from the gallery and it returns the intent, I got the data from that intent via this code:
selectedImageUri = data.getData();
Then I got the path from that via this:
selectedImagePath = getPath(selectedImageUri);
Which made a call to this "getPath" method:
public String getPath(Uri uri)
{
Cursor cursor = getContentResolver().query(uri, null, null, null, null);
cursor.moveToFirst();
int idx = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
return cursor.getString(idx);
}
Then I saved "selectedImagePath" as a SharedPreference string.
Later, to retrieve that string and convert it back to showing an image, I first retrieved the SharedPreference string and converted it back to "selectedImagePath". Then, I set it in the ImageView like this:
targetImage = (ImageView)findViewById(R.id.imageView1);
targgetImage.setImageBitmap(decodeSampledBitmapFromResource(selectedImagePath, 200, 200));
which made a call to the following methods:
public static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 2;
if (height > reqHeight || width > reqWidth) {
if (width > height) {
inSampleSize = Math.round((float)height / (float)reqHeight);
} else {
inSampleSize = Math.round((float)width / (float)reqWidth);
}
}
return inSampleSize;
}
public static Bitmap decodeSampledBitmapFromResource(String resId,
int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(resId, options);
}
It's a heck of a lot of code to do a fairly simple task but it works so I'm happy and moving on. Hopefully this will help someone else who needs to accomplish the same thing.
I know this must be one of the most asked things at SO, but none of the other answers gave me a solution. But from reading the other answers, looks like I'll need to redesign the way the App is working.
It's like this, we have a ScrollView, which will inflate some views. A ListView can't be used in this situation, because to behave the way we want it would require extending the ListView, and this is something we don't want to do (even though this seems to be our only solution to our current way of showing items, because of this OOM exception). The list can have a lot of columns per row, and the bigger the screen, more columns it will have.
Each inflated View has a layout displaying some info from the database, including a picture. This picture is stored through a byte array. It's any picture taken with the device camera. Currently every photo (byte array) is taking 800kb to 1mb, which seems a lot to me. Now the list have 30+ items. I took photos until the OOM happened, and it happened when I took a total of 6 photos (occasionally 7). That would be 8mb-9mb of data. Everytime I go to other Activity, and go back to the Activity the ScrollView is in, the list needs to be repopulated.
This is the snippet of the PopulateList method:
if (item.getImg() != null) {
if (App.debug) {
Log.d(TAG, "Setting bmp.");
}
Bitmap bmp = App.byteArrayToBmp(item.getImg());
imgV.setImageBitmap(bmp);
}
Every inflated View will open an 'Advanced Dialog', which will contain other info. Maybe the Image could be there instead on the list (meaning that there would be only 1 bitmap, as every inflated View shares the same advanced dialog). Or I could extend the ListView and benefit from it recycling method (It's not a good solution as I though it would be considering more than 6 items can be at the screen). Another thing that bothers me is every picture having 800kb. Seems like a lot for a 128x128.
This is the setup for the size:
cameraParams.setPictureSize(App.pxToDpi(128), App.pxToDpi(128));
cameraParams.setPictureFormat(PixelFormat.JPEG);
camera.setParameters(cameraParams);
public static int pxToDpi(int px) {
final int scale = app.getApplicationContext().getResources().getDisplayMetrics().densityDpi;
int pixels = (int) px * (scale / 160);
return pixels;
}
So, do you think there is a solution to my issue keeping the current model of my App, or will I need to reformulate?
EDIT: The bitmap method:
public static Bitmap byteArrayToBmp(byte[] byteArray) {
Bitmap img = null;
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inSampleSize = 2;
img = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length, opts);
return img;
}
You might want to look at the Official Android Training docs, they've just been updated:
Check out Displaying Bitmaps Efficiently with the lesson: Loading Large Bitmaps Efficiently that goes over this.
Basically you can decode the image using sampleSize to decode it to the width and height you want:
public static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
if (width > height) {
inSampleSize = Math.round((float)height / (float)reqHeight);
} else {
inSampleSize = Math.round((float)width / (float)reqWidth);
}
}
return inSampleSize;
}
Explained in much great detail in the links above
I have an android App with plenty of animations.
When I programmatically create animations (using AnimationDrawable) the non-java object (as appears in DDMS Heap tab) grows with every new animation I load and never shrinks back even after my animations get released.
I have only one reference to each AnimationDrawable object from a wrapper object I wrote and I verified this object gets released by overriding the finalize method and making sure it gets called.
Eventually android stops loading images and prints "out of memory" errors to the log.
The interesting thing is that this happens only in some devices (Motorola Xoom, Sony Experia) and not in others (such as the Galaxy S).
This problem is not specific Honeycomb or pre-Honeycomb as you can see from the device examples I gave.
Some of the things I tried:
Calling recycle on each of the frames after I am done with the current animation but it doesn't seem to help.
Assigning null to the AnimationDrawble object
Making sure that there are no static variable related to the class holding the reference to the animation drawable
Make sure the problem disappears once I comment out myAnimation.addFrame(...)
This isn't an exact answer, but rather a helpful hint to find where the exact leak is occurring. Perform a heap-dump after you expect your memory to be reclaimed and see why the objects you think should be dead are still alive.
Make sure you get the memory analyzer tool for eclipse. (http://www.eclipse.org/mat/)
There could be two possible reason, first at the time of creating the bitmap and second when you are converting the bitmap into the BitmapDrawable. As i can see from your comment (new BitmapDrawable(currentFrameBitmap) now this method is depreciated better to use BitmapDrawable(getResources(),currentFrameBitmap) Without the Resources reference, the bitmap may not render properly, even when scaled correctly. To load bitmap efficiently you can scale it properly.
public class BitmapDecoderHelper {
private Context context;
public BitmapDecoderHelper(Context context){
this.context = context;
}
public int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
Log.d("height reqheight width reqwidth", height+"//"+reqHeight+"//"+width+"///"+reqWidth);
if (height > reqHeight || width > reqWidth) {
if (width > height) {
inSampleSize = Math.round((float)height / (float)reqHeight);
} else {
inSampleSize = Math.round((float)width / (float)reqWidth);
}
}
return inSampleSize;
}
public Bitmap decodeSampledBitmapFromResource(String filePath,
int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(filePath, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
Log.d("options sample size", options.inSampleSize+"///");
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
// out of memory occured easily need to catch and test the things.
return BitmapFactory.decodeFile(filePath, options);
}
public int getPixels(int dimensions){
Resources r = context.getResources();
int px = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dimensions, r.getDisplayMetrics());
return px;
}
public String getFilePath(Uri selectedImage){
String[] filePathColumn = {MediaStore.Images.Media.DATA};
Cursor cursor = context.getContentResolver().query(selectedImage, filePathColumn, null, null, null);
cursor.moveToFirst();
int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
String filePath = cursor.getString(columnIndex);
cursor.close();
return filePath;
}
}