Android开发 ListView,Gallery,GridView等图片性能优化

ListView,Gallery,GridView等控件,在载入大量图片时,很容易会产生OutOfMemoryError异常,即内存溢出.因为每个应用可用内存是有限的,但是图片却很占内存,JPG,PNG本身就是压缩格式,如果分辨率高,很可能保存到磁盘中只有几百K,但是读到内存中会占用十几M内存.图片一多,自然就OutOfMemoryError了.
解决这个问题,需要考虑两个方面.
一是按需载入图片,即读图片的缩略图.比如,每个ImageView在屏幕上的面积是120dp*120dp,但是这个图片是高清的,分辨率2560*1600,如果直接载入整个图片,占用的内存是2560*1600*4/1024/1024=15.625M,如果一屏显示5个Item,占用的内存就是78.125M!而Galaxy Nexus给每个应用分配的内存只有64M,低配机型更少.所以,这里必须根据需要的分辨率载入图片.BitmapFactory解析图片时可以传入一个BitmapFactory.Options参数.


BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = false;
options.inSampleSize = inSampleSize;
Bitmap bitmap = BitmapFactory.decodeFile(filePath, options);

这里最重要的是inSampleSize,这是一个int值,等于1时,返回原图,大于1时,返回宽高为原图1/inSampleSize的图片,
所以,我们需要先根据需要的大小,计算inSampleSize.


		BitmapFactory.Options options = new BitmapFactory.Options();
                //设为true,不会去解析图片,只解析边界,即宽高
		options.inJustDecodeBounds = true;
		BitmapFactory.decodeFile(jpgPath, options);
         //读出图片真实的宽高
		int[] size = new int[2];
		size[0] = options.outWidth;
		size[1] = options.outHeight;

		float realWidth = size[0];
		float realHeight = size[1];

		// 如果图片尺寸比最大值小,直接返回
		if (maxWidth > realWidth && maxHeight > realHeight) {
			return 1;
		}
		// 计算宽高比
		float target_ratio = (float) maxWidth / maxHeight;
		float real_ratio = realWidth / realHeight;
		int inSampleSize = 1;
		if (real_ratio > target_ratio) {
			// 如果width太大,height太小,以width为基准,把realWidth设为maxWidth,realHeight缩放
			inSampleSize = (int) realWidth / maxWidth;
		} else {
			inSampleSize = (int) realHeight / maxHeight;
		}

虽然经过上面的处理,图片占用的内存已经大幅减少,放个百来个Item也不会OutOfMemoryError了,但很多应用都是下拉刷新,增加Item,Item的数量不可控,如果所有的图片都放内存里,溢出也是早晚的事.这里就需要做个图片缓存,内存里只保留需要用到的,没用到的,释放.我这里做了二级缓存,第一级放内存,第二级放SD卡.
说到图片内存缓存,网上大部分文章推荐软引用(SoftReference) 和弱引用(WeakReference),但是从Android 2.3开始,所有软引用和弱引用的对象在GC时都会被回收,根本起不到缓存的作用,Google推荐使用LruCache(http://developer.android.com/reference/android/util/LruCache.html),该类可以分配指定大小的缓存空间,当达到临界值时,最先保存的对象会被挤出,释放内存.
下面是一个使用LruCache的适配器:


package com.pocketdigi.googleimage;

import java.util.List;

import android.app.ActivityManager;
import android.content.Context;
import android.graphics.Bitmap;
import android.support.v4.util.LruCache;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import com.pocketdigi.googleimage.mode.ApiResult.ImageData;
import com.pocketdigi.utils.ImageUtils;
import com.pocketdigi.utils.ImageUtils.DownBitmapListener;
import com.pocketdigi.views.ImageViewer;

public class GoogleImageAdapter extends BaseAdapter{
	Context mContext;
	List mImageList;
	LayoutInflater mInflater;
	boolean mBusy = false;
	// 图片缓存,Key是url,Value就是Bitmap
	LruCache mImageCache;
	public GoogleImageAdapter(Context context, List imageList) {
		mContext = context;
		mImageList = imageList;
		mInflater = LayoutInflater.from(mContext);
		//读取应用可用内存大小,这里读出来,单位是兆,Galaxy Nexus是64M
		final int memClass = ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();
		//取1/8作为图片缓存
		final int maxSize = 1024 * 1024 * memClass / 8;
		mImageCache = new LruCache(maxSize) {
			// 必须覆写sizeOf方法,因为默认返回的是1,即统计的是对象的数量,而不是占用的内存
			@Override
			protected int sizeOf(String key, Bitmap value) {
				// TODO 自动生成的方法存根
				return value.getByteCount();
			}
		};

	}

	@Override
	public int getCount() {
		// TODO 自动生成的方法存根
		return mImageList.size();
	}

	@Override
	public Object getItem(int position) {
		// TODO 自动生成的方法存根
		return mImageList.get(position);
	}

	@Override
	public long getItemId(int position) {
		// TODO 自动生成的方法存根
		return position;
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		// TODO 自动生成的方法存根
		final ImageData imageData = mImageList.get(position);
		ViewHolder holder = null;
		if (convertView == null) {
			convertView = mInflater.inflate(R.layout.item_listview, null);
			holder = new ViewHolder();
			holder.tv_title = (TextView) convertView.findViewById(R.id.tv_title);
			holder.iv_thumbnail = (ImageView) convertView.findViewById(R.id.iv_thumbnail);
			holder.thumbnail_url = imageData.getThumbnail_url();
			convertView.setTag(holder);
		} else {
			holder = (ViewHolder) convertView.getTag();
			if (!holder.thumbnail_url.equals(imageData.getThumbnail_url())) {
				holder.iv_thumbnail.setImageResource(R.drawable.loading);
			}
		}

		holder.tv_title.setText(imageData.getAbs());
		if (!isBusy()) {
			final String imgUrl = imageData.getThumbnail_url();
			Bitmap bmp = mImageCache.get(imgUrl);
			if (bmp != null) {
				holder.iv_thumbnail.setImageBitmap(bmp);
			} else {
				loadBmpFromNetWork(imgUrl);
			}

		}

		holder.iv_thumbnail.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View v) {
				//点击ImageView显示大图
				ImageViewer imageViewer = new ImageViewer(mContext);
				imageViewer.setSourceView((ImageView) v);
				imageViewer.setJpgUrl(imageData.getImage_url());
				imageViewer.show();
			}
		});

		return convertView;
	}
	/**
	 * 从网络下载图片
	 * @param imgUrl
	 */
	private void loadBmpFromNetWork(final String imgUrl) {
		//异步从网络下载图片,这个异步下载方法,是有作磁盘缓存的
		//缓存原理,根据传入的url,计算md5,作缓存文件名,下载前先判断该文件存不存在,如果存在,从SD卡读,不存在,再去下载
		//参数120,120,是返回120*120的bitmap,避免在载入大图时浪费内存
		ImageUtils.asynchronousDownloadBitmap(imgUrl, 120, 120, new DownBitmapListener() {
			@Override
			public void onComplete(Bitmap bmp) {
				// TODO 自动生成的方法存根
				if(imgUrl!=null&&bmp!=null)
				{
					mImageCache.put(imgUrl, bmp);
					notifyDataSetChanged();
				}
			}

			@Override
			public void onProgressChanged(long total, long downloaded) {
				// TODO 自动生成的方法存根

			}
		});
	}

	@Override
	public int getItemViewType(int position) {
		// TODO 自动生成的方法存根
		return super.getItemViewType(position);
	}

	@Override
	public int getViewTypeCount() {
		// TODO 自动生成的方法存根
		return super.getViewTypeCount();
	}
	//用来保存各个控件的引用
	static class ViewHolder {
		TextView tv_title;
		ImageView iv_thumbnail;
		String thumbnail_url;
	}



	public boolean isBusy() {
		return mBusy;
	}

	public void setBusy(boolean busy) {
		this.mBusy = busy;
	}

}

© 2013, 冰冻鱼. 请尊重作者劳动成果,复制转载保留本站链接! 应用开发笔记

发表评论

电子邮件地址不会被公开。 必填项已用*标注