1

I am creating an application that retrieves images from the internet and displays them in a listview with infinite scroll. The problem is that I am getting outofmemoryerror after scrolling for some time. Can anyone tell me what I am doing wrong here? Thanks

MainActivity:

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

     fetchPosts(lPost);
       lv=(ListView) findViewById(R.id.listView1);
       lv.setOnScrollListener(new InfiniteScrollListener(5) {
            @Override
            public void loadMore(int page, int totalItemsCount) {
                fetchPosts(lPost);
            }
        });

        cAdapter = new CustomAdapter(this, prgmNameList,prgmImages, prgmLikeNum, prgmCommentNum);
        lv.setAdapter(cAdapter);

}       



public void fetchImages(String imageName, int position) {

    loadImage task = new loadImage(MainActivity.this, R.id.listView1, imageName, position );

    task.setOnResultsListener(MainActivity.this); 
    task.execute("null");
}

public void fetchPosts(int lastPost){
    LoadPosts lTask = new LoadPosts(MainActivity.this, lastPost);

    lTask.setOnResultsListener(MainActivity.this);
    lTask.execute("asd");

}

@Override
public void onResultsSucceeded(Bitmap image, int position) {

            if(prgmImages.size() > position)
            {
                if(lPost==0)
                    prgmImages.set(position, image);
                else
                    prgmImages.set(position+lPost-5, image);
            }

                    cAdapter.notifyDataSetChanged();

}



@Override
public void onPostsSucceeded(JSONArray obj) throws JSONException {

    //tView.setText(obj.toString());
    lPost += obj.length();

    for (int i = 0; i < obj.length(); i++) {
        JSONObject object = obj.getJSONObject(i);
        String postTitle = object.getString("post_title");
        String imageName = object.getString("image_name");
        String likeNum = object.getString("like_num");
        String commentNum = object.getString("comment_num");
        fetchImages(imageName, i);
        prgmImages.add(null);
        prgmNameList.add(postTitle);
        prgmLikeNum.add(likeNum);
        prgmCommentNum.add(commentNum);

    }


}

CustomAdapter:

public CustomAdapter(MainActivity mainActivity, ArrayList<String>              prgmNameList, ArrayList<Bitmap> prgmImages, ArrayList<String> prgmLikeNum, ArrayList<String> prgmCommentNum) {
    // TODO Auto-generated constructor stub
     context=mainActivity;
     activity=mainActivity;
     inflater = ( LayoutInflater )context.
             getSystemService(Context.LAYOUT_INFLATER_SERVICE);

     result = prgmNameList;
     imageId=prgmImages;
     likeNum=prgmLikeNum;
     commentNum=prgmCommentNum;

}






public class Holder
{
    TextView tv;
    ImageView img;
    TextView likes;
    TextView comments;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
    // TODO Auto-generated method stub
    holder=new Holder();
    View rowView = convertView;   
    if(rowView == null)
    {
         rowView = inflater.inflate(R.layout.post_item, null);
    }
         holder.tv=(TextView) rowView.findViewById(R.id.postTitle);
         holder.img=(ImageView) rowView.findViewById(R.id.postImage);       
         holder.likes=(TextView) rowView.findViewById(R.id.likeNum);
         holder.comments=(TextView) rowView.findViewById(R.id.commentNum);
         holder.tv.setText(result.get(position));
         holder.img.setImageBitmap(imageId.get(position));
         holder.likes.setText(likeNum.get(position));
         holder.comments.setText(commentNum.get(position));
     rowView.setOnClickListener(new OnClickListener() {            
        @Override
        public void onClick(View v) {
            // TODO Auto-generated method stub
         //   Toast.makeText(context, "You Clicked "+result[position], Toast.LENGTH_LONG).show();
        }
    });   
    return rowView;
}
3
  • 1
    seems like you are storing a lot of bitmaps in an array list and this is eating up a lot of memory. Commented Oct 11, 2015 at 11:57
  • What do you suggest doing to avoid this? @RahulTiwari Commented Oct 11, 2015 at 12:35
  • Leak Canary will pin point you the problem in no time.... Commented Oct 15, 2015 at 8:06

2 Answers 2

2

Here are some points to improve your list performance and get rid of the OOM

  1. An android app has a very limited heap so you shouldn't download and store a lot of Bitmap in an List, it fires an OOM.

    The solution is to start the download when getView() is called, so instead of holder.img.setImageBitmap(imageId.get(position)); you should have a request to download the image.

    Managing image list in Android is difficult, you have to take care of:

    • Bitmap re-sizing
    • Thread concurrency
    • Cache managing

    For simplicity sake I advise you to use a library such:

    For example to dowload an image with Glide you have:

    Glide.with(mContext)
        .load(IMAGE_URL)
        .into(holder.img);
    

    Glide/Picasso will manage the bitmap resizing, concurrency and cache :)

  2. From the android doc

Your code might call findViewById() frequently during the scrolling of ListView, which can slow down performance.

More information here.

So you have to move these lines into the if(rowView == null) condition

holder.tv=(TextView) rowView.findViewById(R.id.postTitle);
holder.img=(ImageView) rowView.findViewById(R.id.postImage);       
holder.likes=(TextView) rowView.findViewById(R.id.likeNum);
holder.comments=(TextView) rowView.findViewById(R.id.commentNum);
  1. You shouldn't create a new Holder at each getView() call !

Here is a proper implementation for getView()

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

    // Check if the item's view is recycled
    if(convertView == null)
    {
        // The item's view doesn't exist
        // Create the item's view
        LayoutInflater inflater = ((Activity) mContext).getLayoutInflater();
        convertView = inflater.inflate(R.layout.post_item, null);

        holder = new Holder(); // Create the holder

        holder.tv=(TextView) rowView.findViewById(R.id.postTitle);
        holder.img=(ImageView) rowView.findViewById(R.id.postImage);       
        holder.likes=(TextView) rowView.findViewById(R.id.likeNum);
        holder.comments=(TextView) rowView.findViewById(R.id.commentNum);

        // Store the holder with the view.
        convertView.setTag(holder);
    }
    else 
    {
        // The item's view already exist
        // Retrieve the older
        holder = (Holder) convertView.getTag();
    }

    holder.tv.setText(result.get(position));
    holder.img.setImageBitmap(imageId.get(position));
    Glide.with(mContext)
        .load(imageUrls.get(position))
        .into(holder.img)
    holder.likes.setText(likeNum.get(position));
    holder.comments.setText(commentNum.get(position));

    convertView.setOnClickListener(new OnClickListener() {     

        @Override
        public void onClick(View v) {
            // TODO impl
        }
    });

    return convertView;
}

More information here.

Hope its help :)

Sign up to request clarification or add additional context in comments.

3 Comments

Hi. This solution is very well written. I have tried it and it takes care of the OOM error. However I will not be using it since it takes too much time to load the images which makes the listview not so smooth. I did it with webviews instead of imageviews where I load my images in them. Thanks.
Hi. Maybe it takes too much time because of the images size ? What are the average images dimensions you want to display ? You should (if possible) use a service to resize the image before to download it. For an image list you can use a bitmap with only half the ImageView pixels dimension. For instance, for an ImageView with 100*100 pixels, you can download an image with only 50*50 pixels, it'll look good.
No the problem is definitely with Glide. I dont like how it treats the imageviews. I went with webviews instead and its really fast. Thanks for your help. Cheers :)
0

it seems that you load a lot of bitmaps and you don't give the memory free if you don't use them anymore.

you can do this by (when bitmap is the bitmap you do not need anymore)

bitmap.recycle();
bitmap=null;

In your adapter class is an error using the holder. This will not affect the memory issue, but decrease the performence of your app. Normally you use viewholders to avoid calling findViewById to often. If rowview == null your should link once between your viewholder and your layout. If you have done this, and the same view will be loaded from getView a second time, rowview will be !=null and you don't need to call findViewById anymore which saves a lot of time.

So your code should be like this:

if(rowview==null)
 {
  rowView = inflater.inflate(R.layout.post_item, null);
  holder.tv=(TextView) rowView.findViewById(R.id.postTitle);
  holder.img=(ImageView) rowView.findViewById(R.id.postImage);       
  holder.likes=(TextView) rowView.findViewById(R.id.likeNum);
  holder.comments=(TextView) rowView.findViewById(R.id.commentNum);
 }
 holder.tv.setText(result.get(position));
 holder.img.setImageBitmap(imageId.get(position));
 holder.likes.setText(likeNum.get(position));
 holder.comments.setText(commentNum.get(position));
 rowView.setOnClickListener(new OnClickListener() {            
    @Override
    public void onClick(View v) {
        // TODO Auto-generated method stub
     //   Toast.makeText(context, "You Clicked "+result[position], Toast.LENGTH_LONG).show();
    }
});      

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.