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.
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调用。
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