0

Since Android introduced a new memory management of Bitmaps in Android 3.0 (Bitmap Data is now allocated inside the Dalvik Heap), I get this OutOfMemoryError Error which crashes my App.

Basically I have a ListView with approx. 1000 ImageViews holding Bitmaps from my Assets Folder(resolution: 300 x 200 pixel).

I detected this problem on an HTC Desire running Jelly Bean. If I scroll to fast, the Pro version of my App is crashing. (The Free version has only 150 Items in the list) I tried to reproduce the Error in the Emulator and found out that the Garbage Collection Messages of the DDMS in Android Versions 3.0 and above aren't GC_EXTERNAL_ALLOC like on my old 2.3.6 Smartphone (which runs the App without any problems). So the Memory Size for the big amount of Data isn't enough. I don't think that ListView really is holding all the Bitmaps in the memory, but there must really be a difference between 150 Items and 1000 Items.

I tried to AsyncTask the GetView() in my List Adapter, but then the OutOfMemory occurs in the AsyncTask Thread. So I'm getting nowhere with this ...

Someone has an Idea to fix this problem?

Google's development guide didn't help me

EDIT:

Ok, I MAT-analysed my APP, and it seems not to be the ListView that causes the problem, but my ViewFlipper.

My MainMenu, my SearchScreen, my SearchResults (ListView & GridView - toggleable) and my DetailedView are all single "pages" off my ViewFlipper.

MainMenu, SearchScreen and DetailedView take about 20 MB of my memory. I extracted the List- and Gridview from the code, and it seems, that they take up form 8MB up to 13MB (depending on how fast the scrolling is executed).

The Android SDK Emulator (emulating 4.2) has an VM Heap Size of 32MB. So initially it works, and if i scroll to fast i ran into the out of memory, with error message:

skia decoder->decode returned false

followed by diverse Bitmap-decode-OutOfMemory issues.

So I think I have to unload/cache the Pages of my ViewFlipper - right? How to do that???

Here is the extracted source of the ListView/GridView implementation.

public class GridTestActivity extends Activity {

    GridView gv;
    ListView lv;
    int lvScreenWidth;
    int lvScreenHeight;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_grid_test);

        Display display = getWindowManager().getDefaultDisplay(); 
        this.lvScreenWidth  =  display.getWidth();
        this.lvScreenHeight  =  display.getHeight();

        MyAdapter adp = new MyAdapter(this);

        gv = (GridView)findViewById(R.id.gridView1);
        gv.setAdapter(adp);
        gv.setFastScrollEnabled(true);

        lv = (ListView)findViewById(R.id.listView1);
        lv.setAdapter(adp);
        lv.setFastScrollEnabled(true);


        ActivityManager am = (ActivityManager)this.getSystemService(ACTIVITY_SERVICE);
        Toast.makeText(this, "DALVIK HEAP SIZE: " + am.getMemoryClass() + "MB", 5).show();


    }

    static class ViewHolder{
         TextView text;
         ImageView icon;
    }


    public class MyAdapter extends BaseAdapter{

        private LayoutInflater mInflater;

        public MyAdapter(Context context) {
            mInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        }


        @Override
        public int getCount() {
            return 1000;
        }

        @Override
        public Object getItem(int arg0) {
            return null;
        }

        @Override
        public long getItemId(int position) {
            return 0;
        }

        @Override
        public boolean hasStableIds() {
            return true;
        }


        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder;

            if (convertView == null) {
                convertView = mInflater.inflate(R.layout.list_item_icon_text, parent, false);
                holder = new ViewHolder();
                holder.text = (TextView) convertView.findViewById(R.id.text);
                holder.icon = (ImageView) convertView.findViewById(R.id.icon);

                convertView.setTag(holder);
            } else {
                holder = (ViewHolder) convertView.getTag();
            }

            holder.text.setText("pos:" + position);

            // best way to load an asset-image???
            try {
                InputStream ims = getAssets().open("assetpic_" + position + ".jpg");
                BitmapDrawable d = (BitmapDrawable) Drawable.createFromStream(ims, null);
                ims.close();
                holder.icon.setImageDrawable(d);


                if (parent.equals(lv)){   
                    int oldWidth = d.getIntrinsicWidth();
                    int oldHeight= d.getIntrinsicHeight();
                    holder.icon.getLayoutParams().height = lvScreenWidth * oldHeight/oldWidth;
                }


            }
            catch(IOException ex) {
                Log.i("MyAdapter" , "Could not load 'assetpic_" + position + ".jpg' from Assets-folder");
            }

            return convertView;
        }

    }

}

I'm not sure , but maybe I miss some details on my code?

full source (25MB)

trincot
  • 317,000
  • 35
  • 244
  • 286
lx222
  • 248
  • 1
  • 16
  • Maybe this can point you in the right direction : http://stackoverflow.com/questions/477572/android-strange-out-of-memory-issue-while-loading-an-image-to-a-bitmap-object – Slickelito Mar 22 '13 at 13:19

3 Answers3

1

You should recycle bitmaps when not in use.

bitmap.recycle();

You can use Universal Image Loader. https://github.com/nostra13/Android-Universal-Image-Loader.

You can use Lazy Loading of images. https://github.com/thest1/LazyList.

Both use caching.

http://www.youtube.com/watch?v=_CruQY55HOk. The talk is on memory management, recycling bitmaps and how to detect memory leaks using MAT Analyzer.

http://developer.android.com/training/improving-layouts/smooth-scrolling.html. Use a View Holder for smooth scrolling.

http://www.youtube.com/watch?v=wDBM6wVEO70. The talk is on view holder and listview performance.

You have already gone through dispalying bitmaps efficiently as you have mentioned.

Falko
  • 17,076
  • 13
  • 60
  • 105
Raghunandan
  • 132,755
  • 26
  • 225
  • 256
  • thank you for the reply. I have implemented some of your hints (see edited original post added source code). Maybe you know some techniques, how to get rid of the viewflipper-problem? thank you in advance – lx222 Mar 26 '13 at 17:09
  • Have you used universal image loader and still you run into memory leaks??. Pls paste the code here. – Raghunandan Mar 26 '13 at 17:11
  • Paste only relevant code. No one will look at the source code downloading it. (25MB) – Raghunandan Mar 26 '13 at 17:17
  • I haven't tried UIL jet. I thought this libraries/projects are only for loading pictures remotely not for local image loading. But it seems that UILs multithreading and caching may be usefull for my project. – lx222 Mar 26 '13 at 22:46
  • You can load images localy also uisn UIL. – Raghunandan Mar 27 '13 at 06:02
  • Hmm, UIL seems to be smoother, but I still have a memory problem. I have a ListView and a GridView and want to toggle between this views. At the moment I have a LinearLayout with both the ListView and the GridView in Memory, and I alwas show one view and hide the other. But I think it is better, to unload the List/the Grid if it is toggled to the other view. (of course to have more heap memory) How can I unload a ListView / GridView from memory, and still leave it active when resumed ? – lx222 Mar 27 '13 at 17:00
0

This may not be a proper solution but Thats all i have to offer for now. add you manifest (in application tag)

android:largeHeap="true"

that may solve your out of memory problem to a limit.If you show your adapter and listview pair, i may tell some more legit solution.

Ercan
  • 3,705
  • 1
  • 22
  • 37
  • @Erican http://www.youtube.com/watch?v=wDBM6wVEO70. The guy gives a big warning using large heap. Larger the heap more frequent garbabge collection leading to slow performance. – Raghunandan Mar 22 '13 at 14:55
  • true. but i cant offer anything better without seing the code. – Ercan Mar 22 '13 at 15:33
0

The problem is that AsyncTask is only good for a few images, for hundres (or thousands) of images they don't handle them so well.

Caching for sure is absolutely essential, on the Google Link you posted there's explanation for both RAM and disk caching.

For a large data set like this you really have two options:

  1. use a library (such as Universal Image Loader or Lazy List), they're not perfect, but they do work well most of the times.

  2. create your own implementation. I've implemented those before and I wished they were not part of the company IP, cause I wanted to open source it. But you'll have to implement several groups of Threads (using Executors) for threads that are loading from one cache or from the other or from online and deliver back the result and remember to cancel the deliver back if that ImageView is being recycled. It's a long and complex process but if you do it right you'll get some really buttery smooth scrolling.

Budius
  • 39,391
  • 16
  • 102
  • 144