Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android ViewPager with images: memory leak/app crashes

I am writing an app that displays a panorama picture that eventually has several markers on it to display information about certain points.

As the large image crashed the app (I have also another activity in the app that is displaying a large map), I am now trying to display the panorama as a sequence of pages with ViewPager.

I have managed to display the picture in 6 bits and I thought things were going well, but now the app crashes after a few swipes (about 7 to 8) as the memory runs out.

I am pulling my hair out as to why that is as I thought my items would get destroyed once they are off the screen?? I am an absolute noobs and I'm so sorry if I'm being a time waster. I have spent all day reading and trying out solutions from here and elsewhere and I am none the wiser.

here is my code: activity PanoramaView

public class PanoramaView extends Activity {

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.panorama);
    MyPagerAdapter adapter = new MyPagerAdapter();
    ViewPager myPager = (ViewPager) findViewById(R.id.mysixpanelpager);
    myPager.setAdapter(adapter);
    myPager.setCurrentItem(2);
}


}

MyPagerAdapter

public class MyPagerAdapter extends PagerAdapter {
    public int getCount() {
        return 6;
    }
    public Object instantiateItem(View collection, int position) {
        LayoutInflater inflater = (LayoutInflater) collection.getContext()
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        int resId = 0;
        switch (position) {
        case 0:
            resId = R.layout.farleft;
            break;
        case 1:
            resId = R.layout.left;
            break;
        case 2:
            resId = R.layout.middle;
            break;
        case 3:
            resId = R.layout.right;
            break;
        case 4:
            resId = R.layout.farright;
            break;
        case 5:
            resId = R.layout.farfarright;
            break;
        }
        //ImageView imageView = new ImageView(getApplicationContext());
        //imageView.findViewById(R.id.imageView);
        //imageView.setImageBitmap(BitmapFactory.decodeResource(getResources(), ids[position]));

        View view = inflater.inflate(resId, null);
        ((ViewPager) collection).addView(view, 0);
        return view;
    }
    @Override
    public void destroyItem(View collection, int position, Object o) {
        View view = (View)o;
        ((ViewPager) collection).removeView(view);
        view = null;
    }

    @Override
    public boolean isViewFromObject(View arg0, Object arg1) {
        return arg0 == ((View) arg1);
    }
    @Override
    public Parcelable saveState() {
        return null;
    }
}

My main layout file

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<android.support.v4.view.ViewPager
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/mysixpanelpager"/>
</LinearLayout>

I promise I will be a helpful member from now on (or even more so once I actually know what I'm doing).

EDIT: - In the first activity, I'm displaying an image that is 552kb. - The six images that I'm displaying in this activity (PanoramaView) are between 309 and 500kb. - I have used an allocation tracker in Eclipse and all I could see was the memory filling up but the exact data wasn't clear to me - the crash happens after displaying 7 or 8 images (basically after swiping back and fourth several times)

Here is the code for farfarright.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >

<ImageView 
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/imageView"
    android:src="@drawable/panorama6"
    android:adjustViewBounds="true"
    android:contentDescription="@string/panorama" >

</ImageView>
</LinearLayout>

I have tried setting the off screen page limit which didn't help.

I found this link about memory management on another post and I'll have a look at it tonight.

EDIT: here is the LogCat output

11-28 21:17:42.551: D/dalvikvm(328): GC_EXTERNAL_ALLOC freed 51K, 53% free 2558K/5379K, external 2002K/2137K, paused 65ms
11-28 21:17:43.261: D/dalvikvm(328): GC_EXTERNAL_ALLOC freed 1K, 53% free 2557K/5379K, external 3297K/4118K, paused 44ms
11-28 21:17:47.741: W/KeyCharacterMap(328): No keyboard for id 0
11-28 21:17:47.741: W/KeyCharacterMap(328): Using default keymap: /system/usr/keychars/qwerty.kcm.bin
11-28 21:17:49.141: D/DFSAPP(328): my button id before is 2
11-28 21:17:49.691: D/dalvikvm(328): GC_EXTERNAL_ALLOC freed 36K, 52% free 2614K/5379K, external 15576K/15708K, paused 50ms
11-28 21:17:54.571: D/dalvikvm(328): GC_EXTERNAL_ALLOC freed 12K, 52% free 2616K/5379K, external 17386K/17735K, paused 39ms
11-28 21:17:54.661: D/dalvikvm(328): GC_EXTERNAL_ALLOC freed 0K, 52% free 2616K/5379K, external 17386K/17735K, paused 61ms
11-28 21:17:54.711: I/dalvikvm-heap(328): Clamp target GC heap from 25.629MB to 24.000MB
11-28 21:17:54.711: D/dalvikvm(328): GC_FOR_MALLOC freed <1K, 52% free 2616K/5379K, external 18975K/21023K, paused 42ms
11-28 21:18:03.751: D/dalvikvm(328): GC_EXTERNAL_ALLOC freed 6K, 52% free 2616K/5379K, external 18269K/20317K, paused 46ms
11-28 21:18:03.822: I/dalvikvm-heap(328): Clamp target GC heap from 25.628MB to 24.000MB
11-28 21:18:03.852: D/dalvikvm(328): GC_FOR_MALLOC freed <1K, 52% free 2615K/5379K, external 18975K/20317K, paused 32ms
11-28 21:18:04.131: D/dalvikvm(328): GC_EXTERNAL_ALLOC freed <1K, 52% free 2615K/5379K, external 17386K/19434K, paused 49ms
11-28 21:18:04.191: I/dalvikvm-heap(328): Clamp target GC heap from 25.628MB to 24.000MB
11-28 21:18:04.201: D/dalvikvm(328): GC_FOR_MALLOC freed 0K, 52% free 2615K/5379K, external 18975K/19434K, paused 34ms
11-28 21:18:07.301: D/dalvikvm(328): GC_EXTERNAL_ALLOC freed 1K, 52% free 2616K/5379K, external 18269K/19434K, paused 46ms
11-28 21:18:07.381: I/dalvikvm-heap(328): Clamp target GC heap from 25.801MB to 24.000MB
11-28 21:18:07.401: D/dalvikvm(328): GC_FOR_MALLOC freed <1K, 52% free 2616K/5379K, external 19152K/19434K, paused 38ms
11-28 21:18:07.611: D/dalvikvm(328): GC_EXTERNAL_ALLOC freed <1K, 52% free 2615K/5379K, external 18159K/19434K, paused 47ms
11-28 21:18:07.681: I/dalvikvm-heap(328): Clamp target GC heap from 25.801MB to 24.000MB
11-28 21:18:07.681: D/dalvikvm(328): GC_FOR_MALLOC freed 0K, 52% free 2615K/5379K, external 19152K/19434K, paused 36ms
11-28 21:18:18.901: D/dalvikvm(328): GC_EXTERNAL_ALLOC freed 5K, 52% free 2616K/5379K, external 18269K/19434K, paused 57ms
11-28 21:18:18.972: I/dalvikvm-heap(328): Clamp target GC heap from 25.802MB to 24.000MB
11-28 21:18:18.991: D/dalvikvm(328): GC_FOR_MALLOC freed <1K, 52% free 2616K/5379K, external 19152K/19434K, paused 33ms
11-28 21:18:19.181: D/dalvikvm(328): GC_EXTERNAL_ALLOC freed 1K, 52% free 2615K/5379K, external 18159K/19434K, paused 55ms
11-28 21:18:19.251: I/dalvikvm-heap(328): Clamp target GC heap from 25.801MB to 24.000MB
11-28 21:18:19.251: D/dalvikvm(328): GC_FOR_MALLOC freed 0K, 52% free 2615K/5379K, external 19152K/19434K, paused 33ms
11-28 21:18:21.551: D/dalvikvm(328): GC_EXTERNAL_ALLOC freed 1K, 52% free 2616K/5379K, external 18975K/19434K, paused 46ms
11-28 21:18:21.581: E/dalvikvm-heap(328): 1627200-byte external allocation too large for this process.
11-28 21:18:21.621: I/dalvikvm-heap(328): Clamp target GC heap from 25.629MB to 24.000MB
11-28 21:18:21.621: E/GraphicsJNI(328): VM won't let us allocate 1627200 bytes
11-28 21:18:21.631: D/dalvikvm(328): GC_FOR_MALLOC freed 0K, 52% free 2616K/5379K, external 18975K/19434K, paused 34ms
11-28 21:18:21.641: D/AndroidRuntime(328): Shutting down VM
11-28 21:18:21.641: W/dalvikvm(328): threadid=1: thread exiting with uncaught exception (group=0x40015560)
11-28 21:18:21.732: E/AndroidRuntime(328): FATAL EXCEPTION: main
11-28 21:18:21.732: E/AndroidRuntime(328): android.view.InflateException: Binary XML file line #7: Error inflating class <unknown>
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.view.LayoutInflater.createView(LayoutInflater.java:518)
11-28 21:18:21.732: E/AndroidRuntime(328):  at com.android.internal.policy.impl.PhoneLayoutInflater.onCreateView(PhoneLayoutInflater.java:56)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:568)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.view.LayoutInflater.rInflate(LayoutInflater.java:623)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.view.LayoutInflater.inflate(LayoutInflater.java:408)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.view.LayoutInflater.inflate(LayoutInflater.java:320)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.view.LayoutInflater.inflate(LayoutInflater.java:276)
11-28 21:18:21.732: E/AndroidRuntime(328):  at com.businesbike.dfp.MyPagerAdapter.instantiateItem(MyPagerAdapter.java:43)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.support.v4.view.ViewPager.addNewItem(ViewPager.java:692)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.support.v4.view.ViewPager.populate(ViewPager.java:849)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.support.v4.view.ViewPager.populate(ViewPager.java:772)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.support.v4.view.ViewPager.completeScroll(ViewPager.java:1539)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.support.v4.view.ViewPager.computeScroll(ViewPager.java:1422)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.view.ViewGroup.drawChild(ViewGroup.java:1562)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1373)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.view.ViewGroup.drawChild(ViewGroup.java:1644)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1373)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.view.View.draw(View.java:6883)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.widget.FrameLayout.draw(FrameLayout.java:357)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.view.ViewGroup.drawChild(ViewGroup.java:1646)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1373)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.view.ViewGroup.drawChild(ViewGroup.java:1644)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1373)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.view.View.draw(View.java:6883)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.widget.FrameLayout.draw(FrameLayout.java:357)
11-28 21:18:21.732: E/AndroidRuntime(328):  at com.android.internal.policy.impl.PhoneWindow$DecorView.draw(PhoneWindow.java:1862)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.view.ViewRoot.draw(ViewRoot.java:1522)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.view.ViewRoot.performTraversals(ViewRoot.java:1258)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.view.ViewRoot.handleMessage(ViewRoot.java:1859)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.os.Handler.dispatchMessage(Handler.java:99)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.os.Looper.loop(Looper.java:123)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.app.ActivityThread.main(ActivityThread.java:3683)
11-28 21:18:21.732: E/AndroidRuntime(328):  at java.lang.reflect.Method.invokeNative(Native Method)
11-28 21:18:21.732: E/AndroidRuntime(328):  at java.lang.reflect.Method.invoke(Method.java:507)
11-28 21:18:21.732: E/AndroidRuntime(328):  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
11-28 21:18:21.732: E/AndroidRuntime(328):  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
11-28 21:18:21.732: E/AndroidRuntime(328):  at dalvik.system.NativeStart.main(Native Method)
11-28 21:18:21.732: E/AndroidRuntime(328): Caused by: java.lang.reflect.InvocationTargetException
11-28 21:18:21.732: E/AndroidRuntime(328):  at java.lang.reflect.Constructor.constructNative(Native Method)
11-28 21:18:21.732: E/AndroidRuntime(328):  at java.lang.reflect.Constructor.newInstance(Constructor.java:415)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.view.LayoutInflater.createView(LayoutInflater.java:505)
11-28 21:18:21.732: E/AndroidRuntime(328):  ... 36 more
11-28 21:18:21.732: E/AndroidRuntime(328): Caused by: java.lang.OutOfMemoryError: bitmap size exceeds VM budget
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.graphics.Bitmap.nativeCreate(Native Method)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.graphics.Bitmap.createBitmap(Bitmap.java:477)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.graphics.Bitmap.createBitmap(Bitmap.java:444)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.graphics.Bitmap.createScaledBitmap(Bitmap.java:349)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.graphics.BitmapFactory.finishDecode(BitmapFactory.java:498)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:473)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:336)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.graphics.drawable.Drawable.createFromResourceStream(Drawable.java:697)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.content.res.Resources.loadDrawable(Resources.java:1709)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.content.res.TypedArray.getDrawable(TypedArray.java:601)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.widget.ImageView.<init>(ImageView.java:118)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.widget.ImageView.<init>(ImageView.java:108)
11-28 21:18:21.732: E/AndroidRuntime(328):  ... 39 more

In case someone comes across this post, here is what seems to have fixed it: I have change the code to the code from the answer below and I have also cut up my panorama picture into smaller bits so each image is now below 300kb.

like image 407
Workbarby Avatar asked Nov 27 '12 14:11

Workbarby


4 Answers

A bit late with the answer, but the issue is quite simple actually, and I don't see other answers targeting it.

Thing is, when you manually decode bitmaps (as you do in those commented out lines), you have to .recycle() them yourself. So, back to your code, in your adapter you have to complement this:

@Override
public Object instantiateItem(View collection, int position) {
    // ...
    View view = inflater.inflate(resId, null);
    ImageView imageView = (ImageView) view.findViewById(R.id.imageView);
    imageView.setImageBitmap(BitmapFactory.decodeResource(getResources(),
            ids[position]));
    ((ViewPager) collection).addView(view, 0);
    return view;
}

With this -- recycle the Bitmap in addition to removing the View from hierarchy:

@Override
public void destroyItem(View collection, int position, Object o) {
    View view = (View)o;
    ImageView imgView = (ImageView) view.findViewById(R.id.imageView);
    BitmapDrawable bmpDrawable = (BitmapDrawable) imgView.getDrawable();
    if (bmpDrawable != null && bmpDrawable.getBitmap() != null) {
            // This is the important part
            bmpDrawable.getBitmap().recycle();
    }
    ((ViewPager) collection).removeView(view);
    view = null;
}

It's as simple as that, no need to use separate bitmap management or anything.

like image 166
Ivan Bartsov Avatar answered Nov 18 '22 17:11

Ivan Bartsov


Well documented question, and even though you seem to have resolved it, here is a tiny bit of potentially useful information.

From you logcat, I detect that you are developing (or at least testing) on Gingerbread (because Honeycomb onwards does not include the "external" part in the output. This is important only because it highlights what is happening to your bitmaps. In Gingerbread, bitmap data is placed in native memory, outside of the heap. Then, a pointer to that data is placed within the heap, along with some other referential information. Deleting every reference to the bitmap will release the referential information (at some point, when System.gc() runs). However, the bitmap data will never be released, unless your device is hit by an asteroid -- or (less dramatic) you call the recycle() method on that bitmap. That call, it should be noted, really does release the native memory, so you really do need to re-create the bitmap from scratch when it is needed again by the ViewPager (most likely in the instantiateItem. Honeycomb and onward place the bitmap data within the stack, which is slightly less annoying. The data is still released only when you recycle() that bitmap (so you have to dig a bit deeper, using DDMS in order to determine what's going on -- for example, I'm currently fighting this same problem on JB, and in ICS, 4.0.3 behaves differently than 4.0.4, but I digress).

It is probably overkill, but my solution was to implement a class to track my bitmaps, and be certain that I had recycled them -- in your case, this would happen when you destroyItem() in the ViewPager.

Here's the (somewhat pedestrian) class I use to track things, fwiw.

public class BitmapManager {

private final String TAG = "DEBUG -- " + ClassUtils.getShortClassName(this.getClass());
Context mContext = null;

private class BitmapVectorEntry {
    public Bitmap bm = null;
    public String name = null;
}

Vector<BitmapVectorEntry> mBitmapVector = new Vector<BitmapVectorEntry>();

public BitmapManager( Context aContext ) {
    mContext = aContext;
}

public void setContext( Context aContext ) {
    mContext = aContext;
}

public void registerBitmap( String name, Bitmap b) {

    if(mBitmapVector == null) {
        mBitmapVector = new Vector<BitmapVectorEntry>();
    }
    if(b == null) {
        Log.e(TAG, "Bitmap is NULL!!  ");
        return;
    }
    // Log.d(TAG, "        ~~~~~~ Registering ["+name+"]  ["+b+"]");
    BitmapVectorEntry be = new BitmapVectorEntry();
    be.bm = b;
    be.name = name;
    mBitmapVector.add(be);
}

public void registerBitmapForBackgroundDrawable( String name, View v ) {
    if(v != null) {
        Drawable d = v.getBackground();
        if(d != null) {
            if(d instanceof BitmapDrawable) {
                Bitmap bm = ((BitmapDrawable) d).getBitmap();
                if(bm != null) {
                    // Log.w(TAG, "     ~~~~  Registering Background Bitmap [" + bm + "]");
                    registerBitmap(name, bm);
                }  else  {
                    Log.w(TAG, " ~~~~ Background does not have a bitmap in the BitmapDrawable (Probably, but not necessarily, and error)");
                }
            }  else  {
                Log.w(TAG, " ~~~~ Background does not have a BitmapDrawable (Might not be an error)");
            }
        }  else  {
            Log.w(TAG, " ~~~~~ Background is null, no drawable (Might not be an error)");
        }
    } else  {
        Log.e(TAG, "  ~~~~~ View is null, is there no background for this view?");
    }
}

// We cannot recycle certain bitmaps, like the background for the page which houses
    // the ViewPager, since the pager reuses it, so we just delete it from vector
public void clear(Bitmap bm) {
    removeBitmap(bm, false);
}

// In most cases, when we are done with a bitmap, we want to recycle it.  This is a
    // synchronous call that frees external heap (in 2.3.x) or internal heap (3.x < ).
    // And when I say 'synchronous' I mean 'slow' and 'should not be run on the UI Thread,
    // So be sure to throw this on an async thread
public void recycleBitmap(Bitmap bm) {
    removeBitmap(bm, true);
}

private void removeBitmap(Bitmap bm, boolean andRecycleToo) {

    if(bm == null) {
        Log.e(TAG, "(RECYCLE BITMAP)  !!!! Bitmap is NULL!!, cannot recycle");
        return;
    }
    if(mBitmapVector == null) {
        Log.e(TAG, "(RECYCLE BITMAP)  !!!! Bitmap Vector is NULL!!");
        return;
    }

    boolean foundIt = false;
    Bitmap targetBm = null;
    int i = (mBitmapVector.size() - 1);
    try {
        for(; i >= 0; i--)  {
            BitmapVectorEntry b = mBitmapVector.get(i);
            targetBm = b.bm;
            if(targetBm.equals(bm)) {
                foundIt = true;
                if(andRecycleToo) {
                    if(!targetBm.isRecycled()) {
                        targetBm.recycle();
                    }
                }
                mBitmapVector.removeElementAt(i);
                // Log.e(TAG, "       Recycling ["+targetBm.name+"]  ["+targetBm.bm+"]");
                break;
            }
        }
    }  catch(Exception e) {

        Log.e(TAG, "Exception during recycling bitmap position ["+i+"] ["+bm+"] ["+e+"]");

    }  finally {

        mBitmapVector.trimToSize();
        if(andRecycleToo) {
            if(!foundIt && targetBm != null) {
                if(!targetBm.isRecycled())  {
                    targetBm.recycle();
                } 
                Log.e(TAG, "(RECYCLE BITMAP)   ========================= !!! RECYCLING Bitmap ["+targetBm+"], was unregistered, recycled is ["+targetBm.isRecycled()+"]");
            }  else  {
                // Log.i(TAG, "(RECYCLE BITMAP)   ========================= !!! RECYCLING Bitmap ["+targBe.name+"] ["+targBe.bm+"], was registered");
            }
        }

    }

}

public void flush() {
    if(mBitmapVector == null) {
        // Log.e(TAG, "!!!! Bitmap Vector is NULL!!");
        return;
    }
    for(int i = 0; i < mBitmapVector.size(); i++) {
        BitmapVectorEntry bme = mBitmapVector.get(i);
        if(!bme.bm.isRecycled()) {
            // Log.e(TAG, "Flushing Bitmap ["+bme.name+"] ["+bme.bm+"]");
            bme.bm.recycle();
        }
    }
    mBitmapVector.clear();
    mBitmapVector.trimToSize();

}

public void dumpBitmaps() {
    if(mBitmapVector == null) {
        // Log.e(TAG, "!!!! Bitmap Vector is NULL!!");
        return;
    }
    boolean foundOne = false;
    for(int i = 0; i < mBitmapVector.size(); i++) {
        Bitmap bm0 = mBitmapVector.get(i).bm;
        if(!bm0.isRecycled()) {
            foundOne = true;
            break;
        }
    }
    if(mBitmapVector.size() > 0 && foundOne) {
        Log.e(TAG, " ========= Dumping Bitmap Vector === (Found a leaker) ===== ");
        Log.e(TAG, "            "+mBitmapVector.size()+" entries");
        for(BitmapVectorEntry b : mBitmapVector) {
            if(!b.bm.isRecycled()) {
                Log.e(TAG, "       ["+b.name+"]  ["+b.bm+"]  Recycled ["+b.bm.isRecycled()+"]");
            }
        }
        Log.e(TAG, " ========= End of Bitmap Dump    ======== ");
    }
}
}

The key bit is the dumpBitmaps() call (at least for the problem you describe above). If diligently registers all bitmaps, then the dumpBitmaps() call will expose any which require mopping up. If you don't care about where the leak is, and just want it to go away, then you can just call flush() which will remove all the bitmaps.

You will need to place registerBitmap() wherever you create the bitmap. I have had terrible luck with the inflater doing unpredictable things, so I prefer something like:

    public Drawable getPreformattedFile() {
    // Log.d(TAG, "Loading in Drawable ["+preformattedFileName()+"]");
    if( preformattedFileName() == null) {
        Log.e(TAG, "Formatted Filename is null");
        return(null);
    }

    Drawable ret = null;
    try {

        BitmapFactory.Options opts = new BitmapFactory.Options();
        opts.inScaled = false;
        opts.inPurgeable = true;

        Bitmap bm = BitmapFactory.decodeFile(preformattedFileName(), opts);
        if(bm == null) {
            return(null);
        }
        mBitmapManager.registerBitmap(mItem.name(), bm);
        ret = new BitmapDrawable(mContext.getResources(), bm);

        // Log.i(TAG, "         ~~~~~~~~~~~~~~~~~ JUST CHECKING ["+bm+"]  ["+((BitmapDrawable) ret).getBitmap()+"]");

    } catch( OutOfMemoryError e ) {
        // Log.e(TAG, " ============== Before gc ==== OOME  Thread ["+Thread.currentThread().getName()+"]  getPreformattedFile.Before GC Heap Available [[[ "+(Debug.getNativeHeapFreeSize()/1024)+"k ]]]");
        System.gc();
        // Log.e(TAG, " ============== After gc  ==== OOME  Thread ["+Thread.currentThread().getName()+"]  getPreformattedFile.Before GC Heap Available [[[ "+(Debug.getNativeHeapFreeSize()/1024)+"k ]]]");
        e.printStackTrace();
    } catch( Exception e) {
        Log.e(TAG, "Trouble reading PNG file ["+e+"]");
    }
    return(ret);
}

I have mentioned a couple times doing things asynchronously, and you mentioned that you are new-ish to Android. For completeness, I should mention that I also dislike using AsyncTask, since it has some pretty heavy limitations on multi-threading, and images tend to require lots of multi-threading. So, instead I use an Executor and do something like this (which, you will note, used the method above to do the actual work):

public Drawable getPreformattedFileAsync() {
    if(mItem == null) {
        Log.e(TAG, " -- ITEM is NULL!!");
        return(mErrorDrawable);
    }
    if(mFetchFileTask == null) {
        Log.e(TAG, " -- Task is Null!!, Need to start an executor");
        return(mErrorDrawable);
    }
    Runnable job = new Runnable() {
         public void run() {
             Thread.currentThread().setName("ImagePipeline");
             Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
             Thread.currentThread().yield();
             if(mItemDelegate != null) {
                 Drawable retDrawable = getPreformattedFile();
                 if(showAllDebugInformation) {
                     Log.w(TAG, "   ^^^^ Getting preformatted file size ["+retDrawable.getIntrinsicWidth()+"] x ["+retDrawable.getIntrinsicHeight()+"]");
                 }
                 if(retDrawable != null) {
                     Bitmap bm = ((BitmapDrawable) retDrawable).getBitmap();
                     // Log.w(TAG, "       Size of Bitmap is ["+(bm.getRowBytes()*bm.getHeight())+"]");
                     mItemDelegate.onDrawableRequest(mItem, retDrawable);
                     if(mBitmapManager != null) {
                         if(mBusyDrawable != null) {
                             mBitmapManager.recycleBitmap(((BitmapDrawable) mBusyDrawable).getBitmap());
                         }
                         if(mErrorDrawable != null) {
                             mBitmapManager.recycleBitmap(((BitmapDrawable) mErrorDrawable).getBitmap());
                         }
                     }
                 }  else  {
                     mItemDelegate.onDrawableRequest(mItem, mErrorDrawable);
                 }
             }
             // Log.i(TAG, "  RUNNABLE - Set the background");
         }
     };
     mImagePipelineTask.execute(job);
     return(mBusyDrawable);
}

This, of course, requires an Executor:

    private ExecutorService mImagePipelineTask = null;

Which can be created thus:

    mImagePipelineTask = Executors.newSingleThreadExecutor();

(Or, if you are adventurous, you can use a multi-thread executor, same general idea).

Perhaps this helps clarify.

like image 31
Ted Collins Avatar answered Nov 18 '22 17:11

Ted Collins


So use

1

@Override
public Object instantiateItem(ViewGroup collection, int position)
@Override
public void destroyItem(ViewGroup collection, int position, Object view)

instead of

 public Object instantiateItem(View collection, int position)
 public void destroyItem(View collection, int position, Object view)

2 Tested on 6 images size 400k each work fine

@Override
public Object instantiateItem(ViewGroup collection, int position)
{
    LayoutInflater inflater = (LayoutInflater) collection.getContext()
            .getSystemService(Context.LAYOUT_INFLATER_SERVICE);



    int resId = 0;
    switch (position) {
    case 0:
        resId = R.layout.farleft;
        break;
    case 1:
        resId = R.layout.left;
        break;
    case 2:
        resId = R.layout.middle;
        break;
    case 3:
        resId = R.layout.right;
        break;
    case 4:
        resId = R.layout.farright;
        break;
    case 5:
        resId = R.layout.farfarright;
        break;
    }

View view = (View) inflater.inflate(resId, null);


 //get Image view from layout   
//ImageView imageView = (ImageView) view.findViewById(resId);

//imageView.setImageResource(resId);

    collection.addView(view, 0);

    return view;
}

@Override
public void destroyItem(ViewGroup collection, int position, Object view)
{
    collection.removeView((View) view);
}

3 Useful link PageAdapter

Goodluck

like image 3
Mikhaili Avatar answered Nov 18 '22 18:11

Mikhaili


pager.setOffscreenPageLimit(MAX_PAGE);  //pager is the ViewPager instance

here MAX_PAGE is the number of pages that can remain outside the Visibility. Any additional to these will get destroyed and only get recreated when the user swipes back to the nearby position.

like image 2
Shakti Avatar answered Nov 18 '22 18:11

Shakti