I'm trying to implement an image gallery in android.
The code based on http://www.mobisoftinfotech.com/blog/android/android-gallery-widget-example-and-tutorial/ and i've changed some details.
I'm using WeakReference and it seems, that when i've too many bitmaps, the garbage collector destroys my weakreferences. How can i handle this?
I get my bitmaps via this function:
public static WeakReference<Bitmap> getBitmap(String imageName, int width,
int height) {
String pathToImage = getPathToImage(imageName);
Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(pathToImage, options);
/*
* Calculate inSampleSize
*/
options.inSampleSize = calculateInSampleSize(options, width, height);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
WeakReference<Bitmap> scaledBitmap = new WeakReference<Bitmap>(
BitmapFactory.decodeFile(pathToImage, options));
return scaledBitmap;
}
And i've taken the Solution 320x480, so i think it is not this big...
When the gallery has more than 3 pictures, some of them aren't displayed.
Is the gallery-tutorial not that good? Are there other ways to implement this?
Thank you!
Instead of using soft references, you should take a look at the lrucache class (it became available in honeycomb, but is part of the android-support library.
You can read more about it here : http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html
It's pretty convenient : use this and you won't have to handle the memory yourself with weak references :-)
Related
Here is my problem: I'm asking my user to choose between different sizes of a same image in order to upload it. My interface shows 4 buttons with each giving informations about the width, the height of the image, and the length of the file.
I managed to produce the four versions of the image calling Bitmap.createScaledBitmap, then compressing those bitmaps to files again.
I'm well aware I need to recycle the bitmap and delete the files when I'm done.
However, when trying to compute a big file, the method createScaledBitmap throws an out of memory exception.
I was told to use the option inJustDecodeBounds to decode the file but it would return a null bitmap.
Does someone have an idea on how to solve this problem?
Here is the code:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = false;
options.inPreferredConfig = Bitmap.Config.RGB_565;
Bitmap bmp = getScaledBitmap(BitmapFactory.decodeFile(uri, options), ratio);
private Bitmap getScaledBitmap(Bitmap bmp, float ratio) {
int srcWidth = bmp.getWidth();
int srcHeight = bmp.getHeight();
int dstWidth = (int)(srcWidth * ratio);
int dstHeight = (int)(srcHeight * ratio);
Bitmap result = Bitmap.createScaledBitmap(bmp, dstWidth, dstHeight, true);
bmp.recycle();
return result;
}
To answer your replies, I don't consider it to be a duplicate. I previously found the thread you linked but it's quite old and it didn't solve my problem.
Anyway, I solved the problem.
I first tried using Glide library which is said to be better for memory management. Though, there was an improvement but it wasn't perfect and the problem occured again.
Using android:largeHeap="true" put an end to my nightmare. This has to be set in the application field of your manifest.
I hope this will help someone else.
I am trying to prevent OutOfMemoryError in my android app. I have read many post but I cannot solve it yet.
The app has activities with background so I think this is the main problem. OutOfMemoryError only occurs in some devices (maybe due to VM heap) and I need to be sure that this error won't produce a crash in any device.
I have recently read about MAT (Memory Analytics plugin), and I have executed it during the app runtime, here you can see the result:
dominator_tree
report
In this activity I have a background for each orientation (home, home_land). Both sizes are the same (190kb, jpg). When I created the HPROF file the activity was in landscape orientation and I hadn't ran the portrait orientation before. What conclusion can I extract about this result in order to get my purpose?
I can add more information if it is necessary
EDIT
I tried to use the method of this page in order to avoid OutOfMemoryError too, but I couldn't get it. This was my code:
decodeFromResource class
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
public class decodeFromResource {
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 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);
}
public static Drawable getDecodedDrawableFromResource(Resources res, int resId,
int reqWidth, int reqHeight){
return new BitmapDrawable(res, decodeSampledBitmapFromResource(res, resId, reqWidth, reqHeight));
}
}
onCreate method from the main activity
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.home);
resources = getResources();
DisplayMetrics metrics = resources.getDisplayMetrics();
layoutHome = (LinearLayout) findViewById(R.id.home_layout);
if (resources.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
layoutHome.setBackgroundDrawable(decodeFromResource
.getDecodedDrawableFromResource(resources, R.drawable.home,
metrics.widthPixels, metrics.heightPixels));
} else {
layoutHome.setBackgroundDrawable(decodeFromResource
.getDecodedDrawableFromResource(resources,
R.drawable.home_land, metrics.heightPixels,
metrics.widthPixels));
}
I had implemented the "Loading Large Bitmaps Efficiently" method only for the background, because apart from this I have only five small buttons with very small size. Should I also need to implement the method for them? Can you see any errors?
You are probably loading your jpg files as is, which can easily lead to OutOfMemory even on strong devices.
Keep in mind that an image loaded into memory with no compression and that on most devices a single pixel is represented by 4 memory bytes. A 7 MPixel image, for example, will require mem block of 28 MByte which may bring your app real close to OutOfMemory crash.
The solution is simple: always load a scaled-down image, according to your app's needs.
To do this start by reading your image size:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;
The code above will NOT load the actual image file. it will only interrogate it
for its dimension.
Once you have the deminsion you can calculate the 'sample size' to be used for
loading the scaled image. A sample size of N will result in loading 1/(N*N) of the
orig image pixels, e.g. for sample size of 4 only 1/16 of the image pixels will be loaded.
And finally load the scaled down image:
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
options.inSampleSize = mySampleSize; //<-----------------------
options.inJustDecodeBounds = false;
bitmap = BitmapFactory.decodeResource(res, resId, options);
Even when doing a scaled-down load it is a good idea to protect your code with
a try {...} catch(OutOfmemory) clause and allow for a graceful handling of load failure.
More details here: http://developer.android.com/training/displaying-bitmaps/load-bitmap.html
Try using android:largeHeap="true" tag in your AndroidManifest.xml This should make sure that android will handle larger bitmaps.
EDIT
I have to point that this is not ultimate solution to your problem, it will not prevent OutOfMemory exception but they will be less likely to appear. Probably Gilad Haimov posted right solution to this problem
options.inPurgeable = true;
options.inInputShareable = true;
these flags allow the actual bits associated with a Bitmap object to be discarded under memory pressure and transparently re-decoded if it turns out you're still using the Bitmap.
Since bitmaps are decompressed, 190kb in storage doesn't really help, did you simply load up the bitmap with little regard of manipulating the parameters fitting memory paging requirement? About up to screen-resolution image can load straight up with no regard of memory.
I think when you used DecodeResource such as Bitmap ,you'd better use GC artificially,if you don't do that ,it maybe OOM.
Finally, after I have read many post, my last comment in #j__m answer was correct. The problem was drawable folders.
I found this: https://stackoverflow.com/a/19196749/2528167
"Option #1: Just ship the -xxhdpi drawables and let Android downsample them for you at runtime (downside: will only work on fairly recent devices, where -xxhdpi is known)."
I had all my pictures in a xxhdpi folder in order to let Android downsample them at runtime, but as CommonsWare said this only work on recent devices, so I have filled drawable-**dpi folders and now OutOfMemoryError doesn't appear.
I'm re-writing an Android app where each activity (there are several) displays a background image. The user may change this image and so I've done the following:
Created MyAppApplication (extends Application), a reference to which is set up in onCreate() in each activity.
MyAppApplication has a public BitmapDrawable which is applied to the background on start.
Each activity listens for changes in SharedPreferences and re-loads the background image on such changes.
Here's part of the code I used to set the image, based on http://developer.android.com/training/displaying-bitmaps/load-bitmap.html:
Bitmap bitmap = decodeBitmap(R.drawable.background_image, screen_width, screen_height);
}
public BitmapDrawable backgroundImage = new BitmapDrawable(bitmap);
public Bitmap decodeBitmap(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(getResources(), resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(getResources(), resId, options); // crashes here
}
Then, in the activity, I set the background to backgroundImage.
The first time the app starts up this works, but if the shared preferences are changed then the application tries to decode the resource again and the app crashes at the point marked above. May I ask what I might do to avoid this?
every time you are done using a Bitmap, you should release them, since they take up a lot of memory.
in onDestroy() you should write something like:
bitmap.recycle();
bitmap = null;
you should also call these lines whenever you stop using the bitmap.
I'll accept the original answer as it is indeed correct that setting the bitmap to null is what is required. The problem was that I was setting it in the wrong place.
The error occurs in the application when the bitmap is read, but it is because of a bitmap in the activity. So, I had to do this in each activity to fix it:
layout.setBackgroundDrawable(null);
layout.setBackgroundDrawable(myApplication.getBackground());
I am creating an application with frame by frame animation using almost 150 image frames. The animation is played by changing the background image of an Image view using Handler. Inside the handler I am retrieving the images from the folder /mnt/sdcard/Android/data/ApplicationPackage and changing it dynamically as the image view background by the following way :
FileInputStream in;
in = new FileInputStream(mFrames.get(imgpos));
bitmap = BitmapFactory.decodeStream(in);
if (in != null)
{
in.close();
}
This creates some issues in decoding the file input stream since it takes a lot of time for some images to create the bitmap. Image file size is almost less than 40 KB for each images, but it takes different duration to decode the images from the external directory for the same size files. I have tried to sample the file size and load but it directly affects the image clarity. Can any one please suggest me what is the better way to load the images to a bitmap from the external folder and with the same duration for all the images?
Thanks, Tim
I think you must preload the bitmap before displaying them... Use an array of bitmap, and a background task that load all the images when entering your activity.
Your handler is absolutely not allowed to perform time-consuming tasks such as loading bitmaps !
You can downscale the images before creating the bitmap for them.
public Bitmap decodeSampledBitmapFromPath(String path)
{
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, requiredWidth, requiredHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
Bitmap newBmap = BitmapFactory.decodeFile(path, options);
return newBmap;
}
public int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight)
{
int inSampleSize = 1;
if (imageHeight > reqHeight || imageWidth > reqWidth)
{
if (imageWidth > imageHeight)
{
inSampleSize = Math.round((float)imageHeight / (float)reqHeight);
}
else
{
inSampleSize = Math.round((float)imageWidth / (float)reqWidth);
}
}
return inSampleSize;
}
If the images are a lot bigger than the view in which they are loaded, then this down scaling really helps create the bitmap faster. Also, the scaled Bitmap takes much less memory than directly creating the bitmap.
inJustDecodeBounds in BitmapFactory.Options when set to true lets you get the height and width before creating the Bitmap for it. So you can check if the images are larger than required. If yes, then scale them to the required height and width, and then create its Bitmap.
Hope this helps!
Obviously this is an expensive/time-consuming operation. Any way to improve this?
Bitmap bm = BitmapFactory.decodeStream((InputStream) new URL(
someUrl).getContent());
I'm guessing there's really no way to avoid this relatively intense operation, but wanted to see if anyone had any tweaks they could recommend (aside from caching the actual bitmap, which for whatever reasons simply isn't relevant here)
If you don't need the full resolution you can have it only read ever nth pixel, where n is a power of 2. You do this by setting inSampleSize on an Options object which you pass to BitmapFactory.decodeFile. You can find the sample size by just reading the meta-data from the file in a first pass by setting inJustDecodeBounds on the Options object. Aside from that - no, I don't think there's an easy way to make to go any faster than it already does.
Edit, example:
Options opts = new Options();
// Get bitmap dimensions before reading...
opts.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, opts);
int width = opts.outWidth;
int height = opts.outHeight;
int largerSide = Math.max(width, height);
opts.inJustDecodeBounds = false; // This time it's for real!
int sampleSize = ??; // Calculate your sampleSize here
opts.inSampleSize = sampleSize;
Bitmap bmp = BitmapFactory.decodeFile(path, opts);