ImageLoader.java类是加载网络中的图片,并将图片保存到本地,也可以将图片设置到对应的控件中。
package com.example.image; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.widget.ImageView; import com.example.R; import com.example.http.HttpHelper; import com.example.http.HttpHelper.HttpResult; import com.example.manage.ThreadManager; import com.example.manage.ThreadManager.ThreadPoolProxy; import com.example.utils.DrawableUtils; import com.example.utils.FileUtils; import com.example.utils.IOUtils; import com.example.utils.LogUtils; import com.example.utils.StringUtils; import com.example.utils.SystemUtils; import com.example.utils.UIUtils; /** * Created by mwqi on 2014/6/8. */ public class ImageLoader { /** 图片下载的线程池名称 */ public static final String THREAD_POOL_NAME = "IMAGE_THREAD_POOL"; /** 图片缓存最大数量 */ public static final int MAX_DRAWABLE_COUNT = 100; /** 图片的KEY缓存 */ private static ConcurrentLinkedQueue<String> mKeyCache = new ConcurrentLinkedQueue<String>(); /** 图片的缓存 */ private static Map<String, Drawable> mDrawableCache = new ConcurrentHashMap<String, Drawable>(); private static BitmapFactory.Options mOptions = new BitmapFactory.Options(); /** 图片下载的线程池 */ private static ThreadPoolProxy mThreadPool = ThreadManager.getSinglePool(THREAD_POOL_NAME); /** 用于记录图片下载的任务,以便取消 */ private static ConcurrentHashMap<String, Runnable> mMapRuunable = new ConcurrentHashMap<String, Runnable>(); /** 图片的总大小 */ private static long mTotalSize; static { mOptions.inDither = false;// 设置为false,将不考虑图片的抖动值,这会减少图片的内存占用 mOptions.inPurgeable = true;// 设置为ture,表示允许系统在内存不足时,删除bitmap的数组。 mOptions.inInputShareable = true;// 和inPurgeable配合使用,如果inPurgeable是false,那么该参数将被忽略,表示是否对bitmap的数组进行共享 } /** 加载图片 */ public static void load(ImageView view, String url) { if (view == null || StringUtils.isEmpty(url)) { return; } view.setTag(url);//把控件和图片的url进行绑定,因为加载是一个耗时的,等加载完毕了需要判定该控件是否和该url匹配 Drawable drawable = loadFromMemory(url);//从内存中加载 if (drawable != null) { setImageSafe(view, url, drawable);//如果内存中加载到了,直接设置图片 } else { view.setImageResource(R.drawable.ic_default);//如果没加载到,设置默认图片,并异步加载 asyncLoad(view, url); } } /** 异步加载 */ private static void asyncLoad(final ImageView view, final String url) { //先创建一个runnable封装执行过程 Runnable runnable = new Runnable() { @Override public void run() { mMapRuunable.remove(url); Drawable drawable = loadFromLocal(url); if (drawable == null) { drawable = loadFromNet(url); } if (drawable != null) { setImageSafe(view, url, drawable); } } }; cancel(url);//先取消这个url的下载 mMapRuunable.put(url, runnable);//记住这个runnable,以便后面取消 mThreadPool.execute(runnable);//执行任务 } /** 取消下载 */ public static void cancel(String url) { Runnable runnable = mMapRuunable.remove(url);//根据url来获取指定的runnable if (runnable != null) { mThreadPool.cancel(runnable);//从线程池中删除该任务,如果任务已经开始下载了,就无法删除 } } /** 从内存中加载 */ private static Drawable loadFromMemory(String url) { Drawable drawable = mDrawableCache.get(url); if (drawable != null) {//从内存中获取到了,需要重新放到内存队列的最后,以便满足LRC //一般缓存算法有两种,第一是LFU,按使用次数来判定删除优先级,使用次数最少的最先删除 //还有一个就是LRC,就是按最后使用时间来判定删除优先级,最后使用时间越早的最先删除 addDrawableToMemory(url, drawable); } return drawable; } /** 从本地设备中加载 */ private static Drawable loadFromLocal(String url) { Bitmap bitmap = null; Drawable drawable = null; String path = FileUtils.getIconDir(); FileInputStream fis = null; try { //获取流 fis = new FileInputStream(new File(path + url)); if (fis != null) { // BitmapFactory.decodeByteArray(data, offset, length) // BitmapFactory.decodeFile(pathName) // BitmapFactory.decodeStream(is) // 上面三个分析源码可知,他们都是在Java层创建byte数组,然后把数据传递给本地代码。 // 下面这个是把文件描述符传递给本地代码,由本地代码去创建图片 // 优点,由于是本地代码创建的,那么byte数组的内存占用不会算到应用内存中,并且一旦内存不足,将会把bitmap的数组回收掉,而bitmap不会被回收 // 当显示的时候,发现bitmap的数组为空时,将会再次根据文件描述符去加载图片,此时可能由于加载耗时造成界面卡顿,但总比OOM要好得多。 // 由于本地代码在创建图片时,没有对图片进行校验,所以如果文件不完整,或者根本就不是一个图片时,系统也不会报错,仍然会返回一个bitmap,但是这个bitmap是一个纯黑色的bitmap。 // 所以我们在下载图片的时候,一定要先以一个临时文件下载,等下载完毕了,再对图片进行重命名。 bitmap = BitmapFactory.decodeFileDescriptor(fis.getFD(), null, mOptions); } if (null != bitmap) {//把bitmap转换成drawable drawable = new BitmapDrawable(UIUtils.getResources(), bitmap); } if (drawable != null) {//放到内存缓存队列中 addDrawableToMemory(url, drawable); } } catch (OutOfMemoryError e) { mKeyCache.clear(); mDrawableCache.clear(); LogUtils.e(e); } catch (Exception e) { LogUtils.e(e); } finally { IOUtils.close(fis); } return drawable; } /** 从网络加载图片 */ private static Drawable loadFromNet(String url) { HttpResult httpResult = HttpHelper.download(HttpHelper.URL + "image?name=" + url); InputStream stream = null; if (httpResult == null || (stream = httpResult.getInputStream()) == null) {//请求网络 return null; } String tempPath = FileUtils.getIconDir() + url + ".temp"; String path = FileUtils.getIconDir() + url; FileUtils.writeFile(stream, tempPath, true);//把网络下载保存在本地 if (httpResult != null) {//关闭网络连接 httpResult.close(); } FileUtils.copy(tempPath, path, true);//进行改名 return loadFromLocal(url);//从本地加载 } /** 添加到内存 */ private static void addDrawableToMemory(String url, Drawable drawable) { mKeyCache.remove(url); mDrawableCache.remove(url); //如果大于等于100张,或者图片的总大小大于应用总内存的四分之一先删除前面的 while (mKeyCache.size() >= MAX_DRAWABLE_COUNT || mTotalSize >= SystemUtils.getOneAppMaxMemory() / 4) { String firstUrl = mKeyCache.remove(); Drawable remove = mDrawableCache.remove(firstUrl); mTotalSize -= DrawableUtils.getDrawableSize(remove); } mKeyCache.add(url);//添加 mDrawableCache.put(url, drawable); mTotalSize += DrawableUtils.getDrawableSize(drawable); } /** 设置给控件图片 */ private static void setImageSafe(final ImageView view, final String url, final Drawable drawable) { if (drawable == null && view.getTag() == null) { return; } UIUtils.runInMainThread(new Runnable() {//需要在主线程中设置 @Override public void run() { Object tag;//在主线程中判断,可以做到同步 if ((tag = view.getTag()) != null) { String str = (String) tag; if (StringUtils.isEquals(str, url)) {//检测如果url和控件匹配 view.setImageDrawable(drawable);//就进行图片设置 } } } }); } }