Android之打造自己載入高清大圖及瀑布流框架.解決錯位等問題.
阿新 • • 發佈:2018-12-31
首先看效果圖如下:
本框架支援本地圖片和網路圖片的獲取.採用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.先進先載入,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();//初始化完成後訊號量 -1 Looper.loop(); } };
從上面程式碼可以看出PoolTreadHandler收到一個訊息後會讓mThreadPool去執行一個任務,該任務通過getTask()方法獲得一個Runnable物件,並且讓訊號量增加表示,執行緒池中有一個任務了.
看看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是一個LinkedList<Runnable>型別的集合.所以核心在於方法Load()
/**
* 載入圖片
*
* @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);
}
其中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;//唯一的
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);
上面載入方式是直接交給內部處理.圖片預設載入RGB_565.
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相近,但是效果很差,效果圖如下: