Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android ViewPager/PagerAdapter ImageView OutOfMemoryError

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).

like image 374
Mike B Avatar asked Dec 08 '12 19:12

Mike B


2 Answers

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);
}
like image 164
Jelle Avatar answered Oct 19 '22 07:10

Jelle


helpPager.setOffscreenPageLimit(MAX_PAGES);

It will destroy when pages go out of this range.

like image 26
Shakti Avatar answered Oct 19 '22 06:10

Shakti