編輯:關於Android編程
這次做一個圖片加載器,裡面涉及到線程池,bitmap的高效加載,LruCache,DiskLruCache。接下來我先介紹這四個知識點
優點:
(1)重用線程池中的線程,避免因為線程的創建和銷毀帶來性能上的開銷
(2)有效控制線程池的最大並發數,避免大量線程之間因互相搶占系統資源而阻塞
(3)對線程進行簡單管理,並提供定時執行和指定間隔循環執行等功能
1.ThreadPoolExecutor介紹
是線程池的真正實現,構造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory)
corePoolSize:核心線程數
maximumPoolSize:最大線程數。超過將阻塞
keepAliveTime:非核心線程超時時長,超過將會被回收
unit:指定keepAliveTime的時間單位。常用TimeUnit.SECONDS(秒),TimeUnit.MINUTES(分鐘)等
workQueue:存儲線程的隊列。通過ThreadPoolExecutor.execute方法提交的Runnable對象會存儲在這個線程中
threadFactory:是一個接口,提供創建新線程的功能。只有一個方法:public Thread newThread(Runnable r)
2.ThreadPoolExecutor典型配置
/**
* 線程池,用來管理線程
*/
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
// AtomicInteger,一個提供原子操作的Integer的類。
private final AtomicInteger mCount = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "ImageLoader#" + mCount.getAndIncrement());
}
};
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
private static final int MAX_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final long KEEP_ALIVE = 10L;
public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAX_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS,
new LinkedBlockingDeque(), sThreadFactory);
通過BitmapFactory.Options來加載所需尺寸的圖片,主要是用到了inSampleSize參數,即采樣率
流程如下:
public Bitmap decodeSampleBitmapFromResource(Resources resources,
int resId, int reqWidth, int reqHeight) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
// 獲取options
BitmapFactory.decodeResource(resources, resId, options);
// 結合目標view所需大小計算采樣率
options.inSampleSize = calculateInSampleSize(options, reqWidth,
reqHeight);
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(resources, resId, options);
}
/**
* 指定輸出圖片的縮放比例
*
* @param options
* @param reqWidth
* @param reqHeight
* @return
*/
public static int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth, int reqHeight) {
// 獲得原始圖片的寬高
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
int inSimpleSize = 1;
if (imageHeight > reqHeight || imageWidth > reqWidth) {
int halfHeight = imageHeight / 2;
int halfWidth = imageWidth / 2;
while ((halfHeight / inSimpleSize) >= reqHeight
&& (halfWidth / inSimpleSize) >= reqWidth) {
inSimpleSize *= 2;
Log.e("SimpleSize", inSimpleSize + "");
}
}
Log.e("inSimpleSize", inSimpleSize + "");
return inSimpleSize;
}
Lru就是Least Recently Used近期最少使用算法。核心思想:當緩存滿時,優先淘汰近期最少使用的緩存對象
先看源碼:(推薦使用supprt-v4包中的LruCache,地址在E:\adt\sdk\sources\android-19\android\support\v4\util)
public class LruCache{ //map用來存儲外界的緩存對象 private final LinkedHashMap map; /** * @param maxSize for caches that do not override {@link #sizeOf}, this is * the maximum number of entries in the cache. For all other caches, * this is the maximum sum of the sizes of the entries in this cache. */ public LruCache(int maxSize) { if (maxSize <= 0) { throw new IllegalArgumentException("maxSize <= 0"); } this.maxSize = maxSize; this.map = new LinkedHashMap (0, 0.75f, true); } //獲取一個緩存對象 public final V get(K key) { if (key == null) { throw new NullPointerException("key == null"); } V mapValue; synchronized (this) { mapValue = map.get(key); if (mapValue != null) { hitCount++; return mapValue; } missCount++; } /* * Attempt to create a value. This may take a long time, and the map * may be different when create() returns. If a conflicting value was * added to the map while create() was working, we leave that value in * the map and release the created value. */ V createdValue = create(key); if (createdValue == null) { return null; } synchronized (this) { createCount++; mapValue = map.put(key, createdValue); if (mapValue != null) { // There was a conflict so undo that last put map.put(key, mapValue); } else { size += safeSizeOf(key, createdValue); } } if (mapValue != null) { entryRemoved(false, key, createdValue, mapValue); return mapValue; } else { trimToSize(maxSize); return createdValue; } } //添加一個緩存對象 public final V put(K key, V value) { if (key == null || value == null) { throw new NullPointerException("key == null || value == null"); } V previous; synchronized (this) { putCount++; size += safeSizeOf(key, value); previous = map.put(key, value); if (previous != null) { size -= safeSizeOf(key, previous); } } if (previous != null) { entryRemoved(false, key, previous, value); } trimToSize(maxSize); return previous; } /** * Remove the eldest entries until the total of remaining entries is at or * below the requested size. * * @param maxSize the maximum size of the cache before returning. May be -1 * to evict even 0-sized elements. */ public void trimToSize(int maxSize) { while (true) { K key; V value; synchronized (this) { if (size < 0 || (map.isEmpty() && size != 0)) { throw new IllegalStateException(getClass().getName() + ".sizeOf() is reporting inconsistent results!"); } if (size <= maxSize || map.isEmpty()) { break; } Map.Entry toEvict = map.entrySet().iterator().next(); key = toEvict.getKey(); value = toEvict.getValue(); map.remove(key); size -= safeSizeOf(key, value); evictionCount++; } entryRemoved(true, key, value, null); } } //移除一個緩存對象,並且減少該對象對應的size public final V remove(K key) { if (key == null) { throw new NullPointerException("key == null"); } V previous; synchronized (this) { previous = map.remove(key); if (previous != null) { size -= safeSizeOf(key, previous); } } if (previous != null) { entryRemoved(false, key, previous, null); } return previous; } //生成一個null對象 protected V create(K key) { return null; } //返回一個緩存對象的副本 public synchronized final Map snapshot() { return new LinkedHashMap (map); } }
研究完了源碼,使用起來就方便了
初始化:
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getByteCount() / 1024;
}
};
獲取緩存:mMemoryCache.get(key)
添加緩存:mMemoryCache.put(key, bitmap)
書上說地址在:https://android.googlesource.com/platform/libcore/+/android-4.1.1_r1/luni/src/main/java/libcore/io/DiskLruCache.java
但是下載下來好像要改好多東西,所以我就在Universal-ImageLoader裡面找了相同的文件
1.DiskLruCache的創建
public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
directory:存儲路徑
appVersion:通常為1
valueCount:單個節點對應數據個數,通常為1
maxSize:緩存總大小,比如50MB
// 初始化DiskLruCache
File diskCacheDir = getDiskCacheDir(mContext, "bitmap");
if (!diskCacheDir.exists()) {
diskCacheDir.mkdirs();
}
if (getUsableSpace(diskCacheDir) > DISK_CACHE_SIZE) {
try {
mDiskLruCache = DiskLruCache.open(diskCacheDir, 1, 1,
DISK_CACHE_SIZE);
} catch (IOException e) {
e.printStackTrace();
}
}
getDiskCacheDir:獲取磁盤緩存目錄
getUsableSpace:獲取sd卡的大小和剩余空間
這兩個函數的實現方法在代碼包裡面有,就不細說
2.DiskLruCache緩存的添加
緩存的添加是通過Editor來完成的。Editor表示緩存對象的編輯對象
//將uri轉化為key
String key = hashKeyForURI(uri);
try {
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if (editor != null) {
OutputStream outputStream = editor
.newOutputStream(DISK_CACHE_INDEX);
if (downloadURIToStream(uri, outputStream)) {
editor.commit();
} else {
editor.abort();
}
mDiskLruCache.flush();
}
} catch (IOException e) {
return null;
}
上面的代碼就是將圖片寫入文件系統,接下來就可以從文件系統中獲取圖片
解釋幾點
1.為什麼要將uri轉化為hashKey?如果uri中含有特殊字符會影響uri的使用 2.downloadURIToStream實現了“把圖片寫入到文件系統”的功能,確切的來說,還要配合editor.commit
3.DiskLruCache緩存的查找
String key = hashKeyForURI(uri);
try {
DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
if (snapshot != null) {
FileInputStream fileInputStream = (FileInputStream) snapshot
.getInputStream(DISK_CACHE_INDEX);
FileDescriptor fileDescriptor = fileInputStream.getFD();
bitmap = mImageResizer.decodeSampleBitmapFromFileDescriptor(
fileDescriptor, width, height);
if (bitmap != null) {
addBitmapToMemoryCache(key, bitmap);
}
}
} catch (IOException e) {
e.printStackTrace();
}
注意,這裡的Snapshot和LruCache的Snapshot不一樣。LruCache的Snapshot表示內存緩存的副本,這裡的Snapshot僅僅指保存了三個參數的一個對象
至此,ImageLoader已經大體實現。
代碼包裡面SquareImageView.java是為了得到一個寬高相同的ImageView。
同時,為了優化列表的卡頓現象,我們采用了“僅當列表靜止時才加載圖片”的策略
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (scrollState == OnScrollListener.SCROLL_STATE_IDLE) {
mIsGridViewIdle = true;
adapter.notifyDataSetChanged();
} else {
mIsGridViewIdle = false;
}
}
在getView裡面添加如下代碼
if (mIsGridViewIdle) {
imageLoader.bindBitmap(uri, imageView);
}
運行截圖
loadBitmapFromHttp和downloadBitmapFromURI都可以實現網絡加載。前者是先放到disk中,然後獲取,後者是先獲取,然後放到memorycache中
我先把downloadBitmapFromURI注釋掉

然後把loadBitmapFromHttp注釋掉

Android用PopupWindow實現自定義overflow
本文實例為大家分享了PopupWindow實現自定義overflow的具體代碼,供大家參考,具體內容如下當Action Bar的Action放不下時,系統會將其收集在ov
Android7.0 MessageQueue
Android中的消息處理機制大量依賴於Handler。每個Handler都有對應的Looper,用於不斷地從對應的MessageQueue中取出消息處理。一直以來,覺得
android開發步步為營之22:處理Activity中的back按鈕事件
在手機應用中,用戶點擊回退按鈕一般是返回上個頁面,一般頁面不用處理,如果在首頁,點回退,沒任何提示,就把應用給關了,這個用戶體驗就不太好了,所以一般都會給用戶一個確認的提
ListView與Adapter筆記:ZrcListView
github:https://github.com/zarics/ZrcListView先貼一個自己畫的ZrcListView的UML類圖(學習ing。。。)滾動的實現想