首先看效果图如下:
https://github.com/q422013/ImageLoader
本框架支持本地图片和网络图片的获取.采用LruCache算法,最少使用的最先释放.有效的避免OOM,项目结构图:
核心加载类在于ImageLoader.采用了TreadPool去做并发请求.UI处理采用Handler去管理,实现的思路类似于AsnycTask类.该类采用单例模式:
public static ImageLoader getInstance(Context context) { if (null == loader) { synchronized (ImageLoader.class) { if (null == loader) { loader = new ImageLoader(context, defThreadCount, mType); } } } return loader; } public static ImageLoader getInstance(Context context, int threadCount, Type type) { if (null == loader) { synchronized (ImageLoader.class) { if (null == loader) { loader = new ImageLoader(context, threadCount, type); } } } return loader; }
/** * 队列调度模式 */ public enum Type { FIFO, LIFO }工作线程中核心是用Loop去不断的取消息,取到消息后就加入到线程池当中去执行,这样减少了自己去维护轮训,减少内存开销.
//工作线程 mThread = new Thread() { @Override public void run() { Looper.prepare(); mPoolThreadHandler = new Handler() { @Override public void handleMessage(Message msg) { mThreadPool.execute(getTask()); try { mPoolSemaphore.acquire();//信号量 + 1 } catch (InterruptedException e) { e.printStackTrace(); } } }; mSemapHore.release();//初始化完成后信号量 -1 Looper.loop(); } };
看看getTask()代码很简单,仅仅是将任务按不同的方式取出来:
/** * 获取任务 * * @return */ private synchronized Runnable getTask() { if (0 < mTask.size()) { if (mType == Type.LIFO) return mTask.removeFirst(); else return mTask.removeLast(); } return null; }
/** * 加载图片 * * @param path * @param imageview */ public void load(final String path, final View view, final LoadListener<View> loadListener) { if (null == path) throw new RuntimeException("this path is null"); if (null == loadListener) throw new RuntimeException("this loadListener is null"); view.setTag(path); //1.从磁盘,2.从内存 if (null == mDisPlayHandler) mDisPlayHandler = new Handler() { @Override public void handleMessage(Message msg) { int code = msg.what; ViewBeanHolder holder = (ViewBeanHolder) msg.obj; final View view = holder.view; Bitmap bm = holder.bitmap; String path = holder.path; switch (code) { case LOAD_SUCCESS://加载成功 if (view.getTag().toString().equals(path)) { loadListener.LoadSuccess(view, bm, path); if (isNeedAnim) new LoadAnimCore(view); } break; case LOAD_ING://加载中 if (view.getTag().toString().equals(path)) { loadListener.Loading(view, path); } break; case LOAD_FAILE://加载失败 if (view.getTag().toString().equals(path)) { loadListener.LoadError(view, path, null);//暂时消息为空 } break; } } }; addTask(path, view); }
/** * 添加任务 * * @param path * @param view */ private synchronized void addTask(final String path, final View view) { Runnable runnable = new Runnable() { @Override public void run() { ViewBeanHolder holder = new ViewBeanHolder(); holder.view = view; holder.path = path; sendMsg(LOAD_ING, holder); //TODO 从内存中获取 Bitmap bitmap = LruCacheUtils.getInstance().get(path); if (null == bitmap) { //TODO 从磁盘中获取 String tempPath = getImageFromDiskUrl(path); if (null != tempPath) { bitmap = decodeSampledBitmapFromResource(tempPath, (ImageView)view); } else { if (null == bitmap) { // TODO 从网络中获取 bitmap = decodeSampledBitmapFromNetWork(path, (ImageView)view); } else { // TODO 失败 sendMsg(LOAD_FAILE, holder); } } } //加载成功 if (null != bitmap) { LruCacheUtils.getInstance().put(path, bitmap); holder.bitmap = bitmap;//唯一的 sendMsg(LOAD_SUCCESS, holder); } else { //加载失败 sendMsg(LOAD_FAILE, holder); } } }; if (null == mPoolThreadHandler) { try { mSemapHore.acquire(); } catch (InterruptedException e) { e.printStackTrace(); } } mTask.add(runnable); mPoolThreadHandler.sendEmptyMessage(0x1000); mPoolSemaphore.release();//信号量 -1 }
缓存策略:先从内存中获取,如果没有获取到,就从磁盘获取,磁盘也没有获取到,那就从网络获取.最后并将该bitmap设置到内存缓存,假象:如果设置非常多的bitmap到内存缓存中肯定会让内存占满导致OOM,所以便采用了google推荐使用的LruCache缓存算法.该算法可以实现固定内存加载,并且最近少使用的会被内存回收掉.
然后在MainActivity中可以使用如下:
ImageLoader.getInstance(MainActivity.this, 3, ImageLoader.Type.LIFO).load(IMAGES[position], holder.imageView);
ImageLoader.getInstance(MainActivity.this, 3, ImageLoader.Type.LIFO).load(IMAGES[position], holder.imageView, new LoadListener<View>() { @Override public <T> void Loading(View view, String path) { } @Override public <T> void LoadSuccess(View view, Bitmap bitmap, String path) { ((ImageView) view).setImageBitmap(bitmap); } @Override public <T> void LoadError(View view, String path, String errorMsg) { Log.d("Tanck","加载失败:"+path); ((ImageView)view).setImageResource(R.mipmap.ic_launcher); } });
采用几个加载配置方式内存对比:
RGB_565:
约11.31MB,效果如下:
ARGB_8888:
约12.86MB效果图如下:
可以看出差别不是很大.
但是ARGB_4444使用内存和RGB_565相近,但是效果很差,效果图如下: