10

I have seen various issues akin to this on StackOverflow and elsewhere, but none of those solutions seem to be solving my problem, which is as follows:

I have a ViewPager with a simple PagerAdapter. Each "page" has an XML layout associated with it that contains at least one TextView, one ImageView, and sometimes another TextView. The images associated with the ImageViews are generally 1000px in one dimension and less in the other, but they are 8-bit indexed gifs, so none of them is much larger than 20kB. I set them as Drawables, and they scale down appropriately to fit the space allocated to them.

There are only 6 pages. When I launch the activity, it works fine, and I can even flip to the last page, but if I flip all the way to the right and then all the way back to the left, I get an OutOfMemoryError. This doesn't make sense to me because I thought the ViewPager was supposed to destroy the pages more than 1 screen away, and I am handling destroyItem() as suggested in various other places.

Here is the code for the PagerAdapter:

package doop.doop.dedoop;

import doop.doop.dedoop.R.layout;
import android.content.Context;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;

public class HelpPagerAdapter extends PagerAdapter{
    private Context applicationContext;
    private Context activityContext;

    public HelpPagerAdapter(Context appContext, Context activContext){
        applicationContext = appContext;
        activityContext = activContext;
    }

    public int getCount() {
        return 6;
    }

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

        int resId = 0;
        int ivId = 0;
        int drawId = 0;
        switch (position) {
        case 0:
            resId = R.layout.help1;
            ivId = R.id.helpImage1;
            drawId = R.drawable.left_col_highlight;
            break;
        case 1:
            resId = R.layout.help2;
            ivId = R.id.helpImage2;
            drawId = R.drawable.right_col_highlight;
            break;
        case 2:
            resId = R.layout.help3;
            ivId = R.id.helpImage3;
            drawId = R.drawable.p2win;
            break;
        case 3:
            resId = R.layout.help4;
            ivId = R.id.helpImage4;
            drawId = R.drawable.first_move;
            break;
        case 4:
            resId = R.layout.help5;
            ivId = R.id.helpImage5;
            drawId = R.drawable.screen_bottom;
            break;
        case 5:
            resId = R.layout.help6;
            ivId = R.id.helpImage6;
            drawId = R.drawable.illegal_move;
            break;
        }
        View view = inflater.inflate(resId, null);
        collection.addView(view, 0);

        if (ivId != 0 && drawId != 0) {
            ImageView iv = (ImageView) view.findViewById(ivId);
            iv.setImageDrawable(activityContext.getResources().getDrawable(drawId));
        }

        return view;
    }

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

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

}

Here's an example of one of the page layouts:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        style="@style/helpPageLayout" >

    <TextView
        style="@style/helpText"
        android:text="@string/help2" />

    <ImageView android:id="@+id/helpImage2"
        style="@style/helpImage" />            

</LinearLayout>

Here's the Activity that the whole thing is in:

package doop.doop.dedoop;

import android.app.Activity;
import android.graphics.Typeface;
import android.os.Bundle;
import android.support.v4.view.ViewPager;
import android.view.View;
import android.view.ViewGroup;

public class Help extends Activity {

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_help);

        // Set up the ViewPager and its kin
        HelpPagerAdapter adapter = new HelpPagerAdapter(getApplicationContext(), this);
        ViewPager helpPager = (ViewPager) findViewById(R.id.helpPager);
        helpPager.setAdapter(adapter);      

    }

    public void exitHelp(View view) {
        finish();
    }

}

And here's the layout for that Activity:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/mainHelp"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v4.view.ViewPager android:id="@+id/helpPager"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

    <Button
        android:text="@string/helpBack"
        android:onClick="exitHelp"
        style="@style/mainMenuButtons" />

</LinearLayout>

Finally, here's the error:

12-08 11:55:23.114: E/AndroidRuntime(9090): FATAL EXCEPTION: main
12-08 11:55:23.114: E/AndroidRuntime(9090): java.lang.OutOfMemoryError
12-08 11:55:23.114: E/AndroidRuntime(9090):     at android.graphics.Bitmap.nativeCreate(Native Method)
12-08 11:55:23.114: E/AndroidRuntime(9090):     at android.graphics.Bitmap.createBitmap(Bitmap.java:605)
12-08 11:55:23.114: E/AndroidRuntime(9090):     at android.graphics.Bitmap.createBitmap(Bitmap.java:551)
12-08 11:55:23.114: E/AndroidRuntime(9090):     at android.graphics.Bitmap.createScaledBitmap(Bitmap.java:437)
12-08 11:55:23.114: E/AndroidRuntime(9090):     at android.graphics.BitmapFactory.finishDecode(BitmapFactory.java:618)
12-08 11:55:23.114: E/AndroidRuntime(9090):     at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:593)
12-08 11:55:23.114: E/AndroidRuntime(9090):     at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:445)
12-08 11:55:23.114: E/AndroidRuntime(9090):     at android.graphics.drawable.Drawable.createFromResourceStream(Drawable.java:775)
12-08 11:55:23.114: E/AndroidRuntime(9090):     at android.content.res.Resources.loadDrawable(Resources.java:1968)
12-08 11:55:23.114: E/AndroidRuntime(9090):     at android.content.res.Resources.getDrawable(Resources.java:677)
12-08 11:55:23.114: E/AndroidRuntime(9090):     at doop.doop.dedoop.HelpPagerAdapter.instantiateItem(HelpPagerAdapter.java:69)
12-08 11:55:23.114: E/AndroidRuntime(9090):     at android.support.v4.view.ViewPager.addNewItem(ViewPager.java:801)
12-08 11:55:23.114: E/AndroidRuntime(9090):     at android.support.v4.view.ViewPager.populate(ViewPager.java:962)
12-08 11:55:23.114: E/AndroidRuntime(9090):     at android.support.v4.view.ViewPager.populate(ViewPager.java:881)
12-08 11:55:23.114: E/AndroidRuntime(9090):     at android.support.v4.view.ViewPager$3.run(ViewPager.java:237)
12-08 11:55:23.114: E/AndroidRuntime(9090):     at android.os.Handler.handleCallback(Handler.java:605)
12-08 11:55:23.114: E/AndroidRuntime(9090):     at android.os.Handler.dispatchMessage(Handler.java:92)
12-08 11:55:23.114: E/AndroidRuntime(9090):     at android.os.Looper.loop(Looper.java:137)
12-08 11:55:23.114: E/AndroidRuntime(9090):     at android.app.ActivityThread.main(ActivityThread.java:4514)
12-08 11:55:23.114: E/AndroidRuntime(9090):     at java.lang.reflect.Method.invokeNative(Native Method)
12-08 11:55:23.114: E/AndroidRuntime(9090):     at java.lang.reflect.Method.invoke(Method.java:511)
12-08 11:55:23.114: E/AndroidRuntime(9090):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:980)
12-08 11:55:23.114: E/AndroidRuntime(9090):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:747)
12-08 11:55:23.114: E/AndroidRuntime(9090):     at dalvik.system.NativeStart.main(Native Method)

I suppose it may be worth noting that this happens on only one of the two devices I'm currently testing on (Samsung Galaxy SIII, but not Kindle Fire HD 8.9).

Mike B
  • 153
  • 2
  • 6
  • Same issue here : Works on Galaxy S2 and Out of memory on the Galaxy Tab 10. Scroll to the right ok, back to the left OOM. – Gomoku7 Jan 10 '13 at 11:53

3 Answers3

5

Here are a few things you can try:

  • Use PNG's as images, try some png-crunch-app (or use photoshop > export for web)
  • Recycle your views:

Keep the views you remove from the container in destroyItem() in an ArrayList. During instantiateItem(), first check if you have unused views in your arraylist, if so, use that one. If not, inflate one. This way, you only inflate 3 views, and recycle the rest. Check here for more information about ViewHolders to prevent calling findViewById() too much.

  • Load bitmaps more efficiently:

Use BitmapFactory.Options with inJustDecodeBounds = true to decode your bitmap and check the bounds of the bitmap. Then use inSampleSize to decode a smaller version. If this is relevant for you Check here for more information.

  • Primary item

Make use of the setPrimaryItem() of the pagerAdapter. And load the bitmaps in there. This way, you only load the bitmap for the currently visible item, thus decreasing memory load. But kinda defeats the point of preloading the previous/next-page to have smoother scrolling and better user-experience.

Example code (not tested!)

private ImageView primaryView;

@Override
public void setPrimaryItem(ViewGroup container, int position, Object object)
{
    super.setPrimaryItem(container, position, object);
    if (this.primaryView == object)
    {
        //this method might be called a lot, so don't do anything unless the primaryView changes!
        return ;
    }
    if (this.primaryView != null)
    {
        //recycle the previous bitmap to clear memory
        ((BitmapDrawable)this.primaryView.getDrawable()).getBitmap().recycle();
    }
    //set new primary view
    this.primaryView = (ImageView) object;
    int resourceId = 0;//get image resource id here
    //load bitmap here efficiently
    this.primaryView.setImageResource(resourceId);
}
Community
  • 1
  • 1
Jelle
  • 919
  • 9
  • 16
  • i'd also say... 1/ run DDMS and make sure the fragments are destroyed. Maybe they hold, for example, a hard reference to the context that make them unsuitable for gc. As a fast test you can issue a System.gc on Pagelistener.settle and scroll the first 2 pages back and forth... under logcat check that you are not leaking memory... I am pretty sure that is the case... – rupps May 07 '13 at 20:25
2
helpPager.setOffscreenPageLimit(MAX_PAGES);

It will destroy when pages go out of this range.

Shakti
  • 1,581
  • 2
  • 20
  • 33
  • 4
    The default for the offscreen page limit is 1 according to the documentation, so my impression is that setting it manually shouldn't do anything to help with memory. I tried it anyway, with a value of 1 and then a value of 0, and neither solved the problem. Clearly the pages, or at least the Drawables, are not being destroyed. As an experiment, I set it to 5, and the app crashed when I first loaded the activity with the ViewPager. – Mike B Dec 12 '12 at 18:29
  • maybe its something to do with the gifs, google discourages their use. – Shakti Dec 13 '12 at 04:47
  • Well, they were originally PNG files, and I was having the same problem. I converted them to indexed gifs to make them smaller, with the hope that the smaller files might reduce the memory load. – Mike B Dec 13 '12 at 05:08
-1

setting android:largeHeap="true" in application tag of menifest file solved my problem of outOfMemory exception in viewpager.

user1242611
  • 75
  • 1
  • 7