首先看效果图以下:
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;
}
第1种类不需要配置线程池及加载方式.加载方式分为两种:1.先进先加载,2.落后先加载.
/**
* 队列调度模式
*/
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();//初始化完成后信号量 ⑴
Looper.loop();
}
};
从上面代码可以看出PoolTreadHandler收到1个消息后会让mThreadPool去履行1个任务,该任务通过getTask()方法取得1个Runnable对象,并且让信号量增加表示,线程池中有1个任务了.
看看getTask()代码很简单,仅仅是将任务按不同的方式取出来:
/**
* 获得任务
*
* @return
*/
private synchronized Runnable getTask() {
if (0 < mTask.size()) { if (mType == Type.LIFO) return mTask.removeFirst(); else return mTask.removeLast(); } return null; }
真实的工作在于mTask去add,mTask是1个LinkedList类型的集合.所以核心在于方法Load()
/**
* 加载图片
*
* @param path
* @param imageview
*/
public void load(final String path, final View view, final LoadListenerloadListener) {
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);
}
其中view.setTag是为了避免错乱.上面代码可以看出来仅仅是用于callBack,核心的东西其实在addTask方法.我们看看addTask方法做了甚么事情:
/**
* 添加任务
*
* @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;//唯1的
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();//信号量 ⑴
}
缓存策略:先从内存中获得,如果没有获得到,就从磁盘获得,磁盘也没有获得到,那就从网络获得.最后并将该bitmap设置到内存缓存,假象:如果设置非常多的bitmap到内存缓存中肯定会让内存占满致使OOM,所以便采取了google推荐使用的LruCache缓存算法.该算法可以实现固定内存加载,并且最近少使用的会被内存回收掉.
然后在MainActivity中可使用以下:
ImageLoader.getInstance(MainActivity.this, 3, ImageLoader.Type.LIFO).load(IMAGES[position], holder.imageView);
上面加载方式是直接交给内部处理.图片默许加载RGB_565.
ImageLoader.getInstance(MainActivity.this, 3, ImageLoader.Type.LIFO).load(IMAGES[position], holder.imageView, new LoadListener() {
@Override
publicvoid Loading(View view, String path) {
}
@Override
publicvoid LoadSuccess(View view, Bitmap bitmap, String path) {
((ImageView) view).setImageBitmap(bitmap);
}
@Override
publicvoid 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相近,但是效果很差,效果图以下:
上一篇 学习做人的道理
下一篇 01_基于应用拆分的技术架构