上一篇已經介紹了android開發中對圖檔的壓縮和緩存,這一片我們将詳細介紹如何對圖檔使用多線程加載、根據imageveiw大小對圖檔進行壓縮、對最近使用的圖檔進行緩存,避免多次加載。
1.根據imageview大小對圖檔進行合理壓縮,隻為壓縮後的bitmap配置設定記憶體。
第一步:根據ImageView獲得适當的壓縮的寬和高
第二步:根據imageview的width、height計算其BitmapFactory.Options的inSampleSize
第三部:根據BitmapFactory.Options圖檔路徑通過BitmapFactory.decodeFile(pathName, options);的到壓縮後的bitmap
2.使用線程池加載圖檔
第一步:定義LinkedList<Runnable> mTasks任務隊列使用者存放下載下傳圖檔的線程,線程采用LIFO方式,每個圖檔擁有一個線程
第二步:定義private LruCache<String, Bitmap> mLruCache;使用者緩存最近加載過的圖檔。
具體實作看代碼:
package com.zjh.utils;
import java.lang.reflect.Field;
import java.util.LinkedList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.v4.util.LruCache;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.ViewGroup.LayoutParams;
import android.widget.ImageView;
public class ImageLoader {
/**
* 圖檔緩存的核心類 last recently used cache
*/
private LruCache<String, Bitmap> mLruCache;
/**
* 線程池
*/
private ExecutorService mThreadPool;
/**
* 線程池的線程數量,預設為1
*/
private int mThreadCount = 1;
/**
* 隊列的排程方式
*/
private Type mType = Type.LIFO;
/**
* 任務隊列
*/
private LinkedList<Runnable> mTasks;
/**
* 輪詢的線程
*/
private Thread mPoolThread;
/**
* 輪詢線程handler?
*/
private Handler mPoolThreadHander;
/**
* 運作在UI線程的handler,用于給ImageView設定圖檔
*/
private Handler mHandler;
/**
* 引入一個值為1的信号量,防止mPoolThreadHander未初始化完成
*/
private volatile Semaphore mSemaphore = new Semaphore(0);
/**
* 引入一個值為1的信号量,由于線程池内部也有一個阻塞線程,防止加入任務的速度過快,使LIFO效果不明顯
*/
private volatile Semaphore mPoolSemaphore;
private static ImageLoader mInstance;
/**
* 隊列的排程方式
*
* @author zhy
*
*/
public enum Type {
FIFO, LIFO
}
/**
* 單例獲得該執行個體對象
* @return
*/
public static ImageLoader getInstance() {
if (mInstance == null) {
synchronized (ImageLoader.class) {
if (mInstance == null) {
mInstance = new ImageLoader(1, Type.LIFO);
}
}
}
return mInstance;
}
private ImageLoader(int threadCount, Type type) {
init(threadCount, type);
}
private void init(int threadCount, Type type) {
// loop thread
mPoolThread = new Thread() {
@Override
public void run() {
Looper.prepare();
mPoolThreadHander = new Handler() {
@Override
public void handleMessage(Message msg) {
mThreadPool.execute(getTask());
try {
mPoolSemaphore.acquire();
} catch (InterruptedException e) {
}
}
};
// 釋放一個信号量
mSemaphore.release();
Looper.loop();
}
};
mPoolThread.start();
// 擷取應用程式最大可用記憶體
int maxMemory = (int) Runtime.getRuntime().maxMemory();
int cacheSize = maxMemory / 8;
mLruCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight();
}
};
mThreadPool = Executors.newFixedThreadPool(threadCount);
mPoolSemaphore = new Semaphore(threadCount);
mTasks = new LinkedList<Runnable>();
mType = type == null ? Type.LIFO : type;
}
/**
* 加載圖檔
*
* @param path
* @param imageView
*/
public void loadImage(final String path, final ImageView imageView) {
// set tag
imageView.setTag(path);
// UI線程
if (mHandler == null) {
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
ImgBeanHolder holder = (ImgBeanHolder) msg.obj;
ImageView imageView = holder.imageView;
Bitmap bm = holder.bitmap;
String path = holder.path;
if (imageView.getTag().toString().equals(path)) {
imageView.setImageBitmap(bm);
}
}
};
}
Bitmap bm = getBitmapFromLruCache(path);
if (bm != null) {
ImgBeanHolder holder = new ImgBeanHolder();
holder.bitmap = bm;
holder.imageView = imageView;
holder.path = path;
Message message = Message.obtain();
message.obj = holder;
mHandler.sendMessage(message);
} else {
addTask(new Runnable() {
@Override
public void run() {
ImageSize imageSize = getImageViewWidth(imageView);
int reqWidth = imageSize.width;
int reqHeight = imageSize.height;
Bitmap bm = decodeSampledBitmapFromResource(path, reqWidth,
reqHeight);
addBitmapToLruCache(path, bm);
ImgBeanHolder holder = new ImgBeanHolder();
holder.bitmap = getBitmapFromLruCache(path);
holder.imageView = imageView;
holder.path = path;
Message message = Message.obtain();
message.obj = holder;
// Log.e("TAG", "mHandler.sendMessage(message);");
mHandler.sendMessage(message);
mPoolSemaphore.release();
}
});
}
}
/**
* 添加一個任務
* 同步添加任務
* @param runnable
*/
private synchronized void addTask(Runnable runnable) {
try {
// 請求信号量,防止mPoolThreadHander為null
if (mPoolThreadHander == null)
mSemaphore.acquire();
} catch (InterruptedException e) {
}
mTasks.add(runnable);
mPoolThreadHander.sendEmptyMessage(0x110);
}
/**
* 取出一個任務
* 同步擷取任務
* @return
*/
private synchronized Runnable getTask() {
if (mType == Type.FIFO) {
return mTasks.removeFirst();
} else if (mType == Type.LIFO) {
return mTasks.removeLast();
}
return null;
}
/**
* 單例獲得該執行個體對象
*
* @return
*/
public static ImageLoader getInstance(int threadCount, Type type) {
if (mInstance == null) {
synchronized (ImageLoader.class) {
if (mInstance == null) {
mInstance = new ImageLoader(threadCount, type);
}
}
}
return mInstance;
}
/**
* 根據ImageView獲得适當的壓縮的寬和高
*
* @param imageView
* @return
*/
private ImageSize getImageViewWidth(ImageView imageView) {
ImageSize imageSize = new ImageSize();
final DisplayMetrics displayMetrics = imageView.getContext()
.getResources().getDisplayMetrics();
final LayoutParams params = imageView.getLayoutParams();
int width = params.width == LayoutParams.WRAP_CONTENT ? 0 : imageView
.getWidth(); // Get actual image width
if (width <= 0)
width = params.width; // Get layout width parameter
if (width <= 0)
width = getImageViewFieldValue(imageView, "mMaxWidth"); // Check
// maxWidth
// parameter
if (width <= 0)
width = displayMetrics.widthPixels;
int height = params.height == LayoutParams.WRAP_CONTENT ? 0 : imageView
.getHeight(); // Get actual image height
if (height <= 0)
height = params.height; // Get layout height parameter
if (height <= 0)
height = getImageViewFieldValue(imageView, "mMaxHeight"); // Check
// maxHeight
// parameter
if (height <= 0)
height = displayMetrics.heightPixels;
imageSize.width = width;
imageSize.height = height;
return imageSize;
}
/**
* 從LruCache中擷取一張圖檔,如果不存在就傳回null。
*/
private Bitmap getBitmapFromLruCache(String key) {
return mLruCache.get(key);
}
/**
* 往LruCache中添加一張圖檔
*
* @param key
* @param bitmap
*/
private void addBitmapToLruCache(String key, Bitmap bitmap) {
if (getBitmapFromLruCache(key) == null) {
if (bitmap != null)
mLruCache.put(key, bitmap);
}
}
/**
* 計算inSampleSize,用于壓縮圖檔
*
* @param options
* @param reqWidth
* @param reqHeight
* @return
*/
private int calculateInSampleSize(BitmapFactory.Options options,//???options insamplesize
int reqWidth, int reqHeight) {
// 源圖檔的寬度
int width = options.outWidth;
int height = options.outHeight;
int inSampleSize = 1;
if (width > reqWidth && height > reqHeight) {
// 計算出實際寬度和目标寬度的比率
int widthRatio = Math.round((float) width / (float) reqWidth);
int heightRatio = Math.round((float) width / (float) reqWidth);
inSampleSize = Math.max(widthRatio, heightRatio);
}
return inSampleSize;
}
/**
* 根據計算的inSampleSize,得到壓縮後圖檔
*
* @param pathName
* @param reqWidth
* @param reqHeight
* @return
*/
private Bitmap decodeSampledBitmapFromResource(String pathName,
int reqWidth, int reqHeight) {
// 第一次解析将inJustDecodeBounds設定為true,來擷取圖檔大小
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(pathName, options);
// 調用上面定義的方法計算inSampleSize值
options.inSampleSize = calculateInSampleSize(options, reqWidth,
reqHeight);
// 使用擷取到的inSampleSize值再次解析圖檔
options.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeFile(pathName, options);
return bitmap;
}
private class ImgBeanHolder {
Bitmap bitmap;
ImageView imageView;
String path;
}
private class ImageSize {
int width;
int height;
}
/**
* 反射獲得ImageView設定的最大寬度和高度
*
* @param object
* @param fieldName
* @return
*/
private static int getImageViewFieldValue(Object object, String fieldName) {
int value = 0;
try {
Field field = ImageView.class.getDeclaredField(fieldName);
field.setAccessible(true);
int fieldValue = (Integer) field.get(object);
if (fieldValue > 0 && fieldValue < Integer.MAX_VALUE) {
value = fieldValue;
Log.e("TAG", value + "");
}
} catch (Exception e) {
}
return value;
}
}
知識點:
1.為什麼使用LinkedList、ArrayList的差別:
兩個都是List接口的實作類,ArrayList的本職是資料,在初始化的時候回配置設定一定的大小,當空間不夠用時,會配置設定更大的容量,然後把之前的資料拷貝進去
LinkedList是連結清單。ArrayList和LinkedList本質上的差別就是數組和清單這兩種資料結構的差別。
ArrayList:缺點:記憶體使用量要大一些,添加删除元素效率較低。元素随機通路的效率較高。
LinkedList:相反。
2.緩存圖檔類LruCache
Android用LruCache來取代原來強引用和軟引用實作記憶體緩存,因為據說自2.3以後Android将更頻繁的調用GC,導緻軟引用緩存的資料極易被釋放。LruCache使用一個LinkedHashMap簡單的實作記憶體的緩存,沒有軟引用,都是強引用。如果添加的資料大于設定的最大值,就删除最先緩存的資料來調整記憶體。
3.單例模式
public class ImageLoader{
private static ImageLoader mInstance;//不要忘了static
private ImageLoader(int threadCount, Type type) {
init(threadCount, type);
}
public static ImageLoader getInstance() {//<span style="font-family: Arial, Helvetica, sans-serif;">不要忘了static</span>
if (mInstance == null) {//在synchronized之前判斷可以過濾大部分的沒有必要的同步,提高效率
synchronized (ImageLoader.class) {
if (mInstance == null) {
mInstance = new ImageLoader(1, Type.LIFO);
}
}
}
return mInstance;
}
}
**歡迎大家來找茬**