您的位置:首页 > 其它

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

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

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

1.建立内存缓存和磁盘缓存,缓存处理过后的图片到内存中。取图片的时候,优先从内存取;如果内存取不到,就从磁盘获取,获取后加入到内存中并管理内存不超出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.



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

3.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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: