user1555754
user1555754

Reputation: 339

ViewPager with very high number of views throws java.lang.OutOFMemoryError

I have the ViewPager which usually will be storing about to 110 pages, each page is filled by data from SQLite Database and has a lot of TextViews and several ImageViews. But such large amout of views consumes a lot of memory so it throws java.lang.OutOFMemoryError: bitmap size exceeds VM budget. So i tried to achviewe this in 2 ways - at the beginning I tried to use only 3 pages and listen when user swipe page from middle page to right page and after that i re-added views to ViewPager and setCurrentItem back to the middle page. Unfortunately pages swipes was not smooth and was buggy. At the second try I filled all pages by blank views and listen where is the position of user, and add data filled views in his position ,position-1 and position+1. Page swipes was enough smooth but if user run the ViewPager from postion 2 (initial page can be any) and swipes to position 100 there is still java.lang.OutOFMemoryError: bitmap size exceeds VM budget

@Override
protected void onCreate(Bundle savedInstanceState) 
{
for (int i=0; i < ids.length;i++)
    {
        viewsToShow[i] = inflater.inflate(R.layout.blank, null);
        if(ids[i] == getIntent().getIntExtra(DBC.IE_ID, -1))
            currentIDPosition = i;
    }
    pagerAdapter = new MainPagerAdapter();
       pager = (ViewPager) findViewById (R.id.pagerszcz);
        pager.setAdapter (pagerAdapter);

        for (int i = 0; i < viewsToShow.length; i++)
        {
            if(i == currentIDPosition-1)
            {
                 viewsToShow[i] = (View) inflater.inflate(R.layout.szczegoly_item, null);
                 pobierzIZwiazDane(ids[i], viewsToShow[i]); // pobierzIZwiazDane fills view by data from SQLite database and images
                 pagerAdapter.addView (viewsToShow[i], i);
            }
            else if (i == currentIDPosition)
            {
                viewsToShow[currentIDPosition] = (View) inflater.inflate(R.layout.szczegoly_item, null);
                 pobierzIZwiazDane(ids[currentIDPosition], viewsToShow[currentIDPosition]);
                 pagerAdapter.addView (viewsToShow[i], i);
            }
            else if (i == currentIDPosition+1)
            {
                viewsToShow[i] = (View) inflater.inflate(R.layout.szczegoly_item, null);
                 pobierzIZwiazDane(ids[i], viewsToShow[i]);
                 pagerAdapter.addView (viewsToShow[i], i);
            }
            else
                pagerAdapter.addView (viewsToShow[i], i);

        }

        pagerAdapter.notifyDataSetChanged();

        pager.setCurrentItem(currentIDPosition);

         pager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {

            /* int oldPage=1;
             int newPage;*/

               public void onPageSelected(int position) {
                mSelectedPageIndex = position;
                //Log.d("mselect",position+" "+ pager.getCurrentItem());
               }

               public void onPageScrolled(int arg0, float arg1, int arg2) {
                   // Swipe to right page
                   if (pager.getCurrentItem()<currentIDPosition && pager.getCurrentItem() > 0)
                    {
                        Log.d("Zmniejszenie", pager.getCurrentItem()+" "+currentIDPosition);
                    currentIDPosition--;
                    pagerAdapter.removeView(pager, currentIDPosition-1);
                    viewsToShow[currentIDPosition-1] = (View) inflater.inflate(R.layout.szczegoly_item, null);
                     pobierzIZwiazDane(ids[currentIDPosition-1], viewsToShow[currentIDPosition-1]);
                                pagerAdapter.addView (viewsToShow[currentIDPosition-1], currentIDPosition-1);
                                new Handler().post(new Runnable() {
                                    public void run() { // Removing view at current postion +2 
                                        viewsToShow[currentIDPosition+2] = null;
                                         viewsToShow[currentIDPosition+2] = new View(SzczegolyViewPagerActivity.this);
                                         pagerAdapter.removeView(pager, currentIDPosition+2);
                                         pagerAdapter.addView (viewsToShow[currentIDPosition+2], currentIDPosition+2);
                                        }
                                    });

                                 pagerAdapter.notifyDataSetChanged();

                    }
                    else if (pager.getCurrentItem()>currentIDPosition && pager.getCurrentItem() < ids.length-1)
                    {// Swipe to left page
                        Log.d("Zwiekszenie", pager.getCurrentItem()+" "+currentIDPosition);
                        currentIDPosition++;
                    pagerAdapter.removeView(pager, currentIDPosition+1);
                     viewsToShow[currentIDPosition+1] = (View) inflater.inflate(R.layout.szczegoly_item, null);
                     pobierzIZwiazDane(ids[currentIDPosition+1], viewsToShow[currentIDPosition+1]);
                                pagerAdapter.addView (viewsToShow[currentIDPosition+1], currentIDPosition+1);
                                new Handler().post(new Runnable() {
                                    public void run() {
                                         viewsToShow[currentIDPosition-2] = new View(SzczegolyViewPagerActivity.this);
                                         pagerAdapter.removeView(pager, currentIDPosition-2);
                                         pagerAdapter.addView (viewsToShow[currentIDPosition-2], currentIDPosition-2);
                                        }
                                    });

                                 pagerAdapter.notifyDataSetChanged();
                    }
               }

               public void onPageScrollStateChanged(int state) {
                if (state == ViewPager.SCROLL_STATE_IDLE) {
                    Log.d("pagec", currentIDPosition+" "+pager.getCurrentItem() + " " +pagerAdapter.getCount() + " " + mSelectedPageIndex);

                }
               }
              });

}


class MainPagerAdapter extends PagerAdapter
{
  // This holds all the currently displayable views, in order from left to right.
  private ArrayList<View> views = new ArrayList<View>();
    private LayoutInflater inflater;

  public MainPagerAdapter() 
  {

        inflater = getLayoutInflater();
}

  //-----------------------------------------------------------------------------
  // Used by ViewPager.  "Object" represents the page; tell the ViewPager where the
  // page should be displayed, from left-to-right.  If the page no longer exists,
  // return POSITION_NONE.
  @Override
  public int getItemPosition (Object object)
  {
    int index = views.indexOf (object);
    if (index == -1)
      return POSITION_NONE;
    else
      return index;
  }

  //-----------------------------------------------------------------------------
  // Used by ViewPager.  Called when ViewPager needs a page to display; it is our job
  // to add the page to the container, which is normally the ViewPager itself.  Since
  // all our pages are persistent, we simply retrieve it from our "views" ArrayList.
  @Override
  public Object instantiateItem (ViewGroup container, int position)
  {
    View v = views.get (position);
    //View cont = inflater.inflate(R.layout.szczegoly_item, container, false);

    container.addView (v);
    return v;
  }

  //-----------------------------------------------------------------------------
  // Used by ViewPager.  Called when ViewPager no longer needs a page to display; it
  // is our job to remove the page from the container, which is normally the
  // ViewPager itself.  Since all our pages are persistent, we do nothing to the
  // contents of our "views" ArrayList.
  @Override
  public void destroyItem (ViewGroup container, int position, Object object)
  {
    container.removeView (views.get (position));
  }

  //-----------------------------------------------------------------------------
  // Used by ViewPager; can be used by app as well.
  // Returns the total number of pages that the ViewPage can display.  This must
  // never be 0.
  @Override
  public int getCount ()
  {
    return views.size();
  }

  //-----------------------------------------------------------------------------
  // Used by ViewPager.
  @Override
  public boolean isViewFromObject (View view, Object object)
  {
    return view == object;
  }

  //-----------------------------------------------------------------------------
  // Add "view" to right end of "views".
  // Returns the position of the new view.
  // The app should call this to add pages; not used by ViewPager.
  public int addView (View v)
  {
    return addView (v, views.size());
  }

  //-----------------------------------------------------------------------------
  // Add "view" at "position" to "views".
  // Returns position of new view.
  // The app should call this to add pages; not used by ViewPager.
  public int addView (View v, int position)
  {
    views.add (position, v);
    return position;
  }

  //-----------------------------------------------------------------------------
  // Removes "view" from "views".
  // Retuns position of removed view.
  // The app should call this to remove pages; not used by ViewPager.
  public int removeView (ViewPager pager, View v)
  {
    return removeView (pager, views.indexOf (v));
  }

  //-----------------------------------------------------------------------------
  // Removes the "view" at "position" from "views".
  // Retuns position of removed view.
  // The app should call this to remove pages; not used by ViewPager.
  public int removeView (ViewPager pager, int position)
  {
    // ViewPager doesn't have a delete method; the closest is to set the adapter
    // again.  When doing so, it deletes all its views.  Then we can delete the view
    // from from the adapter and finally set the adapter to the pager again.  Note
    // that we set the adapter to null before removing the view from "views" - that's
    // because while ViewPager deletes all its views, it will call destroyItem which
    // will in turn cause a null pointer ref.
   // pager.setAdapter (null);
    views.remove (position);
   // pager.setAdapter (this);

    return position;
  }

  //-----------------------------------------------------------------------------
  // Returns the "view" at "position".
  // The app should call this to retrieve a view; not used by ViewPager.
  public View getView (int position)
  {
    return views.get (position);
  }

  // Other relevant methods:

  // finishUpdate - called by the ViewPager - we don't care about what pages the
  // pager is displaying so we don't use this method.
}

pobierzIZwiazDane(database record id, view) is responsible for filling pages by data from database.

Upvotes: 1

Views: 1800

Answers (3)

thaussma
thaussma

Reputation: 9886

You are creating all Views in advance! If you want to Display 100 Pages you have 100 Pages full of Views in memory. It has to throw an OutOfMemoryError.

The solution is:

  • Create the Views when the ViewPager asks for them (in instantiateItem(ViewGroup container, int position) of your adapter.
  • Delete (remove) them in destroyItem(ViewGroup container, int position, Object object)

If you implement it correctly there will never be more than 3 Pages full of Views in memory.

The following example displays Integer.MAX_VALUE pages in a ViewPager:

public class MainActivity extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    MyPagerAdapter adapter = new MyPagerAdapter();

    ViewPager pager = (ViewPager) findViewById(R.id.pagerszcz);
    pager.setAdapter(adapter);

    pager.setCurrentItem(Integer.MAX_VALUE/2);
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.main, menu);
    return true;
}

private static class MyPagerAdapter extends android.support.v4.view.PagerAdapter{

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

    @Override
    public boolean isViewFromObject(View view, Object object) {
        return view == object;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        //Remove the view added in instantiateItem from the container
        container.removeView((View)object);

        //delete objects created in instantiateItem (non View classes like Bitmap) if necessary
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        TextView tv = new TextView(container.getContext());
        tv.setText("#"+position);

        //Add you View to the container
        container.addView(tv);

        return tv;
    }       
}

}

Upvotes: 0

Steve Bergamini
Steve Bergamini

Reputation: 14600

If you create an android project in eclipse and allow the wizard to set it up with Navigation Type = Fixed Tabs + Swipe, you will see a comment in the created MainActivity that reads:

 /**
 * The {@link android.support.v4.view.PagerAdapter} that will provide
 * fragments for each of the sections. We use a
 * {@link android.support.v4.app.FragmentPagerAdapter} derivative, which
 * will keep every loaded fragment in memory. If this becomes too memory
 * intensive, it may be best to switch to a
 * {@link android.support.v4.app.FragmentStatePagerAdapter}.
 */

So, the correct answer is to switch to FragmentsStatePagerAdapter

Upvotes: 1

superuser
superuser

Reputation: 731

Sorry, but you should think about not using viewpager.

Viewpager initiates all of your fragments at once, and it is just too much for a phone to handle.

You could try using FragmentStatePagerAdapter:

http://developer.android.com/reference/android/support/v4/app/FragmentStatePagerAdapter.html

Which should only load a few fragments at once.

You could also add gesture detection to your app, along with managing your fragments manually, and adding custom animations. This may be a bit harder.

Upvotes: 2

Related Questions