ImageLoader的实现

一般来说,一个优秀的ImageLoader应该具备如下功能:

  • 图片的同步加载
  • 图片的异步加载
  • 图片压缩
  • 内存缓存
  • 磁盘缓存
  • 网络拉取
ImageLoader的完整代码如下:
package com.example.imageloader;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.os.StatFs;
import android.util.Log;
import android.util.LruCache;
import android.widget.ImageView;

public class ImageLoader {
	private static final String TAG = ImageLoader.class.getSimpleName();
	public static final int MESSAGE_POST_RESULT = 1;
	private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
	private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
	private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
	private static final long KEEP_ALIVE = 10L;

	private static final int TAG_KEY_URI = R.id.imageloder_uri;
	private static final int DISK_CACHE_SIZE = 1024 * 1024 * 100;
	private static final int IO_BUFFER_SIZE = 1024 * 8;
	private static final int DISK_CACHE_INDEX = 0;
	private boolean mIsDiskLruCacheCreated = false;

	/**
	 * 线程制造工厂
	 */
	private static final ThreadFactory sThreadFactory = new ThreadFactory() {
		private final AtomicInteger mCount = new AtomicInteger(1);

		@Override
		public Thread newThread(Runnable r) {

			return new Thread(r, "imageloder#" + mCount.getAndIncrement());
		}
	};

	/**
	 * 自定义线程池
	 */
	public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE,
			KEEP_ALIVE, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>(), sThreadFactory);

	private Handler mMainHandler = new Handler() {
		public void handleMessage(android.os.Message msg) {
			LoaderResult result = (LoaderResult) msg.obj;
			ImageView imageView = result.imageView;
			String uri = (String) imageView.getTag(TAG_KEY_URI);
			if (uri.equals(result.uri)) {
				imageView.setImageBitmap(result.bitmap);
			} else {
				Log.w(TAG, "see image bitmap ,but rul has changed,ignored!");
			}
		};
	};

	private Context mContext;
	private ImageResizer mImageResizer = new ImageResizer();
	private LruCache<String, Bitmap> mMemoryCache;
	private DiskLruCache mDiskLruCache;

	private ImageLoader(Context context) {
		mContext = context.getApplicationContext();
		// 当前进程最大内存
		int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
		int cacheSize = maxMemory / 8;

		mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
			@Override
			protected int sizeOf(String key, Bitmap value) {
				// 计算缓存大小
				return value.getRowBytes() * value.getHeight() / 1024;
			}
		};

		File diskCacheDir = getDiskCacheDir(mContext, "bitmap");
		if (!diskCacheDir.exists()) {
			diskCacheDir.mkdirs();
		}
		/**
		 * 判断当前设备剩余的存储空间是否足够
		 */
		if (getUsableSpace(diskCacheDir) > DISK_CACHE_SIZE) {
			try {
				mDiskLruCache = DiskLruCache.open(diskCacheDir, 1, 1, DISK_CACHE_SIZE);
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * 创建ImageLoader单例
	 * 
	 * @param context
	 * @return
	 */
	public static ImageLoader build(Context context) {
		return new ImageLoader(context);
	}

	/**
	 * 添加到内存
	 * 
	 * @param key
	 * @param bitmap
	 */
	private void addBitmapToMemoryCache(String key, Bitmap bitmap) {
		if (getBitmapFromMemoryCache(key) == null) {
			mMemoryCache.put(key, bitmap);
		}
	}

	/**
	 * 从内存中取出
	 * 
	 * @param key
	 * @return
	 */
	private Bitmap getBitmapFromMemoryCache(String key) {
		return mMemoryCache.get(key);
	}

	/**
	 * 异步加载和显示图片
	 * 
	 * @param uri
	 * @param imageView
	 */
	public void bindBitmap(final String uri, final ImageView imageView) {
		bindBitmap(uri, imageView, 0, 0);
	}

	public void bindBitmap(final String uri, final ImageView imageView, final int reqWidth, final int reqHeight) {
		imageView.setTag(TAG_KEY_URI, uri);
		Bitmap bitmap = loadBitmapFromMemCache(uri);
		if (bitmap != null) {
			imageView.setImageBitmap(bitmap);
			return;
		}

		Runnable loadBitmapTask = new Runnable() {

			@Override
			public void run() {
				Bitmap bitmap = loadBitmap(uri, reqWidth, reqHeight);
				if (bitmap != null) {
					LoaderResult result = new LoaderResult(imageView, uri, bitmap);
					mMainHandler.obtainMessage(MESSAGE_POST_RESULT, result).sendToTarget();
				}
			}
		};

		THREAD_POOL_EXECUTOR.execute(loadBitmapTask);
	}

	protected Bitmap loadBitmap(String uri, int reqWidth, int reqHeight) {
		Bitmap bitmap = loadBitmapFromMemCache(uri);
		if (bitmap != null) {
			Log.d(TAG, "loadBitmapFromMemCache,url:" + uri);
			return bitmap;
		}
		try {
			bitmap = loadBitmapFromDiskCache(uri, reqWidth, reqHeight);
			if (bitmap != null) {
				Log.d(TAG, "loadBitmapFromDiskCache,url:" + uri);
				return bitmap;
			}

			bitmap = loadBitmapFromHttp(uri, reqWidth, reqHeight);
			Log.d(TAG, "loadBitmapFromHttp,url:" + uri);
		} catch (IOException e) {
			e.printStackTrace();
		}
		if (bitmap == null && !mIsDiskLruCacheCreated) {
			Log.w(TAG, "encounter error,DiskChache is not created.");
			bitmap = downloadBitmapFromUrl(uri);
		}
		return bitmap;
	}

	/**
	 * 从网络下载图片不缓存
	 * 
	 * @param uri
	 * @return
	 */
	private Bitmap downloadBitmapFromUrl(String uri) {
		Bitmap bitmap = null;
		HttpURLConnection urlConnection = null;
		BufferedInputStream in = null;

		try {
			URL url = new URL(uri);
			urlConnection = (HttpURLConnection) url.openConnection();
			in = new BufferedInputStream(urlConnection.getInputStream(), IO_BUFFER_SIZE);
			bitmap = BitmapFactory.decodeStream(in);

		} catch (IOException e) {
			Log.e(TAG, "Error in downloadBitmap:" + e);
		} finally {
			if (urlConnection != null) {
				urlConnection.disconnect();
			}

			MyUtils.close(in);
		}

		return bitmap;
	}

	/**
	 * 从网络下载图片并缓存
	 * 
	 * @param uri
	 * @param reqWidth
	 * @param reqHeight
	 * @return
	 * @throws IOException
	 */
	private Bitmap loadBitmapFromHttp(String uri, int reqWidth, int reqHeight) throws IOException {
		if (Looper.myLooper() == Looper.getMainLooper()) {
			throw new RuntimeException("can not visit network from UI Thread");
		}
		if (mDiskLruCache == null) {
			return null;
		}
		String key = hashKeyFromUrl(uri);
		DiskLruCache.Editor editor = mDiskLruCache.edit(key);
		if (editor != null) {
			OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX);
			if (downloadUrlToStream(uri, outputStream)) {
				editor.commit();
			} else {
				editor.abort();
			}
			mDiskLruCache.flush();
		}

		return loadBitmapFromDiskCache(uri, reqWidth, reqHeight);
	}

	private boolean downloadUrlToStream(String uri, OutputStream outputStream) {
		HttpURLConnection urlConnection = null;
		BufferedOutputStream out = null;
		BufferedInputStream in = null;

		try {
			final URL url = new URL(uri);
			urlConnection = (HttpURLConnection) url.openConnection();
			in = new BufferedInputStream(urlConnection.getInputStream(), IO_BUFFER_SIZE);
			out = new BufferedOutputStream(outputStream, IO_BUFFER_SIZE);

			int b;
			while ((b = in.read()) != -1) {
				out.write(b);
			}

			return true;
		} catch (IOException e) {
			Log.e(TAG, "downloadBitmap failed." + e);
		} finally {
			if (urlConnection != null) {
				urlConnection.disconnect();
			}
			MyUtils.close(out);
			MyUtils.close(in);
		}

		return false;
	}

	/**
	 * 从磁盘获取缓存并压缩存入内存当中
	 * 
	 * @param uri
	 * @param reqWidth
	 * @param reqHeight
	 * @return
	 * @throws IOException
	 */
	private Bitmap loadBitmapFromDiskCache(String uri, int reqWidth, int reqHeight) throws IOException {
		if (Looper.myLooper() == Looper.getMainLooper()) {
			Log.w(TAG, "loadBitmap from UI Thread,it's not recommended!");
		}
		if (mDiskLruCache == null) {
			return null;
		}

		Bitmap bitmap = null;
		String key = hashKeyFromUrl(uri);
		DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
		if (snapshot != null) {
			FileInputStream fileInputStream = (FileInputStream) snapshot.getInputStream(DISK_CACHE_INDEX);
			FileDescriptor fileDescriptor = fileInputStream.getFD();
			bitmap = mImageResizer.decodeSampledBitmapFromDescriptor(fileDescriptor, reqWidth, reqHeight);
			if (bitmap != null) {
				addBitmapToMemoryCache(key, bitmap);
			}
		}

		return bitmap;
	}

	/**
	 * 向内存中添加
	 * 
	 * @param url
	 * @return
	 */
	private Bitmap loadBitmapFromMemCache(String url) {
		final String key = hashKeyFromUrl(url);
		Bitmap bitmap = getBitmapFromMemoryCache(key);
		return bitmap;
	}

	/**
	 * 对url进行md5加密
	 * 
	 * @param url
	 * @return
	 */
	private String hashKeyFromUrl(String url) {
		String cacheKey;
		try {
			final MessageDigest mDigest = MessageDigest.getInstance("MD5");
			mDigest.update(url.getBytes());
			cacheKey = bytesToHexString(mDigest.digest());

		} catch (NoSuchAlgorithmException e) {
			cacheKey = String.valueOf(url.hashCode());
		}
		return cacheKey;
	}

	/**
	 * 字节数组转化为16进制的字符串
	 * 
	 * @param bytes
	 * @return
	 */
	private String bytesToHexString(byte[] bytes) {
		StringBuilder sb = new StringBuilder();

		for (int i = 0; i < bytes.length; i++) {
			String hex = Integer.toHexString(0xFF & bytes[i]);
			if (hex.length() == 1) {
				sb.append('0');
			}
			sb.append(hex);
		}
		return sb.toString();
	}

	/**
	 * 获取设备的缓存路径
	 * 
	 * @param context
	 * @param uniqueName
	 * @return
	 */
	public File getDiskCacheDir(Context context, String uniqueName) {
		boolean externalStorageAvaliable = Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
		final String cachePath;
		if (externalStorageAvaliable) {
			cachePath = context.getExternalCacheDir().getPath();
		} else {
			cachePath = context.getCacheDir().getPath();
		}

		return new File(cachePath + File.separator + uniqueName);
	}

	/**
	 * sd卡剩余空间
	 * 
	 * @param path
	 * @return
	 */
	@TargetApi(VERSION_CODES.GINGERBREAD)
	private long getUsableSpace(File path) {
		if (Build.VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD) {
			return path.getUsableSpace();
		}
		final StatFs stats = new StatFs(path.getPath());
		return (long) stats.getBlockSize() * (long) stats.getAvailableBlocks();
	}

	/**
	 * bitmap包装类
	 * 
	 * @author yl
	 *
	 */
	private static class LoaderResult {
		private ImageView imageView;
		private String uri;
		private Bitmap bitmap;

		public LoaderResult(ImageView imageView, String uri, Bitmap bitmap) {
			super();
			this.imageView = imageView;
			this.uri = uri;
			this.bitmap = bitmap;
		}

	}
}
ImageResizer的代码如下:
import java.io.FileDescriptor;

import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapFactory.Options;
import android.util.Log;

public class ImageResizer {

	/**
	 * 缩放资源id为resId的图片
	 * 
	 * @param res
	 * @param resId
	 *            资源id
	 * @param reqWidth
	 *            缩放后的宽度
	 * @param reqHeight
	 *            缩放后的高度
	 * @return
	 */
	public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) {
		BitmapFactory.Options options = new BitmapFactory.Options();
		options.inJustDecodeBounds = true;
		BitmapFactory.decodeResource(res, resId, options);

		options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
		options.inJustDecodeBounds = false;

		return BitmapFactory.decodeResource(res, resId, options);
	}

	/**
	 * 对图片文件进行缩放
	 * @param fd
	 * @param reqWidth
	 * @param reqHeight
	 * @return
	 */
	public static Bitmap decodeSampledBitmapFromDescriptor(FileDescriptor fd, int reqWidth, int reqHeight) {
		BitmapFactory.Options options = new BitmapFactory.Options();
		options.inJustDecodeBounds = true;
		BitmapFactory.decodeFileDescriptor(fd,null, options);

		options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
		options.inJustDecodeBounds = false;

		try {
			return BitmapFactory.decodeFileDescriptor(fd, null, options);
		} catch (Exception e) {
			Log.e("ImageResizer", "decodeFileDescriptor error:"+e);
		}
		return null;
	}
	/**
	 * 对图片文件进行缩放
	 * @param path
	 * @param reqWidth
	 * @param reqHeight
	 * @return
	 */
	public static Bitmap decodeSampledBitmapFromFile(String path, int reqWidth, int reqHeight) {
		BitmapFactory.Options options = new BitmapFactory.Options();
		options.inJustDecodeBounds = true;
		BitmapFactory.decodeFile(path, options);
		
		options.inSampleSize = calculateInSampleSizeFromFile(options, reqWidth, reqHeight);
		options.inJustDecodeBounds = false;
		
		return BitmapFactory.decodeFile(path, options);
	}

	/**
	 * 计算inSampleSize
	 * 
	 * @param options
	 * @param reqWidth
	 *            缩放后的宽度
	 * @param reqHeight
	 *            缩放后的高度
	 * @return
	 */
	private static int calculateInSampleSize(Options options, int reqWidth, int reqHeight) {
		final int height = options.outHeight;
		final int width = options.outWidth;
		int inSampleSize = 1;
		if (height > reqHeight || width > reqWidth) {
			final int halfHeight = height / 2;
			final int halfWidth = width / 2;
			// 计算inSampleSize直到缩放后的宽高都小于指定的宽高
			while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) {
				inSampleSize *= 2;
			}

		}

		System.out.println(inSampleSize);
		return inSampleSize;
	}

	/**
	 * 
	 * @param options
	 * @param reqWidth
	 * @param reqHeight
	 * @return
	 */
	private static int calculateInSampleSizeFromFile(Options options, int reqWidth, int reqHeight) {
		//获取比例大小
		int wRatio = (int)Math.ceil(options.outWidth/reqWidth);
		int hRatio = (int)Math.ceil(options.outHeight/reqHeight);
		//如果超出指定大小,则缩小相应的比例
		if(wRatio > 1 && hRatio > 1){
			if(wRatio > hRatio){
				options.inSampleSize = wRatio;
			}else{
				options.inSampleSize = hRatio;
			}
		}
		System.out.println(options.inSampleSize);
		return options.inSampleSize;
	}
}
MyUtils的代码如下:
import java.io.Closeable;
import java.io.IOException;

public class MyUtils {
	/**
	 * 关闭当前流
	 * 
	 * @param closeable
	 */
	public static void close(Closeable closeable) {// 只需要把你想要关闭的流传入就可以关闭此流了
		if (null != closeable) {
			try {
				closeable.close();// 此接口只有一个关闭流的方法
			} catch (IOException e) {
				System.out.println("关闭流出错了,错误信息---->" + e);
			}
		}
	}
}
DiskLruCache的源码可在这里下载
http://download.csdn.net/detail/yule12345/9819313

注意事项:
  • 这里采用了线程池和Handler,没有直接用AsycTask,是因为AsyncTask在3.0以上版本默认是串行的执行任务,这显然不能体现ImageLoader的并发特性。不过依然可以通过改造AsyncTask来达到并发效果。
  • 之所以对url进行MD5加密转换,是因为图片的url中很可能有特殊字符,影响url在android中直接使用
  • 在给ImageView设置图片之前可以检查它的url有没有发生改变,如果发生改变就不在给它设置图片,这样就解决了列表所谓的问题
  • 在列表处于滑动状态时,修改布尔参数,在adpter的getview方法中根据改参数值来判断是否停止加载图片,这样可以解决滑动带来的卡顿问题


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值