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
Related
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 9 years ago.
Improve this question
I'm new to Android so I'm not getting exactly how to make my application more responsive as it creates bitmaps for each processing and set to imageView.Basically What i'm trying to do is that create a bitmap, play with it,like passing values from seekBar to change its properties and set it to imageView.How to create a Copy of Bitmap object to avoid references.Any Suggestions ?? Thanks in advance
You can try this library which handles bitmap very efficiently.
https://github.com/thest1/LazyList
Its very easy to use this lazy list library.
It does the job of caching bitmap automatically:-
ImageLoader imageLoader=new ImageLoader(context);
imageLoader.DisplayImage(url, imageView);
NOTE :
Don't forget to add the following permissions to your AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
Please create only one instance of ImageLoader and reuse it all around your application. This way image caching will be much more efficient.
and also you can look into Nostras ImageLoader, as it efficiently handles loading images into specific sized containers, i.e resizing and compressing them, before you will need to handle them. It also supports content uris which will help you all at once.
Besides this,it’s not worth loading a 1024x768 pixel image into memory if it will eventually be displayed in a 128x96 pixel thumbnail in an ImageView.
You should load a scaled down version of image into memory.
I am also sharing wonderful utility class for bitmap which helps you to scale down your image according to size:-
BitmapUtil.java
/**
* Provides static functions to decode bitmaps at the optimal size
*/
public class BitmapUtil {
private BitmapUtil() {}
/**
* Returns Width or Height of the picture, depending on which size is smaller. Doesn't actually
* decode the picture, so it is pretty efficient to run.
*/
public static int getSmallerExtentFromBytes(byte[] bytes) {
final BitmapFactory.Options options = new BitmapFactory.Options();
// don't actually decode the picture, just return its bounds
options.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
// test what the best sample size is
return Math.min(options.outWidth, options.outHeight);
}
/**
* Finds the optimal sampleSize for loading the picture
* #param originalSmallerExtent Width or height of the picture, whichever is smaller
* #param targetExtent Width or height of the target view, whichever is bigger.
*
* If either one of the parameters is 0 or smaller, no sampling is applied
*/
public static int findOptimalSampleSize(int originalSmallerExtent, int targetExtent) {
// If we don't know sizes, we can't do sampling.
if (targetExtent < 1) return 1;
if (originalSmallerExtent < 1) return 1;
// test what the best sample size is
int extent = originalSmallerExtent;
int sampleSize = 1;
while ((extent >> 1) >= targetExtent) {
sampleSize <<= 1;
extent >>= 1;
}
return sampleSize;
}
/**
* Decodes the bitmap with the given sample size
*/
public static Bitmap decodeBitmapFromBytes(byte[] bytes, int sampleSize) {
final BitmapFactory.Options options;
if (sampleSize <= 1) {
options = null;
} else {
options = new BitmapFactory.Options();
options.inSampleSize = sampleSize;
}
return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
}
}
also You can go to this link It will help you to build the app
http://developer.android.com/training/displaying-bitmaps/index.html
Use Lazy loading and Image loader classes in android to set image on imageView so it will look like more responsive
Here is some tutorial links for this
Tutorial 1
Tutorial 2
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 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
Memory Issues
So I am writing an app that should be able to page through detail views that have one large 640 x 480 image on top and 3 images that are part of a gallery that is being lazy loaded. Following Google design guidelines this is what they suggest doing. I can page through maybe 12 - 13 fragments before it crashes because of being out of memory. I think that there are a couple of culprits in this problem.
1.) I am using the FragmentStatePager. Shouldn't this be destroying the fragments that are not being viewed when memory becomes an issue? This is not happening. I thought it was automatic. What do I have to do to make this happen? Could it have something to do with how I have my Fragment implemented? I do all of my Activity config in onCreateView. For the sake of thoroughness I've included the source for this. Plain Vanilla here:
public static class MyAdapter extends FragmentStatePagerAdapter {
public MyAdapter(FragmentManager fm) {
super(fm);
}
#Override
public int getCount() {
return NUM_ITEMS;
}
#Override
public Fragment getItem(int position) {
return InventoryDetailFragment.newInstance(position);
}
}
2.) I have a method that is trying to figure out the size of the image that needs to be downloaded without placing it in memory. Then compresses the image while downloading it to the required size. This is not successfully implemented. But I'm not sure what is going wrong.
private Bitmap downloadBitmap(String url, int width, int height) {
Bitmap bitmap = null;
int scale = 1;
try {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
bitmap = BitmapFactory.decodeStream((InputStream)new URL (url).getContent(), null, options);
if (options.outHeight > height || options.outWidth > width) {
scale = (int) Math.max(((options.outHeight)/ height), ((options.outWidth)/ width)); }
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize = scale;
bitmap = BitmapFactory.decodeStream((InputStream)new URL (url).getContent(), null, o2);
cache.put(url, new SoftReference<Bitmap>(bitmap));
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (Error e){
Log.d("TEST", "Garbage Collector called!");
System.gc();
}
return bitmap;
}
I have tried everything that I know how to do but it's beyond my meager grasp of Android/Java. Please help! Thanks!
There are a few things that you need to change:
This is a horrible idea: BitmapFactory.decodeStream((InputStream)new URL (url).getContent(), null, options); You're getting the image from the web each time this is executed (so twice in the code you posted). Instead, you need to download the image and cache it locally.
Add logic to your fragments to call recycle() on the bitmaps as soon as the fragment is detached. Add logic to always reload the image (from the cache) whenever the fragment is attached.
Lastly, your inSampleSize calculation is wrong. inSampleSize should be a value that's a power of two, e.g. 1,2,4,8. You can use logarithms or simple binary logic to get the right one, this is what I use, which will always downsample using at least 2 (only call this if you know that the image is too big):
-
int ratio = (int) Math.max((height/options.outHeight), ( width/options.outWidth); //notice that they're flipped
for (int powerOfTwo = 64; powerOfTwo >=2; powerOfTwo = powerOfTwo >> 1 ) { //find the biggest power of two that represents the ratio
if ((ratio & powerOfTwo) > 0) {
return powerOfTwo;
}
}
if you realize your graphics with opengl, this would not counted to memory.
Another ooption is to use
android:largeHeap="true"
in the manifest. Could be working.
did you use ddvm to search for memory leaks?
http://www.youtube.com/watch?v=_CruQY55HOk
I am facing this issue but unable to resolve. My problem goes like this:
I have a list view. My list row contains an image view and one text view
I have 20 images each of 12 MB size stored in my SD card
I have to set these images in imageView of my list view
I am able to do it by following code. This I am doing in getView method of my custom adapter:
public View getView (int position, View convertView, ViewGroup parent) {
//other stuffs like recycling, view holder etc...
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inSampleSize = 4; // tried with 8,12 too
Bitmap bitmap = BitmapFactory.decodeFile(imageUri, opt);
holder.imageView.setImageBitmap(bitmap);
return convertView;
}
Now this code works fine with small images but plays havoc for image of large size. Application crashes with OutOfMemoryError. If I try to increase inSampleSize to 8 or 12, it works but image quality drastically comes down and image doesn't look original image at all. I tried with imageView.setImageURI(imageUri) also but documentation suggests to use setImageBitmap only.
Now please someone help me to rectify this issue. How can I resolve this without compromising with image quality?
Try setting inSampleSize to 2 or 4, and scale width and height down using the Matrix class.
But as you said in another answer, this may hit performance.
float desiredWidth = 60; // the width you want your icon/image to be
float scale = desiredWidth/actualWidth // bitmap.getWidth() for actualWidth
// float scale = desiredHeight/actualHeight // If you want to go by a particular height
Matrix matrix = new Matrix();
matrix.setScale(scale, scale);
holder.imageView.setImageBitmap(Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false));
bitmap.recycle(); // ish
Second option. Before instantiating the Adapter class, get a Cursor object to thumbnail images and pass the cursor into the constructor of the adapter, making it a class varaible. Use the same cursor on every view row in the ListView.
Uri tUri = MediaStore.Images.Thumbnails.EXTERNAL_CONTENT_URI; // Where thumbnails are stored
String[] columns = { MediaStore.Images.ImageColumns._ID, MediaStore.Images.ImageColumns.DATE_TAKEN, MediaStore.Images.ImageColumns.DISPLAY_NAME};
Cursor thumbCursor = this.managedQuery(tUri, null, null, null, null); // May want to use more paramaters to filter results
cursor.moveToFirst();
.... instantiate adapter, pass in cursor ....
public View getView (int position, View convertView, ViewGroup parent) {
int columnIndex = cursor.getColumnIndex(MediaStore.Images.Thumbnails.DATA);
String filePath = cursor.getString(columnIndex);
Bitmap bitmap = BitmapFactory.decodeFile(filePath);
holder.imageView.setImageBitmap(bitmap);
cur.moveToNext();
// BitmapDrawable d = new BitmapDrawable(listActivity.getResources(), filePath);
// holder.imageView.setImageDrawable(d);
}
Something like that. I did it in a hurry. Good luck =)
A quick band-aid that might help would be to use:
opt.inPreferredConfig = Bitmap.Config.RGB_565;
That will load your images without an alpha channel, and only using 2 bytes per pixel (rather than 4 bytes with the default ARGB_8888).
Depending on the length of your list, you might have to load/unload your images as they become visible on screen/leave the screen.
Try calculating scale factor based on the image dimensions and call Bitmap.recycle() to release memory allocated
First of all, as images are of 12mb(even if they are of only 1mb), you have to scale them and downsize them to the dimensions of your imageview.
while you can also check for outofmemory exception...
public View getView (int position, View convertView, ViewGroup parent)
{
//other stuffs like recycling, view holder etc...
boolean OOME = true;
int sample = 4;
while(OOME)
{
try
{
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inSampleSize = sample; // tried with 8,12 too
Bitmap bitmap = BitmapFactory.decodeFile(imageUri, opt);
bitmap = bitmap.createScaledBitmap(bitmap,newWidth,newHeight,true);
holder.imageView.setImageBitmap(bitmap);
OOME = false;
}
catch(OutOfMemoryError e)
{
sample *= 2;
}
}
return convertView;
}
I also take assumption, you are doing all the decoding stuff in a background thread to make your listview scroll smooth...
While having many problems with Bitmaps and ListView, this was the best approach i can come with... still I am open for any changes or modifications or enhancments...