首先看效果圖以下:
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_基于應用拆分的技術架構