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.
Related
Im caching my Bitmaps in GridView to LruCache. I made manager for this, see below:
private LruCache<String, Bitmap> mMemoryCache;
public LruCacheManager(){
init();
}
private void init(){
// Get max available VM memory, exceeding this amount will throw an
// OutOfMemory exception. Stored in kilobytes as LruCache takes an
// int in its constructor.
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// Use 1/8th of the available memory for this memory cache.
final int cacheSize = maxMemory / 8;
//Log.i("ImageCache","cacheSize: " + cacheSize);
if(mMemoryCache == null){
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
#Override
protected int sizeOf(String key, Bitmap bitmap) {
// The cache size will be measured in kilobytes rather than
// number of items.
// The cache size will be measured in kilobytes rather than
// number of items.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
return bitmap.getByteCount() ;
} else {
return bitmap.getRowBytes() * bitmap.getHeight();
}
}
};
}
}
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null) {
Log.i("LruCacheManager","Bitmap is getting added, " + key);
mMemoryCache.put(key, bitmap);
}
}
public Bitmap getBitmapFromMemCache(String key) {
return mMemoryCache.get(key);
}
when I call addBitmapToMemoryCache() in my AsyncTask to save Bitmaps to MemoryCache.
But when i call getBitmapFromMemoryCache() its null.
//get cached Bitmap
LruCacheManager imCache = new LruCacheManager();
String imageKey = categoryNames[position];
Bitmap cachedBm = imCache.getBitmapFromMemCache(imageKey);
//Decide whatever use cached image or not
if (cachedBm != null) {
Log.i("AdapterGridView","Using cached image, " + imageKey);
viewHolder.icon.setImageBitmap(cachedBm);
} else {
//starts Asynctask to scale pictures and show them, happens off the main thread
new AsyncTaskImageLoader(viewHolder.icon, imageKey, mContext, imCache, mThumbIds[position]).execute();
}
Which means, AsyncTask is called again and again. In AsyncTask im adding the Bitmaps to LruCache. Because returned Bitmap is null, there is no Bitmap saved in LruCache. But i have no clue why.
I also searched online and it has maybe to do something with recycling/Garbage Collector.
So how can i properly load cached images?
Any help or clarification is appriciate.
EDIT:
I call this inside BaseAdapter in getView() method. I think it has something to do with it. For the first time, each image is added to Cache, but then, the first image is added like 10 times.
First I would set an arbitrary memory size and try with 1 image. The rest looks good... If what I have below doesn't work, give us printouts of your memory, etc. You might not have any.
In my version I get memory by
final int maxMemory = (int) (Runtime.getRuntime().maxMemory());
then set it by a fraction ( I think I picked an 8th)
I do the /1024 when I return get the size of, I do not do it for setting the memory. So if you have 1/1000 of the memory you think you have, that would be the likely issue..
The app I'm creating requires a number of images to be pulled from our server, and displayed on a page. The user can go into several different categories, and each will have their own images. The problem is after going to 2-3 categories (depending on how many images are in those categories) in a row, the app has no more memory and cannot display the Bitmaps without crashing.
What I'd like to be able to do is clear the memory every time the user goes to a new category so that the old category's images won't be stored in memory anymore, freeing up space for the relevant category's images. I'm not sure if this is a good way to do it, or even how to do it if it was.
If anyone has a better solution let me know. One idea that was thrown around was loading only ~20 images at once, and waiting until the user scrolls to the bottom before loading more, however since our customers are paying to have their images on the app, that would cause less traffic to certain images, so this is not the ideal solution. However it's not out of the question.
Here is the code I'm using to load the images:
EDIT: My Mistake I posted the wrong code, this is the real code I'm using:
#SuppressWarnings("deprecation")
public Drawable loadImageFromWebOperations(String url, String imagePath) {
try {
if(Global.couponBitmaps.get(imagePath) != null){
scaledHeight = Global.couponBitmaps.get(imagePath).getHeight();
return new BitmapDrawable(getResources(), Global.couponBitmaps.get(imagePath));
}
Drawable d = null;
File f = new File(getBaseContext().getFilesDir().getPath().toString() + "/" + imagePath + ".png");
if (f.exists()) {
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
int scaledWidth = 0;
try {
display.getSize(size);
scaledWidth = size.x;
} catch (java.lang.NoSuchMethodError ignore) {
scaledWidth = display.getWidth();
}
Bitmap bitmap = null;
BitmapScaler scaler = new BitmapScaler(f, scaledWidth);
bitmap = scaler.getScaled();
scaledHeight = bitmap.getHeight();
d = new BitmapDrawable(getResources(), bitmap);
Global.couponBitmaps.put(imagePath, bitmap);
} else {
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
int scaledWidth = 0;
try {
display.getSize(size);
scaledWidth = size.x;
} catch (java.lang.NoSuchMethodError ignore) {
scaledWidth = display.getWidth();
}
Bitmap bitmap = BitmapFactory.decodeStream((InputStream) new URL(url).getContent());
int height = bitmap.getHeight();
int width = bitmap.getWidth();
scaledHeight = (int) (((scaledWidth * 1.0) / width) * height);
f.getParentFile().mkdirs();
f.createNewFile();
OutputStream output = new FileOutputStream(f);
bitmap.compress(Bitmap.CompressFormat.PNG, 90, output);
output.close();
bitmap = Bitmap.createScaledBitmap(bitmap, scaledWidth, scaledHeight, false);
d = new BitmapDrawable(getResources(), bitmap);
Global.couponBitmaps.put(imagePath, bitmap);
}
return d;
} catch (MalformedURLException e) {
e.printStackTrace();
return null;
} catch (IOException e) {
e.printStackTrace();
return null;
} catch (OutOfMemoryError e){
e.printStackTrace();
return null;
}
}
If anyone knows if there is a more efficient way of loading the images, or if there is a way to clear the memory before drawing them, it would be greatly appreciated, thank you.
Since you are loading your Bitmaps from a server, you should probably use an image loading library.
Powerful libraries are for example:
Picasso, by square
UniversalImageLoader, by nostra13
They allow you to do pretty much anything with your Bitmap and during loading. Chaching is also supported.
UniversalImageLoader is slightly more powerful, Picasso is easier to use, in my opinion.
Bitmaps in android are a bit tricky.. My first app which required a large number of images in a gridview - I ran into a lot of OOM problems as well.
I ended up using nostra13's "Universal Image Loader" as it seemed to be the best solution for what I needed. It has a lot of built in features such as disk cache, memory cache, bitmap size, thread pool size, image scaling, etc. There are working examples too. :)
Nostra13 Universal Image Loader
There are a few things to keep in mind:
Recycle your bitmaps before displaying the next one else they will
pile up in memory and you'll get OOM
If using Universal Image Loader, make sure you use .bitmapConfig(Bitmap.Config.RGB_565) as it uses the least amount of memory per image.
If you plan on displaying a lot of images in a gridview or listview, the approach I used is to load two different images from your APIs, one being extremely small (100x100 ish) for thumbnail view, and the other being the full size image. This way you wont run out of memory when showing the thumbnails. Then only when the user clicks a thumbnail, will it load the the full size image for that position.
Hopefully this helps. :)
Every time you create new Drawable and BitmapDrawable use one Drawable and refresh image on it.
I am trying to restore the image from Native memory (using NDK,C/C++) but that returns me an Black Image.
What i am doing ::
1)get the image from Drawable
2)apply the rotation to the image
3)After rotation apply the grayscale effect to the image
4)At the end i am trying to save the grayscale image in SD Card
For all the above steps, i am referring this awesome lib,which have the native method to store and restore the images.
Please note image is being stored in the SD card but when i am trying to see the image,its totally black with no display at all.
My Java Implementation ::
public boolean onOptionsItemSelected(MenuItem item)
{
switch (item.getItemId())
{
case R.id.item_rotate_90:
options.inPreferredConfig = Config.ARGB_8888;
bitmapOrig = BitmapFactory.decodeResource(this.getResources(), R.drawable.sample_cam,options);
storeBitmap(bitmapOrig);
bitmapOrig.recycle();
rotateBitmap(90,_handler);
tempBmp=getBitmapAndFree();
bitmapWip = Bitmap.createBitmap(bitmapOrig.getWidth(),bitmapOrig.getHeight(),Config.ALPHA_8);
jniConvertToGray(tempBmp,bitmapWip);
if(bitmapWip!=null)
{
try
{
Bitmap b = Bitmap.createBitmap(bitmapWip.getWidth(),bitmapWip.getHeight(),Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(b);
Paint paint = new Paint();
ColorMatrix cm = new ColorMatrix();
ColorMatrixColorFilter f = new ColorMatrixColorFilter(cm);
paint.setColorFilter(f);
c.drawBitmap(bitmapWip, 0, 0, paint);
storeBitmap(b);
SaveGrayScaledImage(b);
b.recycle();
tempBmp.recycle();
} catch (IOException e) {
e.printStackTrace();
}
ivDisplay.setImageBitmap(bitmapWip);
}
break;
}
}
I have not make any changes in native method(means using the same method as this lib have for storing and restoring the image).
Saving image to SD Card ::
private void SaveGrayScaledImage(Bitmap finalBitmap)throws IOException
{
String imageFileName = "Temp" + "_gray";
File albumF = new File("/mnt/sdcard/","gray_img");
if(!albumF.exists())
{
albumF.mkdirs();
}
// File imageF = File.createTempFile(imageFileName, JPEG_FILE_SUFFIX,
// albumF);
File imageF = new File(albumF,imageFileName + ".jpeg");
if (imageF.exists()) {
imageF.delete();
imageF.createNewFile();
}
try {
FileOutputStream out = new FileOutputStream(imageF);
finalBitmap.compress(Bitmap.CompressFormat.JPEG, 100, out);
out.flush();
out.close();
} catch (Exception e) {
e.printStackTrace();
imageF = null;
}
}
While googling, i found that(may be i am wrong) image which returns for Native Memory have the ALPHA_8 bitmap config,so i convert the config ALPHA_8 t0 ARGB_8888,but the result is same.
Conversion of bitmap from ALPHA_8 to ARGB_8888 ::
Bitmap b = Bitmap.createBitmap(bitmapWip.getWidth(),bitmapWip.getHeight(),Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(b);
Paint paint = new Paint();
ColorMatrix cm = new ColorMatrix();
ColorMatrixColorFilter f = new ColorMatrixColorFilter(cm);
paint.setColorFilter(f);
c.drawBitmap(bitmapWip, 0, 0, paint);
StoreBimap funcation ::
public void storeBitmap(final Bitmap bitmap)
{
if(_handler!=null)
freeBitmap();
_handler=jniStoreBitmapData(bitmap);
}
I have no clue about where i was wrong. i have checked the lib methods and implmentation again and again to find the issue.
I have spent my many hours on this small issue and it really frustrating me.
Let me know please if you need anything else from my side.
Please help me to resolve this issue.
Many Thanks in Advance....
EDIT ::
bitmapHolder=new JniBitmapHolder();
final Options options=new Options();
BitmapFactory.decodeFile(picPath, options);
options.inJustDecodeBounds=true;
options.inPreferredConfig=Config.ARGB_8888;
prepareForDownsampling(options,192,256);
System.gc();
bmpGrayscale=BitmapFactory.decodeFile(picPath,options);
int width = bmpGrayscale.getWidth();
int height = bmpGrayscale.getHeight();
bitmapHolder.storeBitmap(bmpGrayscale);
bmpGrayscale.recycle();
Bitmap thumbnail = null;
int rotationInDegrees = 0;
if (picPath != null) {
Uri uri = Uri.parse(picPath);
ExifInterface exif = null;
try {
exif = new ExifInterface(uri.getPath());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
int rotation = exif.getAttributeInt(
ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_NORMAL);
rotationInDegrees = exifToDegrees(rotation);
}
rotationInDegrees = 90;
ByteBuffer _handler =null;
switch(rotationInDegrees)
{
case 90:
bitmapHolder.rotateBitmapCw90();
break;
case 180:
bitmapHolder.rotateBitmap180();
break;
}
Bitmap bitmapWip = Bitmap.createBitmap(width,height,Config.ALPHA_8);
bitmapHolder.bitmapGrayScale(bitmapWip);
if(bitmapWip!=null){
File CurrentFile = saveGrayScaledIamge(bitmapWip,
takePhotoFile);
}
I have followed your suggestion/steps but the result is same,getting black image with no display.
ok I've found multiple problems and tips for improvements:
the first createBitmap is run with width*height on a bitmap that got rotated instead of height*width. this should be as:
rotateBitmap(90,_handler);
tempBmp=getBitmapAndFree();
bitmapWip=Bitmap.createBitmap(bitmapOrig.getHeight(),bitmapOrig.getWidth(),Config.ALPHA_8);
when saving file you don't get the correct path (you use a hardcoded path, and Lint warns about it).
jniConvertToGray doesn't really need to go over arrays and can just use a pointer, as it just runs on a single pixel. you store the bitmap into JNI twice instead of once (just do: store, rotate, grayscale, restore&free).
you don't use the new bitmap after you have finished working on it, so if I call rotation multiple times, it doesn't seem to do anything.
you already have bitmapWip rotated and grayscaled. why do you need to make a new bitmap that has its content in it, do a grayscale on it, and then save it ?
functions should be named with lowercase letter in the beginning of their names.
and finally , the most important thing: you use ALPHA_8 for the image that you show and need to save to file. this configuration has no color. it's a mask. In order to see the problem, you should set a background color to the imageView :
ivDisplay.setBackgroundColor(0xFFff0000);
before choosing the rotation, you see nothing red. after choosing it, everything you think is white, has actually become red. that's because it's transparent...
If in any phase of your development you've succeeded saving the image to a file and thought it's a black image (yet the size is not 0) , try to edit it and put a background behind it. Maybe you got lucky and just got transparent pixels...
Adding the fact that you save the file to a jpg format, which doesn't support transparency, might also contribute to unexpected behaviors.
in order to solve this, you should use the same technique i've used - use a single bitmap all the time. no need to create so many. only one should exist on the java world, and it should support having colors.
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
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!!