編輯:關於Android編程
這次做一個圖片加載器,裡面涉及到線程池,bitmap的高效加載,LruCache,DiskLruCache。接下來我先介紹這四個知識點
優點:
(1)重用線程池中的線程,避免因為線程的創建和銷毀帶來性能上的開銷
(2)有效控制線程池的最大並發數,避免大量線程之間因互相搶占系統資源而阻塞
(3)對線程進行簡單管理,並提供定時執行和指定間隔循環執行等功能
1.ThreadPoolExecutor介紹
是線程池的真正實現,構造方法
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueueworkQueue, 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注釋掉
本文實例為大家分享了PopupWindow實現自定義overflow的具體代碼,供大家參考,具體內容如下當Action Bar的Action放不下時,系統會將其收集在ov
Android中的消息處理機制大量依賴於Handler。每個Handler都有對應的Looper,用於不斷地從對應的MessageQueue中取出消息處理。一直以來,覺得
在手機應用中,用戶點擊回退按鈕一般是返回上個頁面,一般頁面不用處理,如果在首頁,點回退,沒任何提示,就把應用給關了,這個用戶體驗就不太好了,所以一般都會給用戶一個確認的提
github:https://github.com/zarics/ZrcListView先貼一個自己畫的ZrcListView的UML類圖(學習ing。。。)滾動的實現想