0

I'm learning Android, and following this tutorial on custom ListView Items.

However, I've created my own ListView item and when I load up the app (on my Galaxy S4, physical device) it becomes incredibly slow.

When I use a simple_list_item_1 for my listview, everything runs smooth, but when I use my own custom item it runs super slow. I can't find out why this is. There seem to be no expensive (and definitely not infinitely running) operations that I created.

I've also noticed that even tho I have only 5 listItems, the getView method gets called around 15 times. An explanation to why this is would also be welcome. (They might be related)

For my Activity I used Android Studio (1.2.2) standard "Navigation Drawer Activity". I've only been adding stuff in the onCreateView method. Which now looks like this:

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_main, container, false);

        /* Start of my custom code */
        //Create some list items
        String[] words = {"Defenestration", "Indicative", "Executive", "Developmental", "Consciousness"};
        //The list in the Fragment
        ListView list = (ListView) rootView.findViewById(R.id.mainList);
        //The custom ListAdapter
        ListAdapter la = new ShaggyAdapter(getActivity(), words);
        //A built in listadapter for testing
        //ListAdapter la2 = new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, words);
        list.setAdapter(la);

        //Create listener
        list.setOnItemClickListener(
                new AdapterView.OnItemClickListener() {
                    @Override
                    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                        String word = String.valueOf(parent.getItemAtPosition(position));
                        Toast.makeText(getActivity(), word, Toast.LENGTH_SHORT).show();
                    }
                });

        /* End of my custom code */
        return rootView;
    }

The custom adapter looks like this:

class ShaggyAdapter extends ArrayAdapter<String>{
    private static final String TAG = "coo";
    public ShaggyAdapter(Context context, String[] words) {
        super(context, R.layout.shaggy_item, words);

    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        LayoutInflater inflater = LayoutInflater.from(getContext());

        if (convertView == null){
            convertView = inflater.inflate(R.layout.shaggy_item, parent, false);
            Log.i(TAG, "inflate");
        }else{
            Log.i(TAG, "Don't inflate");
        }

        String word = getItem(position);
        TextView name = (TextView) convertView.findViewById(R.id.itemName);

        name.setText(word);

        return convertView;

    }
}

The custom List Item looks like this:

<?xml version="1.0" encoding="utf-8"?>
<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent" android:layout_height="wrap_content"
    android:columnCount="5">

    <ImageView
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:id="@+id/itemImage"
        android:layout_row="0"
        android:layout_column="0"
        android:src="@drawable/no_profile"
        android:layout_margin="8dp"
        android:layout_rowSpan="2"
        android:contentDescription="@string/shaggy_item_image_description" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:text="@string/shaggy_item_name_placeholder"
        android:id="@+id/itemName"
        android:layout_row="0"
        android:layout_column="1"
        android:layout_margin="8dp"
        android:layout_marginTop="14dp" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceMedium"
        android:text="@string/shaggy_item_new_tag"
        android:id="@+id/itemNew"
        android:layout_row="0"
        android:layout_column="2"
        android:layout_marginTop="14dp" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceMedium"
        android:text="@string/shaggy_item_date_placeholder"
        android:id="@+id/itemDate"
        android:layout_row="1"
        android:layout_column="1"
        android:layout_margin="8dp"
        android:layout_columnSpan="2" />

    <ImageView
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:id="@+id/itemStar"
        android:layout_row="0"
        android:layout_column="3"
        android:src="@drawable/rating_star_1"
        android:layout_margin="8dp"
        android:layout_marginTop="14dp"
        android:layout_rowSpan="2"
        android:contentDescription="@string/shaggy_item_star_description" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceMedium"
        android:text="@string/shaggy_item_rating_placheholder"
        android:id="@+id/itemRating"
        android:layout_row="0"
        android:layout_column="4"
        android:layout_margin="8dp"
        android:layout_marginTop="14dp"
        android:layout_rowSpan="2" />

</GridLayout>

Any suggestions would be highly appreciated.

Coo
  • 1,842
  • 3
  • 19
  • 37

2 Answers2

1

I found the answer, I made a very silly mistake. The image I was using in the profile image view was 2000x2000px and I displayed it in a 80x80dp imageview. I noticed memory usage suddenly doubled.

Using a smaller image (currently 300x300px) made everything run super smooth. What I've learned today:

- Use correctly sized images! Android doesn't like handling images.

I will also be using the Holding Pattern as suggested by Boss and King of Masses to make it extra smooth.

Coo
  • 1,842
  • 3
  • 19
  • 37
1

why findViewById is so slow? And why View Holder Pattern is faster?

When you are not using Holder so getView() method will call findViewById() as many times as you row(s) will be out of View. So if you have 1000 rows in List and 990 rows will be out of View then 990 times will be called findViewById() again.

Holder design pattern is used for View caching - Holder (arbitrary) object holds child widgets of each row and when row is out of View then findViewById() won't be called but View will be recycled and widgets will be obtained from Holder.

if (convertView == null) {
   convertView = inflater.inflate(layout, null, false);
   holder = new Holder(convertView);
   convertView.setTag(holder); // setting Holder as arbitrary object for row
}
else { // view recycling
   // row already contains Holder object
   holder = (Holder) convertView.getTag();
}

// set up row data from holder
titleText.setText(holder.getTitle().getText().toString());

Where Holder class can looks like:

public class Holder {

   private View row;
   private TextView title;

   public Holder(View row) {
      this.row = row;
   }

   public TextView getTitle() {
      if (title == null) {
         title = (TextView) row.findViewById(R.id.title);
      }
      return title;
   }
}

Here is second approach how to use ViewHolder pattern:

ViewHolder holder;
// view is creating
if (convertView == null) {
   convertView = LayoutInflater.from(mContext).inflate(R.layout.row, parent, false);
   holder = new ViewHolder();   
   holder.title = (TextView) convertView.findViewById(R.id.title);
   holder.icon = (ImageView) convertView.findViewById(R.id.icon);
   convertView.setTag(holder);
}
// view is recycling
else {
   holder = (ViewHolder) convertView.getTag();
}

// set-up row
final MyItem item = mItems.get(position);
holder.title.setText(item.getTitle());
...

private static class ViewHolder {

   public TextView title;
   public ImageView icon;
}

This Android listview using ViewHolder will help you to implement the same.

As everybody know, Google and AppCompat v7 as support library released new ViewGroup called RecyclerView that is designed for rendering any adapter-based views.

Cheers !!

Community
  • 1
  • 1
King of Masses
  • 18,405
  • 4
  • 60
  • 77
  • 1) `"So if you have 1000 rows in List and 990 rows will be out of View then 990 times will be called findViewById() again."` it's not true: not 990 but only 10, the rest 990 times will be called only when you scroll to the bottom 2) `"Holder design pattern is used for View caching - Holder (arbitrary) object holds child widgets of each row and when row is out of View then findViewById() won't be called but View will be recycled and widgets will be obtained from Holder."` so without holder pattern views are not recycled? – pskink Aug 12 '15 at 05:42
  • 1) Thank you for correcting me 2) Of course we can do it . As everybody know, Google and AppCompat v7 as support library released new ViewGroup called RecyclerView that is designed for rendering any adapter-based views. – King of Masses Aug 12 '15 at 05:46
  • 1
    i mean that holder pattern is not required for view reusing: they are completely two different things, moreover holder pattern is in 95% cases useless: i made some measurements and i saved 1ms or something when scrolling 1k+ items list from the top to the bottom: also see: http://daniel-codes.blogspot.com/2013/11/is-findviewbyid-slow.html – pskink Aug 12 '15 at 05:48
  • let me look into this in detail. thanks for pointing me into this !! – King of Masses Aug 12 '15 at 05:55
  • also notice that for google's std BaseAdapter classes like: ArrayAdapter, SimpleAdapter, CursorAdapter etc view holder pattern is not used, they use it only in RecyclerView – pskink Aug 12 '15 at 05:57