ListView/GridView 加载图片性能优化技巧

        ListView/GridView经常会遇到加载大量图片,最近恰好遇到这个问题,但由于项目对源代码数目有要求,需要代码尽可能少。结合最近实践,谈一下ListView加载图片的优化策略。

        常用的ListView优化性能的方法主要是利用convertView 减少 View inflate的次数,通过viewHolder减少findView的耗时。但如果对于加载网络图片来说,仅采取上述措施还是不够的。常见的图片加载优化方法

a.建立内存缓存和磁盘缓存,缓存处理过后的图片到内存中。取图片的时候,优先从内存取;如果内存取不到,就从磁盘获取,获取后加入到内存中并管理内存不超出Cache最大容量;如果磁盘中仍然没有,则从网络拉取,拉取后,加入内存缓存中并并管理内存不超出Cache最大容量。

内存缓存策略最常使用的是LruCache, 需要注意的是由于android引用处理机制和java不同,要用bitmap的强引用做缓存,不能像Java中用SoftReference或者WeakReference做缓存。

In the past, a popular memory cache implementation was a SoftReference or WeakReferencebitmap cache, however this is not recommended. Starting from Android 2.3 (API Level 9) the garbage collector is more aggressive with collecting soft/weak references which makes them fairly ineffective. In addition, prior to Android 3.0 (API Level 11), the backing data of a bitmap was stored in native memory which is not released in a predictable manner, potentially causing an application to briefly exceed its memory limits and crash.

ListView/GridView 加载图片性能优化技巧

b.异步加载(滚动的时候不加载,在滚动结束对可见的item加载),具体实现是在ListView/GridView中设置OnScrollListener, 在onScrollStateChanged中,如果scrollState为SCROLL_STATE_IDLE时候加载图片,并只对firstVisiablePosition 和 lastVisiablePosition之间的图片做加载,需要注意的是获取获取对应Item View, 要用 i - firstVisiblePosition。 另外对于第一次的处理,我的方法是在getView中去调用,但如ListView/GridView处于滚动状态,则不加载。

c.decode图片时只decode对应view大小,如果无带透明度的图片,可采用RGB565格式,保存尽可能多图片到内存中。

附之前设计的类图,ImageLoader负责网络图片拉取,LruCache为内存缓存, LoadManager负责内存缓存和从网络协调,并由BookShopAdapter调用。

ListView/GridView 加载图片性能优化技巧


public class TemplateAdapter extends BaseAdapter {
	public static final int COLUMNS = 3;
	private Context mContext;
	private ArrayList<TemplateItemModel> mTemplateList;
	private Handler mHandler;
	private int mSource = CallShowConst.PEOPLE_CENTER;
	protected LruCache<String, Bitmap> mMemoryCache;
	private int mRowWidth = 0;
	private int mColumnWidth = 0;
	private boolean mFastScrolling;
	private ListView mListView;
	protected HashMap<TemplateView, String> mMap;
	protected HashMap<TemplateView, Boolean> mNewMap;
	private static final String TAG = "TemplateAdapter";

	public TemplateAdapter(Context context, ArrayList<TemplateItemModel> templateList, ListView listView, int source) {
		super();
		mContext = context;
		mTemplateList = templateList;
		mHandler = new BaseHandler();
		int cacheSize = calculateMemoryCacheSize(mContext);
		mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {

			// 必须重写此方法,来测量Bitmap的大小
			@Override
			protected int sizeOf(String key, Bitmap bitmap) {
				if (bitmap != null) {
					return bitmap.getRowBytes() * bitmap.getHeight();
				}
				return 0;
			}

		};
		mMap = new HashMap<TemplateView, String>();
		mNewMap = new HashMap<TemplateView, Boolean>();
		mSource = source;
		mListView = listView;
		int screenWidth = ScreenUtil.mScreenWidth;
		mRowWidth = (int)((screenWidth - Tools.dip2px(mContext, 10) * 2) * (143.0 / 170.0)); 
		mColumnWidth = (int) ((mRowWidth - Tools.dip2px(mContext, 5) * (COLUMNS -1)) / COLUMNS);
		Log.i(TAG, "screenWidth = " + screenWidth + ", mRowWidth = " + mRowWidth + ", columnWidth = " + mColumnWidth);
	}
	
	private  int calculateMemoryCacheSize(Context context) {
		ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
		int memoryClass = am.getMemoryClass();
		int size = 1024 * 1024 * memoryClass / 7;
		final int MIN_SIZE = 1 * 1024 * 1024; // 1MB
		final int MAX_SIZE = 4 * 1024 * 1024; // 4MB
		return Math.max(Math.min(size, MAX_SIZE), MIN_SIZE);
	}

	public void addBitmapToMemoryCache(String key, Bitmap ref) {
		if (getBitmapFromMemoryCache(key) == null && ref != null) {
			mMemoryCache.put(key, ref);
		}
	}

	public Bitmap getBitmapFromMemoryCache(String key) {
		return mMemoryCache.get(key);
	}

	@Override
	public int getCount() {
		if (mTemplateList == null) {
			return 0;
		}
		return mTemplateList.size();
	}

	@Override
	public TemplateItemModel getItem(int position) {
		if (mTemplateList == null) {
			return null;
		}
		if (position < 0 || position > mTemplateList.size()) {
			return null;
		}
		return mTemplateList.get(position);
	}

	@Override
	public long getItemId(int position) {
		return position;
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		ViewHolder viewHolder;
		if (convertView == null) {
			convertView = PluginResUtil.getInstance().inflate(mContext, R.layout.layout_item_template, null);
			viewHolder = new ViewHolder();
			viewHolder.mCategoryTitle = (TextView) PluginResUtil.findView(convertView, R.id.category_title);
			viewHolder.mThumbnailLayout = (LinearLayout) PluginResUtil.findView(convertView, R.id.thumbail_layout);
			convertView.setTag(viewHolder);
		} else {
			viewHolder = (ViewHolder) convertView.getTag();
		}
		LinearLayout layout = (LinearLayout) PluginResUtil.findView(convertView, R.id.template_linear_layout);
		TemplateItemModel templateItemModel = getItem(position);
		if (TextUtils.isEmpty(templateItemModel.title)) {
			viewHolder.mCategoryTitle.setVisibility(View.INVISIBLE);
			layout.setPadding(Tools.dip2px(mContext, 10), Tools.dip2px(mContext, 5), Tools.dip2px(mContext, 10), 0);
		} else {
			viewHolder.mCategoryTitle.setText(templateItemModel.title);
			viewHolder.mCategoryTitle.setVisibility(View.VISIBLE);
			layout.setPadding(Tools.dip2px(mContext, 10), Tools.dip2px(mContext, 20), Tools.dip2px(mContext, 10), 0);
		}
		viewHolder.mThumbnailLayout.removeAllViews();
		viewHolder.mThumbnailLayout.setLayoutParams(new LinearLayout.LayoutParams(0, mColumnWidth, 143));
		int rowCount = templateItemModel.mTemplates.size();
		for (int i = 0; i < rowCount; i++) {
			final CallShowTemplate template = templateItemModel.mTemplates.get(i);
			LayoutParams param = new LinearLayout.LayoutParams(mColumnWidth, mColumnWidth);
			int rightMargin = (i != (rowCount -1)) ? Tools.dip2px(mContext, 5) : 0; // 最后一个item marginRight为 0
			param.setMargins(0, 0, rightMargin, 0);
			TemplateView tempView = new TemplateView(mContext);
			viewHolder.mThumbnailLayout.addView(tempView, param);
			Bitmap ref = getBitmapFromMemoryCache(template.thumbnailUrl);
			mMap.put(tempView, template.thumbnailUrl); 
			mNewMap.put(tempView, template.isTop);
			if (ref != null) {
				tempView.setImageDrawable(new BitmapDrawable(ref));
				tempView.setNew(template.isTop);
			} else {
				if (!mFastScrolling) {
					setTemplateThumbail(tempView, template.thumbnailUrl, template.isTop);
				}
				
			}
			tempView.setOnClickListener(new OnClickListener() {
				@Override
				public void onClick(View v) {
					PluginIntent intent = new PluginIntent(InnerConst.ViewId.CALLSHOW_TEMPLATE_DISPLAY_VIEW);
					intent.putExtra(InnerConst.Key.TEMPLATE_ID, template.id);
					intent.putExtra(CallShowConst.CALL_SHOW_SOURCE, mSource);
					PiCallShow.getInstance().startActivity(intent, false);

				}
			});
		}
		return convertView;
	}

	public void clearMemoryCache() {
		if (mMemoryCache != null) {
			mMemoryCache.evictAll();
		}
	}

	private class ViewHolder {
		TextView mCategoryTitle;
		LinearLayout mThumbnailLayout;
	}

	private void setTemplateThumbail(final TemplateView templateView, final String thumbnailUrl, final boolean isNew) {
		ThreadPoolManager threadPool = (ThreadPoolManager) PiCallShow.getInstance().getPluginContext()
				.getMeriService(ServiceName.THREAD_POOL);
		threadPool.addTask(new Runnable() {
			@Override
			public void run() {
				final BitmapFactory.Options options = new BitmapFactory.Options();
				int size = templateView.getWidth();
				options.outWidth = size;
				options.outHeight = size;
				options.inPreferredConfig = Bitmap.Config.RGB_565;
				options.inPurgeable = true;
				options.inInputShareable = true;
				final Bitmap bitmap = ImageLogoLoader.getInstance().getBitmapFromLocal(thumbnailUrl, options);
				if (bitmap != null) {
					mHandler.post(new Runnable() {
						@Override
						public void run() {
							templateView.setImageDrawable(new BitmapDrawable(bitmap));
							templateView.setNew(isNew);
							addBitmapToMemoryCache(thumbnailUrl, bitmap);
						}
					});

				} else {
					String path = ImageLogoLoader.getInstance().loadImgFromNetwork(thumbnailUrl);
					if (path != null) {
						final Bitmap temp = ImageLogoLoader.getInstance().getBitmapFromLocal(thumbnailUrl, options);
						if (temp != null) {
							mHandler.post(new Runnable() {

								@Override
								public void run() {
									templateView.setImageDrawable(new BitmapDrawable(temp));
									templateView.setNew(isNew);
									addBitmapToMemoryCache(thumbnailUrl, temp);
								}
							});
						}
					}
				}

			}

		}, "CallShowThumbnailImage");

	}

	public void onScrollStateChanged(int scrollState) {
		Log.i(TAG, "scrollState = " + scrollState);
		switch (scrollState) {
		case OnScrollListener.SCROLL_STATE_IDLE:
			mFastScrolling = false;
			showImage();
			break;
		case OnScrollListener.SCROLL_STATE_FLING:
			mFastScrolling = true;
			break;
		case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
			mFastScrolling = true;
			break;
		default:
			break;
		}

	}

	private void showImage() {
		int firstVisiablePosition = mListView.getFirstVisiblePosition();
		int lastVisiablePosition = mListView.getLastVisiblePosition();
		for (int i = firstVisiablePosition; i <= lastVisiablePosition; i++) {
			View view = mListView.getChildAt(i - firstVisiablePosition);
			if (view != null) {
				LinearLayout thumbnailLayout = (LinearLayout) PluginResUtil.findView(view, R.id.thumbail_layout);
				if (thumbnailLayout != null) {
					int childCount = thumbnailLayout.getChildCount();
					for (int j = 0; j < childCount; j++) {
						TemplateView imageView = (TemplateView) thumbnailLayout.getChildAt(j);
						if (imageView != null) {
							String thumbnailUrl = mMap.get(imageView);
							boolean isNew = mNewMap.get(imageView);
							if (!TextUtils.isEmpty(thumbnailUrl)) {
								Bitmap bitmap = getBitmapFromMemoryCache(thumbnailUrl);
								if (bitmap != null) {
									imageView.setImageDrawable(new BitmapDrawable(bitmap));
								} else {
									setTemplateThumbail(imageView, thumbnailUrl, isNew);
								}
							}
						}
					}
				}
			}
		}
	}

}

参考文章:

http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html

https://developer.android.com/training/displaying-bitmaps/manage-memory.html

http://developer.android.com/training/displaying-bitmaps/display-bitmap.html

https://developer.android.com/training/volley/request.html


       

你可能感兴趣的:(ListView/GridView 加载图片性能优化技巧)