Sanat Pandey
Sanat Pandey

Reputation: 4103

Showing large images on view pager from sdcard in android

I have a problem that I have 80 to 150 images of very High Resolution and I have to show them in a view pager. I had written the simple mechanism of decoding but get OutofMemory Error after the 2nd Page. I tried very much but unable to find the exact solution to avoid that. My own suggestion is that if we will able to load a image in parts on an Image View then perhaps we avoid this but I don't know how to achieve this. Please suggest any solution regarding this.

Code:

private class MyPagerAdapter extends PagerAdapter {

        @Override
        public int getCount() {
            return Integer.parseInt(pagesCount);
        }


        @Override
        public boolean isViewFromObject(View arg0, Object arg1) {
            return arg0==((ImageView)arg1);
        }
        @Override
        public void destroyItem(View container, int position, Object object) {
             View view = (View)object;
                ((ViewPager) container).removeView(view);
                view = null;
        }
        @Override
        public View instantiateItem(View container, final int position) {
            final ImageView imgPage = new ImageView(getApplicationContext());
            /*imgPage.setImageURI(Uri.withAppendedPath(
                    Uri.parse(Environment.getExternalStorageDirectory()+"/MAGZ/"+magName+issueName), "" + (issueName+position)+".png"));*/
            //imgPage.setImageBitmap(BitmapFactory.decodeFile(Environment.getExternalStorageDirectory()+"/MAGZ/"+magName+issueName+"/"+(issueName+position)));
            //destroyItem(container, position, imgPage);
            imgPage.setScaleType(android.widget.ImageView.ScaleType.FIT_XY);
            imgPage.setImageDrawable(getImageFromSdCard(issueName+position));
            //imgPage.setImageResource(getImageFromSdCard(issueName+position));


            ((ViewPager) container).addView(imgPage,0); 
            return imgPage;
        }
    }


    public Drawable getImageFromSdCard(String imageName) {
        Drawable d = null;
        try {
            String path = Environment.getExternalStorageDirectory()+"/MAGZ/"+magName+"/"+magName+issueName;
            bitmap = BitmapFactory.decodeFile(path + "/" + imageName
                    + ".png");

           // d = new BitmapDrawable(bitmap);
            d = new BitmapDrawable(decodeFile(new File(path+"/"+imageName+".png")));
           // bitmap.recycle();
            bitmap = null;
        } catch (IllegalArgumentException e) {
            bitmap = null;
        }
        return d;

    }


    //decodes image and scales it to reduce memory consumption
    private Bitmap decodeFile(File f){
        try {
            //Decode image size
            BitmapFactory.Options o = new BitmapFactory.Options();
            o.inJustDecodeBounds = true;
            BitmapFactory.decodeStream(new FileInputStream(f),null,o);

            //The new size we want to scale to
            final int REQUIRED_SIZE=550;

            //Find the correct scale value. It should be the power of 2.
            int scale=1;
            while(o.outWidth/scale/2>=REQUIRED_SIZE && o.outHeight/scale/2>=REQUIRED_SIZE)
                scale*=2;

            //Decode with inSampleSize
            BitmapFactory.Options o2 = new BitmapFactory.Options();
            o2.inSampleSize=scale;
            return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
        } catch (FileNotFoundException e) {}
        return null;
    }

Thanks in advance.

Upvotes: 1

Views: 2255

Answers (3)

himanshurb
himanshurb

Reputation: 1115

inJustDecodeBounds & inSampleSize

You are decoding high resolution images at each PagerAdapter call, and if you are willing to reduce the decoded bitmap memory by powers of 2 (x/2, x/4 ... x : original bitmap size), then go for this method


Bitmap.recycle()

A very handy method, when used at the right place, can work wonders. I am assuming that you are setting a BitmapDrawable or calling setImageBitmap to an ImageView. Add the following snippet in destroyItem() callback of PagerAdapter.

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    View view = (View) object;
    ImageView imageView = (ImageView) view.findViewById(R.id.image_view);
    Drawable drawable = imageView.getDrawable();
    if(drawable instanceof BitmapDrawable) {
        BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
        if(bitmapDrawable != null) {
            Bitmap bitmap = bitmapDrawable.getBitmap();
            if(bitmap != null && !bitmap.isRecycled()) bitmap.recycle();    
        }
    }
    ((ViewPager) container).removeView(view);
}

The idea is that when any child view is removed from Pager, its bitmap should get recycled, that way you would not depend on GC calls to clear memory for you.


largeHeap = true

Add this property in Manifest.xml <application> tag. Wherever largeHeap is supported, it will increase heap memory to some large value, even goes till 8 times.


Also, in general don't hold any silly references to any bitmap, just decode them and assign it to View, rest will be taken care of.

Hope that helps. :)

Upvotes: 2

Magnus
Magnus

Reputation: 1502

Remove the line bitmap = BitmapFactory.decodeFile(path + "/" + imageName + ".png"); from your Drawable getImageFromSdCard(String imageName) method to begin with. Also remove the bitmap variable in that method since it´s unused, now you have removed unnecessary allocations, the problem you experience could be that the GC simply does not cope with the amount of garbage / allocations that was made since you did twice as many that was needed. The method should look something like:

public Drawable getImageFromSdCard(String imageName) {
    Drawable d = null;
    String path = Environment.getExternalStorageDirectory()+"/MAGZ/"+magName+"/"+magName+issueName;
    d = new BitmapDrawable(decodeFile(new File(path+"/"+imageName+".png")));
    return d;
}

Also reuse your o variable in decodeFile method just remember to flip the boolean inJustDecodeBounds after the first call.

Also do not catch runtime exceptions, bad practice, they're there because of just runtime errors. You´re hiding the real problem by catching them.

BitmapDrawable(Bitmap bitmap) constructor has been deprecated since api version 4, checkout BitmapDrawable for what constructor you should be using.

Upvotes: 0

Stan
Stan

Reputation: 6551

I'll suggest you to look at official android developers sample code called BitmapFun and use it for your purpose. Actually you missed o2.inPurgable=true; Also there is no need to use o and o2, o is good enough.

 //decodes image and scales it to reduce memory consumption
    private Bitmap decodeFile(File f){
        try {
            //Decode image size
            BitmapFactory.Options o = new BitmapFactory.Options();
            o.inJustDecodeBounds = true;
            BitmapFactory.decodeStream(new FileInputStream(f),null,o);

            //The new size we want to scale to
            final int REQUIRED_SIZE=550;

            //Find the correct scale value. It should be the power of 2.
            int scale=1;
            while(o.outWidth/scale/2>=REQUIRED_SIZE && o.outHeight/scale/2>=REQUIRED_SIZE)
                scale*=2;

            //Decode with inSampleSize
            //BitmapFactory.Options o2 = new BitmapFactory.Options();
            o.inSampleSize=scale;
            o.inJustDecodeBounds = false;
            o.inPurgealbe = true;
            return BitmapFactory.decodeStream(new FileInputStream(f), null, o);
        } catch (FileNotFoundException e) {}
        return null;
    }

Upvotes: 0

Related Questions