Is it a good practice to catch OutOfMemoryError even you have tried some ways to reduce memory usage? Or should we just not catching the exception? Which one is better practice?
try {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 4;
bitmap = BitmapFactory.decodeFile(file, options);
} catch (OutOfMemoryError e) {
e.printStackTrace();
}
Thanks
It's good practice to catch it once and give decodeFile another chance. Catch it and call System.gc() and try decoding again. There is a high probability that it will work after calling System.gc().
try {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 4;
bitmap = BitmapFactory.decodeFile(file, options);
} catch (OutOfMemoryError e) {
e.printStackTrace();
System.gc();
try {
bitmap = BitmapFactory.decodeFile(file);
} catch (OutOfMemoryError e2) {
e2.printStackTrace();
// handle gracefully.
}
}
I did something like this: I catch the error only for try to scale down the image until it works. Eventually it can not work at all; then returns null; otherwise, in success, returns the bitmap.
Outside I decide what to do with the bitmap whether it's null or not.
// Let w and h the width and height of the ImageView where we will place the Bitmap. Then:
// Get the dimensions of the original bitmap
BitmapFactory.Options bmOptions= new BitmapFactory.Options();
bmOptions.inJustDecodeBounds= true;
BitmapFactory.decodeFile(path, bmOptions);
int photoW= bmOptions.outWidth;
int photoH= bmOptions.outHeight;
// Determine how much to scale down the image.
int scaleFactor= (int) Math.max(1.0, Math.min((double) photoW / (double)w, (double)photoH / (double)h)); //1, 2, 3, 4, 5, 6, ...
scaleFactor= (int) Math.pow(2.0, Math.floor(Math.log((double) scaleFactor) / Math.log(2.0))); //1, 2, 4, 8, ...
// Decode the image file into a Bitmap sized to fill the View
bmOptions.inJustDecodeBounds= false;
bmOptions.inSampleSize= scaleFactor;
bmOptions.inPurgeable= true;
do
{
try
{
Log.d("tag", "scaleFactor: " + scaleFactor);
scaleFactor*= 2;
bitmap= BitmapFactory.decodeFile(path, bmOptions);
}
catch(OutOfMemoryError e)
{
bmOptions.inSampleSize= scaleFactor;
Log.d("tag", "OutOfMemoryError: " + e.toString());
}
}
while(bitmap == null && scaleFactor <= 256);
if(bitmap == null)
return null;
For example, with an image of 3264x2448, the loop iterates 2 times on my phone, and then it works.
You'd want to catch it if you want to display either a smaller image / different image / show a custom error message to the user.
Your image access wrapper can catch these errors and return some custom error codes defined within your code; your activity that uses this code can decide what to do with the error code - warn user, force him to exit with a better error message than the one the android system would provide, etc.
Btw, you are not using the options variable in your sample code.
Though it might not be a good idea to catch OutOfMemoryError using try-catch. But, sometimes you have no choice, because all of us hate app crashes.
So, what you can do is
Catch OutOfMemoryError using try-catch
Since, after this error your activity may become unstable, restart it.
You may disable animations so that user doesn't know that activity is restarted.
You may put some extra data in intent to know that app was crashed during previous run.
How I did is:
try {
//code that causes OutOfMemoryError
} catch (Exception e) {
// in case of exception handle it
e.printStackTrace();
} catch (OutOfMemoryError oome)
{
//restart this activity
Intent i=this.getIntent();
i.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); //disable animation
//EXTRA_ABNORMAL_SHUTDOWN is user defined
i.putExtra(this.EXTRA_ABNORMAL_SHUTDOWN, true);
//put extra data into intent if you like
finish(); //and finish the activity
overridePendingTransition(0, 0);
startActivity(i); //then start it(there is also restart method in newer API)
return false;
}
And then on onCreate of Activity you can resume(something like this):
boolean abnormalShutdown=getIntent().getBooleanExtra(this.EXTRA_ABNORMAL_SHUTDOWN, false);
if (abnormalShutdown)
{
//Alert user for any error
//get any extra data you put befor restarting.
}
This approach saved my app.
Hope it helps you too!!
Related
In Activity-A, I am using the Surfaceview and the Bitmap as a background in it. When I go to onPause I release this and set it to null and made a explicit GC. It works fine. But When I come back to the same Activity-A huge heap of nearly 3MB is allocated to decode the bitmap. This is because I am decoding the bitmap after the GC.
I am fine in recycling the bitmap and GC process. But I am worried that the heap allocation is getting increased so as to process the same bitmap.
When I move the next Activity, I need the save it in some place which should not hold any space in heap and when I come back I should go and pick the image. Any idea how to achieve this ?
Following is the existing code
#Override
protected void onPause() {
// TODO Auto-generated method stub
super.onPause();
ourSurfaceView.pause();
Log.i("DragDrop", "In pause drag drop");
backGround.release();
optionSelected.release();
backgoundImage.recycle();
backgoundImage=null;
backGround=optionSelected=null;
if (tts != null) {
Log.i("DragDrop", "In pause drag drop stop");
tts.stop();
tts.shutdown();
}
System.gc();
}
public void run() {
// TODO Auto-generated method stub
// Log.i("Notice", "In run of mybringback");
if(backgoundImage == null){
try {
Log.i("MyBringBack", "In run of mybringback");
backgoundImage = Bitmap.createScaledBitmap(getAssetImage(getApplicationContext(),"backgroundhomepage"), (int) dWidth, (int) dHeight, true);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
ourHolder = getHolder();
while (isRunning) {
// Log.i("DragDrop", "ourHolder.getSurface().isValid()" + ourHolder.getSurface().isValid() );
if (!ourHolder.getSurface().isValid()){
continue;
}
canvas = ourHolder.lockCanvas();
screenCenterX = dWidth / 2;
screenCenterY = dHeight / 2;
canvas.drawBitmap(backgoundImage, 0, 0, null);
if (imagePublishDone) {
if(!welcomeDone){
message = "Drop your wish to panda";
tts.speak(message, TextToSpeech.QUEUE_FLUSH, null);
welcomeDone=true;
}
moveImageInEllipticalPath();
} else {
initialImagePublish();
}
centreReached = false;
ourHolder.unlockCanvasAndPost(canvas);
}
}
public Bitmap getAssetImage(Context context, String filename) throws IOException {
AssetManager assets = getApplicationContext().getResources().getAssets();
InputStream buffer = null;
try {
buffer = new BufferedInputStream((assets.open("drawable/" + filename + ".png")));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
BitmapFactory.Options options = new BitmapFactory.Options();
options.inTempStorage = new byte[16*1024];
options.inPurgeable = true;
Bitmap temp = BitmapFactory.decodeStream(buffer, null, options);
Bitmap finalImage = Bitmap.createScaledBitmap(temp, (int) dWidth, (int) dHeight, true);
temp.recycle();
temp=null;
return finalImage;
}
I think a better solution to your problem would be simply using the inPurgeable flag when decoding the Bitmap.
That way when the system needs to reclaim memory, it will internally "recycle" the bitmap but when the bitmap needs to be displayed again it will automatically load it in.
[EDIT]
There are several problems with the code you have.
The inTempStorage in your getAssetImage(...) is useless. This "optimization" is supposed to provide the decoder with memory that it would need for temporary calculations. If you don't provide it, the decoder will simply allocate it by itself. This is only an optimization if you amortize the cost of that allocation over multiple decodes. However, your solution does the allocation every time. Might as well let the decoder do that since it will know exactly how much memory it will need.
It seems like you're wasting A LOT of memory on scaling your bitmaps. getAssetImage(...) decodes a full size image which is then scaled, returned, and scaled again. That is a lot of extra work and memory. You should consider sampling the image via inSampleSize. To figure out the proper sampling factor you can use inJustDecodeBounds.
Finally, your use of inPurgeable is incorrect. The system will only be able to purge the bitmap decoded with that option. However, as mentioned above, you go ahead and create new bitmaps after loading in the purgeable one. Furthermore, even if you fix your design so that you only load in the correct bitmap, I don't think the system will be able to reload it for you because you use decodeStream(...). Streams cannot be reopened and rewound in a generic way so the system will not be able to reread that data. Try decoding from a file instead. You can also play with inInputShareable for more control over the purging behavior.
Days, I've spent working on this. Weeks, perhaps. Literally. :(
So I've got an image on an SD card that more than likely came out of the built-in camera. I want to take that image and downsample it to an arbitrary size (but always smaller and never larger). My code uses standard Android Bitmap methods to decode, resize, recompress, and save the image. Everything works fine as long as the final image is smaller than 3MP or so. If the image is larger, or if I try to do several of these at once, the application crashes with an OutOfMemoryError. I know why that's happening, and I know it's happening for a perfectly legitimate reason, I just want it to not happen anymore.
Look, I'm not trying to launch a rocket here. All I want to do is resize a camera image and dump it to an OutputStream or even a temporary file. Surely someone out there must have done such a thing. I don't need you to write my code for me, and I don't need my hand held. But between my various programming abortions and days of obsessed Googling, I don't even know which direction to head in. Roughly speaking, does anyone know how to decode a JPEG, downsample it, re-compress it in JPEG, and send it out on an OutputStream without allocating a massive amount of memory?
Ok I know it's a little bit late but, I had this problem and I found solution. It is actually easy and I am sure it supports back to api 10(I have no idea about before 10). I tried this with my phone. It is a samsung galaxy s2 with an 8mp camera and the code perfectly resized camera images to the 168x168 as well as images i found on web. I checked the images by using file manager too. I never tried resizing images to bigger resoulation.
private Bitmap resize(Bitmap bp, int witdh, int height){
return Bitmap.createScaledBitmap(bp, width, height, false);
}
you can save it like this
private void saveBitmap(Bitmap bp) throws FileNotFoundException{
String state = Environment.getExternalStorageState();
File folder;
//if there is memory card available code choose that
if (Environment.MEDIA_MOUNTED.equals(state)) {
folder=Environment.getExternalStorageDirectory();
}else{
folder=Environment.getDataDirectory();
}
folder=new File(folder, "/aaaa");
if(!folder.exists()){
folder.mkdir();
}
File file=new File(folder, (int)(Math.random()*10000)+".jpg");
FileOutputStream os=new FileOutputStream(file);
bp.compress(Bitmap.CompressFormat.JPEG, 90, os);
}
thanks to this link
The following code is from my previous project. Key point is "options.inSampleSize".
public static Bitmap makeBitmap(String fn, int minSideLength, int maxNumOfPixels) {
BitmapFactory.Options options;
try {
options = new BitmapFactory.Options();
options.inPurgeable = true;
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(fn, options);
if (options.mCancel || options.outWidth == -1
|| options.outHeight == -1) {
return null;
}
options.inSampleSize = computeSampleSize(
options, minSideLength, maxNumOfPixels);
options.inJustDecodeBounds = false;
//Log.e(LOG_TAG, "sample size=" + options.inSampleSize);
options.inDither = false;
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
return BitmapFactory.decodeFile(fn, options);
} catch (OutOfMemoryError ex) {
Log.e(LOG_TAG, "Got oom exception ", ex);
return null;
}
}
private static int computeInitialSampleSize(BitmapFactory.Options options,
int minSideLength, int maxNumOfPixels) {
double w = options.outWidth;
double h = options.outHeight;
int lowerBound = (maxNumOfPixels == UNCONSTRAINED) ? 1 :
(int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
int upperBound = (minSideLength == UNCONSTRAINED) ? 128 :
(int) Math.min(Math.floor(w / minSideLength),
Math.floor(h / minSideLength));
if (upperBound < lowerBound) {
// return the larger one when there is no overlapping zone.
return lowerBound;
}
if ((maxNumOfPixels == UNCONSTRAINED) &&
(minSideLength == UNCONSTRAINED)) {
return 1;
} else if (minSideLength == UNCONSTRAINED) {
return lowerBound;
} else {
return upperBound;
}
}
I read an image from sd card with BitmapFactory:
String myJpgPath = "/sdcard/yourdollar/img001.jpg";
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2;
Bitmap bm = BitmapFactory.decodeFile(myJpgPath, options);
If I write:
Log.i("width and height: ", bm.getWidth() + " " + bm.getHeight());
I get a nullpointer exception. I tried to scale with Bitmap.createScaledBitmap() but I get the same error. After it I am processing the bitmap so I would like to have a bitmap in the end that has any width or height, because it seems like I didn't give parameters for the bitmap. But I cannot scale it, so how can I get this image as a bitmap with a width of 500 and a height of 500?
***UPDATE
buttonClick = (Button) findViewById(R.id.buttonClick);
buttonClick.setOnClickListener(new OnClickListener() {
public void onClick(View v) { // <5>
preview.camera.takePicture(shutterCallback, rawCallback, jpegCallback);
Intent intentstart = new Intent(CameraActivity.this, Intent2.class);
startActivity(intentstart);
}
});
Okay here is the thing. This button takes a picture with the camera then change activity. If I do this way, the app does not have time to create the so when I use it in my second activity to read it, it throws nullpointerexception. So I got tricky (and wrong as well) and put:
preview.camera.takePicture(shutterCallback, rawCallback, jpegCallback);
try {
Thread.sleep(1200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Intent intentstart = new Intent(CameraActivity.this, Intent2.class);
startActivity(intentstart);
So it has 1.2 seconds to create the file, then change activity. Is there any way to check if the file created or not? Of course if its true then it should change activity.
Any suggestion?
First just check whether your image in your required directory /sdcard/yourdollar/img001.jpg And, Just try like this -
String myJpgPath = "/sdcard/yourdollar/img001.jpg";
Bitmap bm = BitmapFactory.decodeFile(myJpgPath);
and get image height & width in your Logcat
Log.i("width and height: ", bm.getWidth() + " " + bm.getHeight());
I am trying to store a bitmap (that i have previously read from a file) after decoding it in a preferable size using BitmapFactory.Options.inSampleSize. The problem is that the file size of the stored bitmap is at least double the size of the original file. I have searched a lot and could not find how i can deal with this, since i don't want it to happen for memmory efficiency (later i reuse the stored bitmap). Here is my method that does what i describe:
private Bitmap decodeFileToPreferredSize(File f) {
Bitmap b = null;
try {
// Decode image size
Log.i("Bitmap", "Imported image size: " + f.length() + " bytes");
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
BitmapFactory.decodeFile(f.getAbsolutePath(), o);
//Check if the user has defined custom image size
int scale = 1;
if(pref_height != -1 && pref_width != -1) {
if (o.outHeight > pref_height || o.outWidth > pref_width) {
scale = (int) Math.pow(
2,
(int) Math.round(Math.log(pref_width
/ (double) Math.max(o.outHeight, o.outWidth))
/ Math.log(0.5)));
}
}
// Decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize = scale;
b = BitmapFactory.decodeFile(f.getAbsolutePath(), o2);
String name = "Image_" + System.currentTimeMillis() + ".jpg";
File file = new File(Environment.getExternalStorageDirectory(), name);
FileOutputStream out = new FileOutputStream(file);
b.compress(Bitmap.CompressFormat.JPEG, 100, out);
out.close();
Log.i("Bitmap", "Exported image size: " + file.length() + " bytes");
} catch (Exception e) {
b = null;
}
return b;
}
UPDATE I saw the densities on the two image files. The one that came from camera intent has 72 dpi density for width and height. The image file created from my above method has 96 dpi density for width and height. This explains why a 0.5 MByte image that came form the camera is resized in approximatelly 2.5 MByte with my above method since the rescale factor is (96/72) * 2 ~= 2.5. For some reason, the bitmap i create does not take the density of the image that came from the camera. I tried to set the density with all variation of BitmapFactory.Options.inDensity but no luck. Also i tried to change the bitmap density with bitmap.setDensity(int dpi); but still no effect. So, my new question is if there is a way to define the density of the bitmap when the image is stored.
Thanks in advance.
I had a similar issue. When I downloaded images from web they used more space on the SD than they did when downloaded to my PC from browser. I think the issue is simply that BitmapFactory saves the images in a non optimzed format of some sort.
My workaround was to use following instead of the bitmapfactory:
try {
try {
is = yourinputstream;
// Consider reading stream twice and return the bitmap.
os = youroutputstream;
byte data[] = new byte[4096];
int count;
while ((count = is.read(data)) != -1) {
os.write(data, 0, count);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if(is != null) {
is.close();
}
if(os != null) {
os.close();
}
}
} catch (IOException e) {
e.printStackTrace();
}
You are correct that the problem is in a density change.
This thread revealed a solution: BitmapFactory returns bigger image than source
Before decoding also disable inScaled:
options.inSampleSize = 2; //or however you desire, power of 2
options.inScaled = false;
This will keep the density from changing and you will see the decrease in size you were looking for.
When BitmapFactory.Options.inSampleSize = 0.5, it does 100% / 0.5 = 200%.
What you want I think is BitmapFactory.Options.inSampleSize = 2, it does 100% / 2 = 50%
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