【问题标题】:Android custom ArrayAdapter<String> becomes super slowAndroid 自定义 ArrayAdapter<String> 变得超级慢
【发布时间】:2015-08-12 05:04:08
【问题描述】:

我正在学习 Android,并遵循 this 自定义 ListView 项目的教程。

但是,我创建了自己的 ListView 项目,当我加载应用程序(在我的 Galaxy S4 物理设备上)时,它变得异常缓慢。

当我为我的列表视图使用simple_list_item_1 时,一切运行顺利,但是当我使用我自己的自定义项时,它运行得非常慢。我不知道为什么会这样。我创建的似乎没有昂贵的(而且绝对不是无限运行的)操作。

我还注意到,即使我只有 5 个 listItem,getView 方法也被调用了大约 15 次。也欢迎解释为什么会这样。 (它们可能是相关的)

对于我的活动,我使用了 Android Studio (1.2.2) 标准“导航抽屉活动”。我只是在 onCreateView 方法中添加了一些东西。现在看起来像这样:

    @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;
    }

自定义适配器如下所示:

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;

    }
}

自定义列表项如下所示:

<?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>

任何建议都将受到高度赞赏。

【问题讨论】:

  • 您可能想要使用 ViewHolder 模式
  • 啊,这肯定会有所帮助。干杯!

标签: android android-listview


【解决方案1】:

我找到了答案,我犯了一个非常愚蠢的错误。我在个人资料图像视图中使用的图像是 2000x2000 像素,我将其显示在 80x80dp 图像视图中。我注意到内存使用量突然翻了一番。

使用较小的图片(目前为 300x300 像素)让一切运行起来都非常流畅。我今天学到了什么:

- 使用正确尺寸的图片! Android 不喜欢处理图片。

我还将使用 BossKing of Masses 建议的保持模式,使其更加流畅。

【讨论】:

  • 1+ .i 在我的回答中详细解释了正常方法与视图持有者模式
【解决方案2】:

为什么 findViewById 这么慢?为什么View Holder 模式更快?

当您不使用 Holder 时,getView() 方法将调用 findViewById() 的次数与您的行不在视图中一样多。因此,如果您在 List 中有 1000 行并且 990 行将不在视图中,那么将再次调用 990 次 findViewById()。

Holder 设计模式用于 View 缓存 - Holder(任意)对象保存每一行的子小部件,当行超出 View 时 findViewById() 将不会被调用,但 View 将被回收,小部件将从持有人。

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());

Holder 类可以如下所示:

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;
   }
}

这是使用 ViewHolder 模式的第二种方法:

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;
}

Android listview using ViewHolder 将帮助您实现相同的功能。

众所周知,Google 和 AppCompat v7 作为支持库发布了名为 RecyclerView 的新 ViewGroup,它旨在呈现任何基于适配器的视图。

干杯!!

【讨论】:

  • 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." 这不是真的:不是 990 而是只有 10 次,剩下的 990 次只有在滚动到底部时才会被调用 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." 所以没有持有人模式的视图不会被回收?
  • 1) 谢谢你的指正 2) 我们当然可以。众所周知,Google 和 AppCompat v7 作为支持库发布了名为 RecyclerView 的新 ViewGroup,旨在呈现任何基于适配器的视图。
  • 我的意思是视图重用不需要持有人模式:它们完全是两个不同的东西,而且持有人模式在 95% 的情况下是无用的:我做了一些测量,我在滚动 1k+ 时节省了 1ms 或其他东西从上到下的项目列表:另见:daniel-codes.blogspot.com/2013/11/is-findviewbyid-slow.html
  • 让我详细研究一下。谢谢你指点我!!
  • 还要注意,对于 google 的 std BaseAdapter 类,例如:ArrayAdapter、SimpleAdapter、CursorAdapter 等视图持有者模式未使用,它们仅在 RecyclerView 中使用
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-10-08
  • 1970-01-01
  • 1970-01-01
  • 2021-10-31
相关资源
最近更新 更多