前言:
本文是主要关注的android系统中是ListView的滑动性能,关于ListView的重要性以及高效ListView的重要性本文讲不做讨论。同时,这里面列举的一些技巧对于GridView,Gallery也同样适应(注意:某些技巧对于Gallery不适应,后文中会特别指出)。 本文后续会逐条讲到处优化ListView的方法,并以一个例子来不断优化ListView的滑动性能,大部分的方法都会用到例子中。 一.例子介绍: 本文使用的例子是设计一个在媒体库中选择图片的Activity,把媒体库中的图片放到到GridView显示,用户可以点击选择以及取消选择。当Activity返回时,把所选择的图片ID传回。
例子介绍完了,让我们来实现吧。 二.搭建框架 (1)资源文件 下面列举的是选择图片Activity的资源文件,其中主要是一个GridView和两个button,如下:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <GridView android:id="@+id/grid_view" android:layout_width="fill_parent" android:layout_height="fill_parent" android:clickable="true" android:background="#ffffff" android:numColumns="4" android:padding="4dip" android:horizontalSpacing="4dip" android:verticalSpacing="4dip" /> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:background="@color/lighter_gray" android:orientation="vertical" > <Button android:id="@+id/cancel" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@android:string/cancel" android:layout_alignParentLeft="true" android:onClick="onCancel" /> <Button android:id="@+id/ok" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@android:string/ok" android:layout_alignParentRight="true" android:onClick="onOk" /> </RelativeLayout>
</RelativeLayout> 下面是每个item在展示时所需要的资源文件,主要是两个ImageView。 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" > <ImageView android:id="@+id/photo" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_alignParentTop="true" android:scaleType="centerCrop" android:src="@drawable/default_photo" android:contentDescription="@string/photo_image"/> <ImageView android:id="@+id/selector" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignBottom="@+id/photo" android:layout_alignRight="@+id/photo" android:paddingBottom="4dip" android:paddingRight="4dip" android:src="@drawable/checked" android:contentDescription="@string/check_box" /> </RelativeLayout> 定义好了资源文件,我们需要定义数据结构和从媒体库中加载数据。首先定义数据结构: public class PhotoEntity { public static final int DEFAULT_THUMB_ID = 0; public static final int NONE_THUMB_ID = -1;
private final long mId; /* 图片ID */ private final String mPath; /* 图片路径 */ private long mThumbId; /* 缩略图ID */ private boolean mSelected; /* 是否被选中 */ ... } PhotoEntity 代表一个图片对象,包含该对象在媒体库的ID,文件路径,缩略图的ID,是否被选中(这个主要用于显示)。 然后我们在启动Activity的时候,把媒体库中的图片加载到内存,数据加载这里就不介绍了,本文的例子用的是AsyncTask加载数据,并把加载后的数据 传给UI线程。 private final List<PhotoEntity> mData = new ArrayList<PhotoEntity>(); 这里的mData就是用来存储加载后的数据。 然后我们需要在onCreate函数中获取GridView,代码如下:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.setContentView(R.layout.select_photo);
mGridView = (GridView) this.findViewById(R.id.grid_view); mAdapter = createAdapter(); mGridView.setAdapter(mAdapter); mGridView.setOnItemClickListener(this); ... } 其中 createAdapter 用来创建Adapter,本文的后续工作就是介绍各个Adapter。 至此,准备工作就差不多了,然后我们来看一下最基本的Adapter。我们通过继承BaseAdapter来实现自己的Adapter,Adapter中最主要的函数是getView,ListView或者GridView通过调用adapter的getView函数获取相应的item,然后布局到页面中并进行绘制。 下面是我们第一个adapter,这个adapter没有任何优化,只是把功能填补进取。
@Override public View getView(int position, View convertView, ViewGroup parent) { View view = mInflater.inflate(R.layout.photo_item, null); ImageView photoView = (ImageView) view.findViewById(R.id.photo); ImageView selectorView = (ImageView) view.findViewById(R.id.selector);
// get data PhotoEntity entity = mData.get(position);
// get bitmap Bitmap bitmap = PhotoEntityBitmapLoader.loadBitmap(mContext, entity, getDefaultImageWidth()); if (bitmap != null) { photoView.setImageBitmap(bitmap); } else { photoView.setImageResource(R.drawable.default_photo); }
// check box if (entity.isSelected()) { selectorView.setVisibility(View.VISIBLE); } else { selectorView.setVisibility(View.INVISIBLE); }
return view; } 可以看到,我们在getView函数中,创建一个新的view,然后找到子view photoView和selectorView,并给这两个子view设置相关数据。 三. 基本优化 (1)重用View ListView,GridView在设计的时候,就是允许能够重用子View,所以getView的第二个参数才会是 View convertView, 一般来说 convertView 为 null 的话,那么adapter的继承者就需要创建一个新的view。如果 convertView 不为空,那么就可以重用这个view,而不用通过Inflater去创建,毕竟创建view首选需要读取xml文件,然后根据xml创建各个子view,这个过程是比较是比较耗时的。 (2)ViewHolder模式 这里面另外一个比较耗时的函数是findViewById,所以我们可以设计一个类,来保存子view的引用,下次的时候就可以直接用ViewHolder来访问这些子view了。
protected static class ViewHolder { ImageView photoView; ImageView selectorView; long target; }
@Override public View getView(int position, View convertView, ViewGroup parent) { // reuse scap views View view = convertView; if ( view == null ) { view = newView(); } // get view holder ViewHolder holder = (ViewHolder)view.getTag(); // get data PhotoEntity entity = mData.get(position);
// bind view bindView(holder, entity);
return view; }
protected View newView() { View view = mInflater.inflate(R.layout.photo_item, null);
// create view holder and bind it to view ViewHolder holder = new ViewHolder(photoView, selectorView); view.setTag(holder); return view; }
protected void bindView(ViewHolder holder, PhotoEntity entity) { // get bitmap Bitmap bitmap = PhotoEntityBitmapLoader.loadBitmap(mContext, entity, getDefaultImageWidth()); if (bitmap != null) { holder.photoView.setImageBitmap(bitmap); } else { holder.photoView.setImageResource(R.drawable.default_photo); } // check box if (entity.isSelected()) { holder.selectorView.setVisibility(View.VISIBLE); } else { holder.selectorView.setVisibility(View.INVISIBLE); } } 这里面特别说明一下,这个重用View和ViewHolder方法对Gallery是不适用的,因为Gallery每次传入的convertView都是null。 四. 异步加载图片 对于子view中包含图片的列表来说,上面两种方法能够优化的空间有效,之前我们用的是同步加载图片,这里我们把图片的加载放到后台线程去进行, 代码如下: @Override protected void bindView(ViewHolder holder, PhotoEntity entity) { // get bitmap holder.target = entity.getId();
// use default photo holder.photoView.setImageResource(R.drawable.default_photo);
// run a load bitmap task LoadBitmapTask task = new LoadBitmapTask(mContext, holder, entity, getDefaultImageWidth()); task.execute(); // check box if (entity.isSelected()) { holder.selectorView.setVisibility(View.VISIBLE); } else { holder.selectorView.setVisibility(View.INVISIBLE); } } protected static class LoadBitmapTask extends AsyncTask<Void, Void, Bitmap> { private final PhotoEntity mEntity; private final ViewHolder mHolder; private final Context mContext; private final int mItemSize; public LoadBitmapTask(Context context, ViewHolder holder, PhotoEntity entity, int itemSize) { mContext = context; mHolder = holder; mEntity = entity; mItemSize = itemSize; } @Override protected Bitmap doInBackground(Void... params) { return PhotoEntityBitmapLoader.loadBitmap(mContext, mEntity, mItemSize); }
@Override protected void onPostExecute(Bitmap bitmap) { if (bitmap != null && mHolder.target == mEntity.getId()) { mHolder.photoView.setImageBitmap(bitmap); } } } 五. 使用缓存 缓存有很多中,一般使用的是LRU淘汰策略的缓存,这里选择LRUCache(注意,该类在Android3.0后才有)。 protected static class BitmapCache extends LruCache<Long, Bitmap> { public BitmapCache(int maxSize) { super(maxSize); }
@Override protected int sizeOf(Long key, Bitmap value) { return value.getRowBytes() * value.getHeight(); } } protected void bindView(ViewHolder holder, PhotoEntity entity) { // get bitmap holder.target = entity.getId();
// use default photo Bitmap bitmap = mCache.get(entity.getId()); if ( bitmap != null ) { holder.photoView.setImageBitmap(bitmap); } else { holder.photoView.setImageResource(R.drawable.default_photo);
// run a load bitmap task LoadBitmapTask task = new LoadBitmapTask(mContext, mCache, holder, entity, getDefaultImageWidth()); task.execute(); } 六. 预加载和随时终止过时的任务 adapter需要对OnScrollListener事件进行监听,并且在onScroll函数中根据滑动放心加载后续需要的图片和随时终止已经过时的任务。 @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { if (firstVisibleItem == mLastActivePos) { return; } mLastActivePos = firstVisibleItem;
int firstPos = firstVisibleItem; int endPos = firstVisibleItem + visibleItemCount; if (endPos >= totalItemCount) { endPos = totalItemCount - 1; }
// get the task id which will be kept. mActiveList.clear(); for (int i = firstPos; i <= endPos; i++) { mActiveList.add(mData.get(i).getId()); }
// cancel other task quickly mTaskManager.removeException(mActiveList); } |
Android ListView性能提升技巧(转)
最新推荐文章于 2022-10-22 06:41:21 发布