要加载大量图片并在listView那样的控件中流畅的滑动和展示:
原则是让内存占用和内存垃圾尽可能的少,从而避免oom和频繁GC导致的丢帧。另外加载图片要尽可能的快,需要进行异步加载。
所以,要做到流畅,从以下几点入手
- listView中convertView和Viewholder的复用。
这个是个码农都清楚,就不细说了。直接上代码:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null || convertView.getTag() == null) {
convertView = View.inflate(mContext, R.layout.item_pic, null);
holder = new ViewHolder(convertView);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
System.out.println(":::convertView:" + convertView.toString());
System.out.println(":::holder:" + holder.toString());
ImageLoader.getImageLoader(mContext).loadImageFromRes((Integer) getItem(position), holder.picView);
return convertView;
}
class ViewHolder {
private TextView tv_1;
private TextView tv_2;
private ImageView picView;
public ViewHolder(View v){
picView = (ImageView) v.findViewById(R.id.iv_pic);
tv_1 = (TextView) v.findViewById(R.id.tv_1);
tv_2 = (TextView) v.findViewById(R.id.tv_2);
}
}
我们将每次的convertView和ViewHolder的地址打印出来,会发现确实是复用了。
另外,如果不复用的话,在不停的滑动过程中。在观察到内存占用会不停的增大。
- Bitmap的缓存和复用
Bitmap可谓内耗大户,尤其是大图的bitmap,当用户不断滑动的时候,如果不对bitmap进行缓存,内存必然吃不消。但是缓存也是要开辟程序内存的,而一般手机系统分配给单个程序的内存只有一百多M,如果在清单文件的application节点配置了 android:largeHeap=”true” 会增加一倍。所以缓存大小的设置要考虑到图片大小和数量,可用内存等方面。一般采用LRUCache缓存策略,再加上本地缓存。
private static LruCache<String, Bitmap> mMemoryCache;
private ImageLoader(Context context) {
defaultBitmap = BitmapFactory.decodeResource(context.getResources(),R.mipmap.ic_launcher);
mContext = context.getApplicationContext();
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
final int cacheSize = maxMemory;
// 创建缓存,cachesize为缓存大小。
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getByteCount() / 1024;
}
};
}
// 添加bitmap到缓存
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null) {
mMemoryCache.put(key, bitmap);
}
}
// 从缓存取bitmap
public Bitmap getBitmapFromMemCache(String key) {
return mMemoryCache.get(key);
}
- bitmap的瘦身。
图片如果特别大,可能一个bitmap对象就要几十M,这个就不得不从以下几个方面来缩小它了。
opts.inPreferredConfig : 图片解码的色彩精度 Bitmap.Config.ARGB_8888要比Bitmap.Config.ARGB_8888小一倍。
opts.inDensity opts.inTargetDensity : 设置图片解码的分辨率,如果屏幕分辨率很高,解码出来的图片分辨率也会很高。所以针对高分辨率的手机需适当缩小分辨率。解码的分辨率为inTargetDensity,如果设置inscaled为true(默认值),则宽高会按inDensity/inTargetDensity比例缩放。
opts.inSampleSize: 宽高值会乘以1/inSampleSize。
opts = new BitmapFactory.Options();
opts.inJustDecodeBounds = true;
BitmapFactory.decodeResource(mContext.getResources(), resId, opts);
int densityDpi = mContext.getResources().getDisplayMetrics().densityDpi;
if (densityDpi > 240) {
opts.inDensity = (int) (densityDpi * 2f);
opts.inTargetDensity = (int) (densityDpi * 0.5f);
}
opts.inPreferredConfig = Bitmap.Config.ARGB_8888;
opts.inScaled = false;
int outWidth = opts.outWidth;
int outHeight = opts.outHeight;
int scale = 1;
int ws = 1;
int hs = 1;
if (outWidth > 300 || outHeight > 300) {
ws = outWidth / 300;
hs = outHeight / 300;
scale = ws > hs ? ws : hs;
}
opts.inJustDecodeBounds = false;
if (scale % 2 != 0) {
scale--;
}
scale = scale < 1 ? 1 : scale;
System.out.println("----scale==" + scale);
opts.inSampleSize = scale;
- 异步任务对象的复用。
老婆突然吼道:老公睡觉了。 无奈,只能被迫停下,赶紧把代码贴上,擦干眼泪陪她睡。
public void loadImageFromRes(int resId, ImageView imageView) {
if (cancelPotentialWork(resId,imageView)){
DeBitmapTask deBitmapTask = new DeBitmapTask(imageView);
AsyncDrawable asyncDrawable = new AsyncDrawable(defaultBitmap, deBitmapTask);
imageView.setImageDrawable(asyncDrawable);
deBitmapTask.execute(resId);
}
}
public boolean cancelPotentialWork(int data, ImageView imageView) {
DeBitmapTask weakrefTask = getTask(imageView);
if (weakrefTask != null) {
int resId1 = weakrefTask.resId;
if (data != resId1 || resId1 == 0){
weakrefTask.cancel(true);
} else {
// The same work is already in progress
return false;
}
}
// No task associated with the ImageView, or an existing task was cancelled
return true;
}
DeBitmapTask getTask(ImageView imageView) {
Drawable imageViewDrawable = imageView.getDrawable();
if (imageViewDrawable instanceof AsyncDrawable) {
AsyncDrawable asyncDrawable1 = (AsyncDrawable) imageViewDrawable;
return asyncDrawable1.getWeakrefTask();
}
return null;
}
class DeBitmapTask extends AsyncTask {
Bitmap bitmap = null;
private WeakReference<ImageView> imageViewWeakReference;
int resId;
private DeBitmapTask(ImageView imageView) {
imageViewWeakReference = new WeakReference<ImageView>(imageView);
}
@Override
protected void onPreExecute() {
super.onPreExecute();
}
@Override
protected Object doInBackground(Object[] params) {
resId = (Integer) params[0];
bitmap = getBitmapFromMemCache(String.valueOf(resId));
if (bitmap == null) {
getBitmapOpts(resId);
bitmap = BitmapFactory.decodeResource(mContext.getResources(), resId, opts);
addBitmapToMemoryCache(String.valueOf(resId), bitmap);
}
return bitmap;
}
@Override
protected void onPostExecute(Object o) {
imageViewWeakReference.get().setImageBitmap((Bitmap) o);
}
}
// 由于asyntask没被回收利用,并且每次显示都开启一个一步任务,容易产生错位。所以给每个imageView设置一个AsyncDrawable,且每个AsyncDrawable保存对应的asyntask。
static class AsyncDrawable extends BitmapDrawable {
private WeakReference<DeBitmapTask> weakrefTask = null;
public AsyncDrawable(Bitmap bitmap, DeBitmapTask task) {
super(mContext.getResources(), bitmap);
weakrefTask = new WeakReference<DeBitmapTask>(task);
}
public DeBitmapTask getWeakrefTask() {
return weakrefTask.get();
}
}