I use these two functions, slightly modified from Google's code in the Android docs to use filepaths:
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) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) >= reqHeight
&& (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
public static Bitmap decodeSampledBitmapFromFilePath(String pathName, int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(pathName, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(pathName, options);
}
With the idea being to use a scaled-down version of the Bitmap to be mapped onto an ImageView rather than the full-thing, which is wasteful.
mImageView.setImageBitmap(decodeSampledBitmapFromFilePath(pathToFile, 100, 100));
I implemented a thing where you press a button and it rotates to the next image, but there's still a significant lag (it takes a moment for the ImageView to populate) on my phone compared to my emulator. And then occasionally my phone app will crash and I can't replicate it on my emulator.
Is there a problem with this code I've posted above? Is there a problem with the way I am using the code?
Example:
public void reloadPic() {
new Thread(new Runnable() {
#Override
public void run() {
final Bitmap bm = decodeSampledBitmapFromFilePath(filepath, mImageViewWidth, mImageViewHeight);
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
mImageView.setImageBitmap(bm);
}
});
}
}).start();
}
Your code is jumping between threads several times. First you launch a thread. Then you wait for it to be scheduled You decode on that thread. Then you post a command to the UI thread and wait for it to be scheduled. THen you post a draw command to the ui thread (that's part of what setImageBitmap does). Then you have to process any other commands that came in first. Then you actually draw the screen. There's really only 3 ways to speed this up:
1) Get rid of the thread. You shouldn't decode lots of images on the UI thread, but decoding 1 isn't too bad.
2)Store the images in the right size to begin with. This may mean creating thumbnails of the images ahead of time. Then you don't need to scale.
3)Preload your images. If there's only one button and you know what image it will load, load it before you need it, so when the button is pressed you have it ready. Wastes a bit of memory, but only 1 image worth. (This isn't a viable solution if you have a lot of possible next images).
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.
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'm loading a bitmap into an ImageView, and seeing this error. I gather this limit relates to a size limit for OpenGL hardware textures (2048x2048). The image I need to load is a pinch-zoom image of about 4,000 pixels high.
I've tried turning off hardware acceleration in the manifest, but no joy.
<application
android:hardwareAccelerated="false"
....
>
Is it possible to load an image larger than 2048 pixels into an ImageView?
This isn't a direct answer to the question (loading images >2048), but a possible solution for anyone experiencing the error.
In my case, the image was smaller than 2048 in both dimensions (1280x727 to be exact) and the issue was specifically experienced on a Galaxy Nexus. The image was in the drawable folder and none of the qualified folders. Android assumes drawables without a density qualifier are mdpi and scales them up or down for other densities, in this case scaled up 2x for xhdpi. Moving the culprit image to drawable-nodpi to prevent scaling solved the problem.
I have scaled down the image in this way:
ImageView iv = (ImageView)waypointListView.findViewById(R.id.waypoint_picker_photo);
Bitmap d = new BitmapDrawable(ctx.getResources() , w.photo.getAbsolutePath()).getBitmap();
int nh = (int) ( d.getHeight() * (512.0 / d.getWidth()) );
Bitmap scaled = Bitmap.createScaledBitmap(d, 512, nh, true);
iv.setImageBitmap(scaled);
All rendering is based on OpenGL, so no you can't go over this limit (GL_MAX_TEXTURE_SIZE depends on the device, but the minimum is 2048x2048, so any image lower than 2048x2048 will fit).
With such big images, if you want to zoom in out, and in a mobile, you should setup a system similar to what you see in google maps for example. With the image split in several pieces, and several definitions.
Or you could scale down the image before displaying it (see user1352407's answer on this question).
And also, be careful to which folder you put the image into, Android can automatically scale up images. Have a look at Pilot_51's answer below on this question.
Instead of spending hours upon hours trying to write/debug all this downsampling code manually, why not use Picasso? It was made for dealing with bitmaps of all types and/or sizes.
I have used this single line of code to remove my "bitmap too large...." problem:
Picasso.load(resourceId).fit().centerCrop().into(imageView);
Addition of the following 2 attributes in (AndroidManifest.xml) worked for me:
android:largeHeap="true"
android:hardwareAccelerated="false"
Changing the image file to drawable-nodpi folder from drawable folder worked for me.
I used Picasso and had the same problem. image was too large at least in on size, width or height. finally I found the solution here. you can scale the large image down according to display size and also keep the aspect ratio:
public Point getDisplaySize(Display display) {
Point size = new Point();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
display.getSize(size);
} else {
int width = display.getWidth();
int height = display.getHeight();
size = new Point(width, height);
}
return size;
}
and use this method for loading image by Picasso:
final Point displySize = getDisplaySize(getWindowManager().getDefaultDisplay());
final int size = (int) Math.ceil(Math.sqrt(displySize.x * displySize.y));
Picasso.with(this)
.load(urlSource)
.resize(size, size)
.centerInside()
.into(imageViewd);
also for better performance you can download the image according to width and height of the display screen, not whole the image:
public String reviseImageUrl(final Integer displayWidth, final Integer displayHeight,
final String originalImageUrl) {
final String revisedImageUrl;
if (displayWidth == null && displayHeight == null) {
revisedImageUrl = originalImageUrl;
} else {
final Uri.Builder uriBuilder = Uri.parse(originalImageUrl).buildUpon();
if (displayWidth != null && displayWidth > 0) {
uriBuilder.appendQueryParameter(QUERY_KEY_DISPLAY_WIDTH, String.valueOf(displayWidth));
}
if (displayHeight != null && displayHeight > 0) {
uriBuilder.appendQueryParameter(QUERY_KEY_DISPLAY_HEIGHT, String.valueOf(displayHeight));
}
revisedImageUrl = uriBuilder.toString();
}
return revisedImageUrl;
}
final String newImageUlr = reviseImageUrl(displySize.x, displySize.y, urlSource);
and then:
Picasso.with(this)
.load(newImageUlr)
.resize(size, size)
.centerInside()
.into(imageViewd);
EDIT: getDisplaySize()
display.getWidth()/getHeight() is deprecated. Instead of Display use DisplayMetrics.
public Point getDisplaySize(DisplayMetrics displayMetrics) {
int width = displayMetrics.widthPixels;
int height = displayMetrics.heightPixels;
return new Point(width, height);
}
BitmapRegionDecoder does the trick.
You can override onDraw(Canvas canvas), start a new Thread and decode the area visible to the user.
As pointed by Larcho, starting from API level 10, you can use BitmapRegionDecoder to load specific regions from an image and with that, you can accomplish to show a large image in high resolution by allocating in memory just the needed regions. I've recently developed a lib that provides the visualisation of large images with touch gesture handling. The source code and samples are available here.
View level
You can disable hardware acceleration for an individual view at runtime with the following code:
myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
I ran through same problem, here is my solution. set the width of image same as android screen width and then scales the height
Bitmap myBitmap = BitmapFactory.decodeFile(image.getAbsolutePath());
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
int width = size.x;
int height = size.y;
Log.e("Screen width ", " "+width);
Log.e("Screen height ", " "+height);
Log.e("img width ", " "+myBitmap.getWidth());
Log.e("img height ", " "+myBitmap.getHeight());
float scaleHt =(float) width/myBitmap.getWidth();
Log.e("Scaled percent ", " "+scaleHt);
Bitmap scaled = Bitmap.createScaledBitmap(myBitmap, width, (int)(myBitmap.getWidth()*scaleHt), true);
myImage.setImageBitmap(scaled);
This is better for any size android screen. let me know if it works for you.
Scale down image:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
// Set height and width in options, does not return an image and no resource taken
BitmapFactory.decodeStream(imagefile, null, options);
int pow = 0;
while (options.outHeight >> pow > reqHeight || options.outWidth >> pow > reqWidth)
pow += 1;
options.inSampleSize = 1 << pow;
options.inJustDecodeBounds = false;
image = BitmapFactory.decodeStream(imagefile, null, options);
The image will be scaled down at the size of reqHeight and reqWidth. As I understand inSampleSize only take in a power of 2 values.
Use Glide library instead of directly loading into imageview
Glide : https://github.com/bumptech/glide
Glide.with(this).load(Uri.parse(filelocation))).into(img_selectPassportPic);
I tried all the solutions above, one-after-the-other, for quite many hours, and none seemed to work! Finally, I decided to look around for an official example concerning capturing images with Android's camera, and displaying them. The official example (here), finally gave me the only method that worked. Below I present the solution I found in that example app:
public void setThumbnailImageAndSave(final ImageView imgView, File imgFile) {
/* There isn't enough memory to open up more than a couple camera photos */
/* So pre-scale the target bitmap into which the file is decoded */
/* Get the size of the ImageView */
int targetW = imgView.getWidth();
int targetH = imgView.getHeight();
/* Get the size of the image */
BitmapFactory.Options bmOptions = new BitmapFactory.Options();
bmOptions.inJustDecodeBounds = true;
BitmapFactory.decodeFile(imgFile.getAbsolutePath(), bmOptions);
int photoW = bmOptions.outWidth;
int photoH = bmOptions.outHeight;
/* Figure out which way needs to be reduced less */
int scaleFactor = 1;
if ((targetW > 0) || (targetH > 0)) {
scaleFactor = Math.min(photoW/targetW, photoH/targetH);
}
/* Set bitmap options to scale the image decode target */
bmOptions.inJustDecodeBounds = false;
bmOptions.inSampleSize = scaleFactor;
bmOptions.inPurgeable = true;
/* Decode the JPEG file into a Bitmap */
Bitmap bitmap = BitmapFactory.decodeFile(imgFile.getAbsolutePath(), bmOptions);
/* Associate the Bitmap to the ImageView */
imgView.setImageBitmap(bitmap);
imgView.setVisibility(View.VISIBLE);
}
NOTE FOR THOSE WHO WANT TO PUT IMAGES OF SMALL SIZE:
Pilot_51's solution (moving your images to drawable-nodpi folder) works, but has another problem:
It makes images TOO SMALL on screen unless the images are resized to a very large (like 2000 x 3800) resolution to fit screen -- then it makes your app heavier.
SOLUTION: put your image files in drawable-hdpi -- It worked like a charm for me.
Using the correct drawable subfolder solved it for me. My solution was to put my full resolution image (1920x1200) into the drawable-xhdpi folder, instead of the drawable folder.
I also put a scaled down image (1280x800) into the drawable-hdpi folder.
These two resolutions match the 2013 and 2012 Nexus 7 tablets I'm programming. I also tested the solution on some other tablets.
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
///*
if (requestCode == PICK_FROM_FILE && resultCode == RESULT_OK && null != data){
uri = data.getData();
String[] prjection ={MediaStore.Images.Media.DATA};
Cursor cursor = getContentResolver().query(uri,prjection,null,null,null);
cursor.moveToFirst();
int columnIndex = cursor.getColumnIndex(prjection[0]);
ImagePath = cursor.getString(columnIndex);
cursor.close();
FixBitmap = BitmapFactory.decodeFile(ImagePath);
ShowSelectedImage = (ImageView)findViewById(R.id.imageView);
// FixBitmap = new BitmapDrawable(ImagePath);
int nh = (int) ( FixBitmap.getHeight() * (512.0 / FixBitmap.getWidth()) );
FixBitmap = Bitmap.createScaledBitmap(FixBitmap, 512, nh, true);
// ShowSelectedImage.setImageBitmap(BitmapFactory.decodeFile(ImagePath));
ShowSelectedImage.setImageBitmap(FixBitmap);
}
}
This code is work
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;
}
}