Kodi Kodi - 4 months ago 20
Java Question

Android application crashes when showing multiple images in GridView component

I am fairly new to this site and I'm here because I couldn't find this answer anywhere else so I would like to see if I could get some help!

I have a GridView in my project as well as a Image Adapter. I need help with the following code:

package com.humanoid.sigma;

import android.app.Activity;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.GridView;
import android.widget.ImageView;

public class ImageAdapter extends BaseAdapter {
private Context mContext;


public Integer[] Tattoos = {
R.drawable.tattoo1, R.drawable.tattoo2,
R.drawable.tattoo3, R.drawable.tattoo4,
R.drawable.tattoo5, /*R.drawable.tattoo6,
R.drawable.tattoo7, R.drawable.tattoo8,
R.drawable.tattoo9, R.drawable.tattoo10,
R.drawable.tattoo11, R.drawable.tattoo12,
R.drawable.tattoo13, R.drawable.tattoo14,
R.drawable.tattoo15, R.drawable.tattoo16,
R.drawable.tattoo17, R.drawable.tattoo18,
R.drawable.tattoo19, R.drawable.tattoo20,
R.drawable.tattoo21, R.drawable.tattoo22,
R.drawable.tattoo23, R.drawable.tattoo24,
R.drawable.tattoo25, R.drawable.tattoo26,
R.drawable.tattoo27, R.drawable.tattoo28,
R.drawable.tattoo29, R.drawable.tattoo30,
R.drawable.tattoo31, R.drawable.tattoo32,
R.drawable.tattoo33, R.drawable.tattoo34,
R.drawable.tattoo35, R.drawable.tattoo36,
R.drawable.tattoo37, R.drawable.tattoo38,
R.drawable.tattoo39, R.drawable.tattoo40,
R.drawable.tattoo41, R.drawable.tattoo42,
R.drawable.tattoo43, R.drawable.tattoo44,
R.drawable.tattoo45, R.drawable.tattoo46,
R.drawable.tattoo47, R.drawable.tattoo48,
R.drawable.tattoo49, R.drawable.tattoo50,
R.drawable.tattoo51, R.drawable.tattoo52,
R.drawable.tattoo53, R.drawable.tattoo54,
R.drawable.tattoo55, R.drawable.tattoo56,
R.drawable.tattoo57, R.drawable.tattoo58,
R.drawable.tattoo59, R.drawable.tattoo60,
R.drawable.tattoo61, R.drawable.tattoo62,
R.drawable.tattoo63, R.drawable.tattoo64,
R.drawable.tattoo65, R.drawable.tattoo66,
R.drawable.tattoo67, R.drawable.tattoo68,
R.drawable.tattoo69, R.drawable.tattoo70,
R.drawable.tattoo71, R.drawable.tattoo72,
R.drawable.tattoo73, R.drawable.tattoo74,
R.drawable.tattoo75, R.drawable.tattoo76,
R.drawable.tattoo77, R.drawable.tattoo78,
R.drawable.tattoo79, R.drawable.tattoo80,
R.drawable.tattoo81, R.drawable.tattoo82,*/

};

// Constructor
public ImageAdapter(Context c){
mContext = c;
}

public int getCount() {
return Tattoos.length;
}

public Object getItem(int position) {
return Tattoos[position];
}

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

@Override
public View getView(int position, View convertView, ViewGroup parent) {
ImageView imageView = new ImageView(mContext);
imageView.setImageResource(Tattoos[position]);
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setLayoutParams(new GridView.LayoutParams(100, 70));
return imageView;
}

}


Logcat Stack Trace:


06-28 17:51:14.104: E/AndroidRuntime(818): FATAL EXCEPTION: main 06-28
17:51:14.104: E/AndroidRuntime(818):

java.lang.OutOfMemoryError 06-28 17:51:14.104: E/AndroidRuntime(818):
at

android.graphics.BitmapFactory.nativeDecodeAsset(Native Method) 06-28
17:51:14.104: E/AndroidRuntime(818): at
android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:502)
06-28 17:51:14.104: E/AndroidRuntime(818): at
android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:355)
06-28 17:51:14.104: E/AndroidRuntime(818): at
android.graphics.drawable.Drawable.createFromResourceStream(Drawable.java:785)
06-28 17:51:14.104: E/AndroidRuntime(818): at
android.content.res.Resources.loadDrawable(Resources.java:1965) 06-28
17:51:14.104: E/AndroidRuntime(818): at
android.content.res.Resources.getDrawable(Resources.java:660) 06-28
17:51:14.104: E/AndroidRuntime(818): at
android.widget.ImageView.resolveUri(ImageView.java:616) 06-28
17:51:14.104: E/AndroidRuntime(818): at
android.widget.ImageView.setImageResource(ImageView.java:349) 06-28
17:51:14.104: E/AndroidRuntime(818): at
com.humanoid.sigma.ImageAdapter.getView(ImageAdapter.java:80) 06-28
17:51:14.104: E/AndroidRuntime(818): at
android.widget.AbsListView.obtainView(AbsListView.java:2143) 06-28
17:51:14.104: E/AndroidRuntime(818): at
android.widget.GridView.makeAndAddView(GridView.java:1341) 06-28
17:51:14.104: E/AndroidRuntime(818): at
android.widget.GridView.makeRow(GridView.java:341) 06-28 17:51:14.104:
E/AndroidRuntime(818): at
android.widget.GridView.fillDown(GridView.java:283) 06-28
17:51:14.104: E/AndroidRuntime(818): at
android.widget.GridView.fillFromTop(GridView.java:417) 06-28
17:51:14.104: E/AndroidRuntime(818): at
android.widget.GridView.layoutChildren(GridView.java:1229) 06-28
17:51:14.104: E/AndroidRuntime(818): at
android.widget.AbsListView.onLayout(AbsListView.java:1994) 06-28
17:51:14.104: E/AndroidRuntime(818): at
android.view.View.layout(View.java:14003) 06-28 17:51:14.104:
E/AndroidRuntime(818): at
android.view.ViewGroup.layout(ViewGroup.java:4375) 06-28 17:51:14.104:
E/AndroidRuntime(818): at
android.widget.RelativeLayout.onLayout(RelativeLayout.java:985) 06-28
17:51:14.104: E/AndroidRuntime(818): at
android.view.View.layout(View.java:14003) 06-28 17:51:14.104:
E/AndroidRuntime(818): at
android.view.ViewGroup.layout(ViewGroup.java:4375) 06-28 17:51:14.104:
E/AndroidRuntime(818): at
android.widget.FrameLayout.onLayout(FrameLayout.java:448) 06-28
17:51:14.104: E/AndroidRuntime(818): at
android.view.View.layout(View.java:14003) 06-28 17:51:14.104:
E/AndroidRuntime(818): at
android.view.ViewGroup.layout(ViewGroup.java:4375) 06-28 17:51:14.104:
E/AndroidRuntime(818): at
android.widget.LinearLayout.setChildFrame(LinearLayout.java:1663)
06-28 17:51:14.104: E/AndroidRuntime(818): at
android.widget.LinearLayout.layoutVertical(LinearLayout.java:1521)
06-28 17:51:14.104: E/AndroidRuntime(818): at
android.widget.LinearLayout.onLayout(LinearLayout.java:1434) 06-28
17:51:14.104: E/AndroidRuntime(818): at
android.view.View.layout(View.java:14003) 06-28 17:51:14.104:
E/AndroidRuntime(818): at
android.view.ViewGroup.layout(ViewGroup.java:4375) 06-28 17:51:14.104:
E/AndroidRuntime(818): at
android.widget.FrameLayout.onLayout(FrameLayout.java:448) 06-28
17:51:14.104: E/AndroidRuntime(818): at
android.view.View.layout(View.java:14003) 06-28 17:51:14.104:
E/AndroidRuntime(818): at
android.view.ViewGroup.layout(ViewGroup.java:4375) 06-28 17:51:14.104:
E/AndroidRuntime(818): at
android.view.ViewRootImpl.performLayout(ViewRootImpl.java:1892) 06-28
17:51:14.104: E/AndroidRuntime(818): at
android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1711)
06-28 17:51:14.104: E/AndroidRuntime(818): at
android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:989) 06-28
17:51:14.104: E/AndroidRuntime(818): at
android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:4351)
06-28 17:51:14.104: E/AndroidRuntime(818): at
android.view.Choreographer$CallbackRecord.run(Choreographer.java:749)
06-28 17:51:14.104: E/AndroidRuntime(818): at
android.view.Choreographer.doCallbacks(Choreographer.java:562) 06-28
17:51:14.104: E/AndroidRuntime(818): at
android.view.Choreographer.doFrame(Choreographer.java:532) 06-28
17:51:14.104: E/AndroidRuntime(818): at
android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:735)
06-28 17:51:14.104: E/AndroidRuntime(818): at
android.os.Handler.handleCallback(Handler.java:725) 06-28
17:51:14.104: E/AndroidRuntime(818): at
android.os.Handler.dispatchMessage(Handler.java:92) 06-28
17:51:14.104: E/AndroidRuntime(818): at
android.os.Looper.loop(Looper.java:137) 06-28 17:51:14.104:
E/AndroidRuntime(818): at
android.app.ActivityThread.main(ActivityThread.java:5039) 06-28
17:51:14.104: E/AndroidRuntime(818): at
java.lang.reflect.Method.invokeNative(Native Method) 06-28
17:51:14.104: E/AndroidRuntime(818): at
java.lang.reflect.Method.invoke(Method.java:511) 06-28 17:51:14.104:
E/AndroidRuntime(818): at
com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
06-28 17:51:14.104: E/AndroidRuntime(818): at
com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560) 06-28
17:51:14.104: E/AndroidRuntime(818): at
dalvik.system.NativeStart.main(Native Method)


Gallery.Java code:

package com.humanoid.sigma;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
import android.widget.GridView;
import android.widget.ImageView;

public class Gallery extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.gallery);

GridView gridView = (GridView) findViewById(R.id.photos);

// Instance of ImageAdapter Class
gridView.setAdapter(new ImageAdapter(this));
gridView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View v,
int position, long id) {

// Sending image id to FullScreenActivity
Intent i = new Intent(getApplicationContext(), FullScreen.class);
// passing array index
i.putExtra("id", position);
startActivity(i);
}
});
}
}


As you can see from above, I have commented out what isn't working for me. I don't know if it's because I have too many pictures for this GridView but it's rather annoying. Once I commented those images out the program runs fine but if I delete the /* and run the program and select Gallery, it would just show a black screen. Shortly after it would crash.

Please help me with this code I keep searching but can't find anything to help.

Answer

Your problem arises due to the exhaustion of the heap size while allocating drawable resources (it is a common problem, since lots of memory required for this kind of operation).

When i have tried your code, i got java.lang.OutOfMemoryError: bitmap size exceeds VM budget . Which is a straightforward self-explanatory error message.

Then i made some research and came across a 'native' thread on official Android Dev website: http://developer.android.com/training/displaying-bitmaps/index.html about Bitmaps and examples on how to make efficient resource allocations in applications.

So I tried it and combined with your code and this is what i got:

ImageAdapter.java :

package com.humanoid.sigma;

import android.app.Activity;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.GridView;
import android.widget.ImageView;

public class ImageAdapter extends BaseAdapter {
private Context mContext;


public Integer[] Tattoos = {
        R.drawable.tattoo1, R.drawable.tattoo2,
        R.drawable.tattoo3, R.drawable.tattoo4,
        R.drawable.tattoo5, R.drawable.tattoo6,
        R.drawable.tattoo7, R.drawable.tattoo8,
        R.drawable.tattoo9, R.drawable.tattoo10, 
        R.drawable.tattoo11, R.drawable.tattoo12,
        R.drawable.tattoo13, R.drawable.tattoo14,
        R.drawable.tattoo15, R.drawable.tattoo16,
        R.drawable.tattoo17, R.drawable.tattoo18,
        R.drawable.tattoo19, R.drawable.tattoo20, 
        R.drawable.tattoo21, R.drawable.tattoo22,
        R.drawable.tattoo23, R.drawable.tattoo24,
        R.drawable.tattoo25, R.drawable.tattoo26,
        R.drawable.tattoo27, R.drawable.tattoo28,
        R.drawable.tattoo29, R.drawable.tattoo30, 
        R.drawable.tattoo31, R.drawable.tattoo32,
        R.drawable.tattoo33, R.drawable.tattoo34,
        R.drawable.tattoo35, R.drawable.tattoo36,
        R.drawable.tattoo37, R.drawable.tattoo38,
        R.drawable.tattoo39, R.drawable.tattoo40, 
        R.drawable.tattoo41, R.drawable.tattoo42,
        R.drawable.tattoo43, R.drawable.tattoo44,
        R.drawable.tattoo45, R.drawable.tattoo46,
        R.drawable.tattoo47, R.drawable.tattoo48,
        R.drawable.tattoo49, R.drawable.tattoo50, 
        R.drawable.tattoo51, R.drawable.tattoo52,
        R.drawable.tattoo53, R.drawable.tattoo54,
        R.drawable.tattoo55, R.drawable.tattoo56,
        R.drawable.tattoo57, R.drawable.tattoo58,
        R.drawable.tattoo59, R.drawable.tattoo60, 
        R.drawable.tattoo61, R.drawable.tattoo62,
        R.drawable.tattoo63, R.drawable.tattoo64,
        R.drawable.tattoo65, R.drawable.tattoo66,
        R.drawable.tattoo67, R.drawable.tattoo68,
        R.drawable.tattoo69, R.drawable.tattoo70, 
        R.drawable.tattoo71, R.drawable.tattoo72,
        R.drawable.tattoo73, R.drawable.tattoo74,
        R.drawable.tattoo75, R.drawable.tattoo76,
        R.drawable.tattoo77, R.drawable.tattoo78,
        R.drawable.tattoo79, R.drawable.tattoo80,
        R.drawable.tattoo81, R.drawable.tattoo82

};

// Constructor
public ImageAdapter(Context c){
    mContext = c;
}

public int getCount() {
    return Tattoos.length;
}

public Object getItem(int position) {
    return Tattoos[position];
}

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

public View getView(int position, View convertView, ViewGroup parent) {
    //This actually is a bad solution, because every time convertView is reused, you will still initialize new ImageView, which is wrong
    //ImageView imageView = new ImageView(this.mContext);
    //new BitmapWorkerTask(imageView).execute(Tattoos[position]);
    //return imageView;

    //Better solution
    ImageView imageView = null;

    if (convertView == null) {
        imageView = new ImageView(this.mContext);
        new BitmapWorkerTask(imageView).execute(Tattoos[position]);
        //create new ImageView if it is not present and populate it with some image
    } else {
        imageView = (ImageView) convertView;
        //re-use ImageView that already exists in memory
    }

return imageView;
}


class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
        private final WeakReference<ImageView> imageViewReference;
        private int data = 0;

        public BitmapWorkerTask(ImageView imageView) {
            // Use a WeakReference to ensure the ImageView can be garbage collected
            imageViewReference = new WeakReference<ImageView>(imageView);
        }

        // Decode image in background.
        @Override
        protected Bitmap doInBackground(Integer... params) {
            data = params[0];
            return decodeSampledBitmapFromResource(ImageAdapter.this.mContext.getResources(), data, 100, 100);
        }

        // Once complete, see if ImageView is still around and set bitmap.
        @Override
        protected void onPostExecute(Bitmap bitmap) {
            if (imageViewReference != null && bitmap != null) {
                final ImageView imageView = imageViewReference.get();
                if (imageView != null) {
                    imageView.setImageBitmap(bitmap);
                    imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
                    imageView.setLayoutParams(new GridView.LayoutParams(100, 70));
                }
            }
        }
    }

    public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
                                                         int reqWidth, int reqHeight) {

        // First decode with inJustDecodeBounds=true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(res, resId, options);

        // Calculate inSampleSize
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(res, resId, options);
    }

    public static int calculateInSampleSize(
            BitmapFactory.Options options, int reqWidth, int reqHeight) {
        // Raw height and width of image
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;

        if (height > reqHeight || width > reqWidth) {

            // Calculate ratios of height and width to requested height and width
            final int heightRatio = Math.round((float) height / (float) reqHeight);
            final int widthRatio = Math.round((float) width / (float) reqWidth);

            // Choose the smallest ratio as inSampleSize value, this will guarantee
            // a final image with both dimensions larger than or equal to the
            // requested height and width.
            inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
        }

        return inSampleSize;
    }

}

Which loads images asynchronously and does some additional magic on loaded resources.

This solution works fine for me and when you run your code, you can actually see images being loaded one at a time, starting with one that is of less weight and going upwards, i guess, because for every ImageView there will be a separate worker Thread, which will load and transform the resource and then apply it to the ImageView.

Proof:

asynctask load of resources in getview method inside custom adapter