Android - ListView scrollig太慢了

时间:2011-08-08 13:40:27

标签: android garbage-collection android-listview

我有一个ListView,每个元素有一个图像和两行文本(由RelativeLayout组织)。它工作正常,但它太慢了,我知道问题的来源!

这是我正在使用的自定义适配器的getView()方法:

public View getView(int position, View convertView, ViewGroup parent) {
            if (convertView == null) {
                convertView = mLayoutInflater.inflate(R.layout.list_view_item, parent, false);
                mViewHolder = new ViewHolder();
                mViewHolder.cover = (ImageView) convertView.findViewById(R.id.app_icon);
                mViewHolder.title = (TextView) convertView.findViewById(R.id.selection);
                mViewHolder.description = (TextView) convertView.findViewById(R.id.app_short_description);
                convertView.setTag(mViewHolder);
            } else {
                mViewHolder = (ViewHolder) convertView.getTag();
            }

            // Here is the origin of the issue ! 
            final Feed currentFeed = getItem(position);
            mViewHolder.title.setText(currentFeed.getTitle());
            mViewHolder.description.setText(currentFeed.getDescription());

            try {
                if(currentFeed.getThumbnailUrl() != null) {
                     downloadThumbnail(mViewHolder.cover, currentFeed.getThumbnailUrl());
                }
            } catch(Exception e) {
                e.printStackTrace();
            }

            return convertView;
}

private static class ViewHolder {
        TextView title;
        TextView description;
        ImageView cover;
}

所以我做了一些手动基准测试,似乎分配Feed的实例是这种缓慢的根源:

final Feed currentFeed = getItem(position);

我知道这是因为我写了另一个版本来比较两者:

// Here is the origin of the issue ! 
            //final Feed currentFeed = getItem(position);
            mViewHolder.title.setText("Title");
            mViewHolder.description.setText("Description");

            try {
            if(currentFeed.getThumbnailUrl() != null) {
                 downloadThumbnail(mViewHolder.cover, "some url");
            }
        } catch(Exception e) {
            e.printStackTrace();
        }

这个方法更顺畅(即使downloadThumbnail()方法有效)。

我也确切地知道我的ListView只有15个项目。

我知道因为垃圾收集而分配对象非常昂贵,但我无法用其他任何方式来做它!

有什么想法吗?

谢谢!

修改

不要过多关注downloadThumbnail()方法,它已经做了一些缓存。实际上,即使没有任何图片,它仍然很慢。

2 个答案:

答案 0 :(得分:4)

当用户滚动列表时,将在适配器上调用getView。确保您不要重复执行相同的操作,例如生成缩略图。如果项目数量有限(例如视频内容),那么您可以创建所有视图并使其准备好进行查看。否则,您可能必须实现缓存。

下面的代码显示了一个适配器和listView实现,其中所有listviews都创建并存储在内存中。由于这是用于视频浏览,因此内存不会造成任何问题。 (内容数量有限,最多100个)

视频列表适配器

import java.util.ArrayList;
import java.util.Formatter;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;

import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.provider.MediaStore;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.LinearLayout.LayoutParams;

public class VideoListAdapter extends BaseAdapter {
    private Context mContext = null;
    private HashMap<String, VideoListItem> mHashedItems = new HashMap<String, VideoListItem>(); 
    private static final String TAG = "VideoListAdapter";

    public static final int VIDEO_CONTENT_ID       = 0;
    public static final int VIDEO_CONTENT_TITLE    = 1;
    public static final int VIDEO_CONTENT_DURATION = 2;
    public static final int VIDEO_CONTENT_RESOLUTION = 3;
    public static final int VIDEO_CONTENT_MIME = 4;

    private Cursor mCursorForVideoList = null;
    private ContentResolver mContentResolver = null;
    private int mListCount = 0;

    VideoListAdapter(Context context, ContentResolver cr) {
        mContext         = context;
        mContentResolver = cr;
        Log.i(TAG, "In the Constructor");

        mCursorForVideoList = 
            mContentResolver.query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, 
                                  new String[] { MediaStore.MediaColumns._ID, 
                                                 MediaStore.MediaColumns.TITLE, 
                                                 MediaStore.Video.VideoColumns.DURATION,
                                                 MediaStore.Video.VideoColumns.RESOLUTION
                                               }, 
                                  null, 
                                  null,  
                                  null);
        mListCount = mCursorForVideoList.getCount();
    }

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

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

    @Override
    public long getItemId(int position) {
        //Log.i(TAG, "position : " + position);
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        //Log.i(TAG, "GetView :: Position : " + position);
        return getVideoListItem(position);
    }

    private VideoListItem getVideoListItem(int position)
    {
        //Log.i(TAG, "getVideoListItem :: Position : " + position);
        String key = Integer.toString(position);
        VideoListItem item = mHashedItems.get(key);
        if(item == null)
        {
            //Log.i(TAG, "New getVideoListItem :: Position : " + position);
            mCursorForVideoList.moveToPosition(position);
            mHashedItems.put(key, new VideoListItem(mContext, mContentResolver, mCursorForVideoList));
        }
        return mHashedItems.get(key);
    }

};

视频列表视图

import java.util.Formatter;
import java.util.Locale;

import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Typeface;
import android.provider.MediaStore;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TableLayout;
import android.widget.TextView;
import android.widget.LinearLayout.LayoutParams;

class VideoListItem extends LinearLayout
{
    private static final String TAG = "VideoListAdapter";

    private ImageView mThumbnail = null;
    private TextView mDuration   = null;
    private TextView mTitle      = null;
    private TextView mResolution = null;

    private LayoutInflater mLayoutFactory = null;

    private long mContentId = 0;

    public VideoListItem(Context context, ContentResolver cr, Cursor cursor) {
        super(context);
        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
        params.setMargins(10, 10, 10, 10);

        mLayoutFactory = LayoutInflater.from(context);
        View thisView = mLayoutFactory.inflate(R.layout.videolistitem, null);
        addView(thisView);

        mThumbnail  = (ImageView) findViewById(R.id.thumbnail); 
        mDuration   = (TextView)  findViewById(R.id.DDuration);
        mTitle      = (TextView)  findViewById(R.id.DTitle);
        mResolution = (TextView)  findViewById(R.id.DResolution);

        mThumbnail.setLayoutParams(new LinearLayout.LayoutParams(144, 144));

        Resources r = this.getResources();
        Bitmap bMap = MediaStore.Video.Thumbnails.getThumbnail(cr, cursor.getLong(VideoListAdapter.VIDEO_CONTENT_ID), MediaStore.Video.Thumbnails.MINI_KIND, null);
        if(bMap != null)
        {
            mThumbnail.setImageBitmap(Bitmap.createScaledBitmap(bMap, 128, 128, true)); 
        }
        else
        {
            mThumbnail.setImageDrawable(r.getDrawable(R.drawable.error));
        }
        mThumbnail.setPadding(16, 16, 16, 16);
        mTitle.setText(cursor.getString(VideoListAdapter.VIDEO_CONTENT_TITLE));
        mTitle.setSingleLine();
        mTitle.setTextColor(Color.GREEN);

        mResolution.setText(cursor.getString(VideoListAdapter.VIDEO_CONTENT_RESOLUTION));
        mResolution.setSingleLine();
        mResolution.setTextColor(Color.RED);

        mDuration.setText(stringForTime(cursor.getInt(VideoListAdapter.VIDEO_CONTENT_DURATION)));
        mDuration.setSingleLine();
        mDuration.setTextColor(Color.CYAN);

        mContentId = cursor.getLong(VideoListAdapter.VIDEO_CONTENT_ID);
    }

    public long getContentId()
    {
        return mContentId;
    }

    private StringBuilder mFormatBuilder = null;
    private Formatter mFormatter = null;

    private String stringForTime(int timeMs) {
        int totalSeconds = timeMs / 1000;

        mFormatBuilder = new StringBuilder();
        mFormatter = new Formatter(mFormatBuilder, Locale.getDefault());

        int seconds = totalSeconds % 60;
        int minutes = (totalSeconds / 60) % 60;
        int hours   = totalSeconds / 3600;

        mFormatBuilder.setLength(0);
        if (hours > 0) {
            return mFormatter.format("%d:%02d:%02d", hours, minutes, seconds).toString();
        } else {
            return mFormatter.format("%02d:%02d", minutes, seconds).toString();
        }
    }

};

词shash

答案 1 :(得分:1)

不要在Feed支架中分配或存储Feed对象,而只存储位置(位置)。当您需要引用该对象时,请从ViewHolder中获取引用索引并相应地执行操作。

修改
当然我错过了你以后使用的对象...你也可以为你的Feed对象创建一些最小的静态方法,只返回特定的东西,比如标题等。然后在你的方法中调用这些方法。 getView方法设置UI元素而不完全创建Feed本身。