1. 怎麼實現大圖片的下載,防止OOM
前面分析volley,我們知道volley並不適合大文件的下載,因爲volley把輸入流都寫入了byte[]內存,然後寫入硬盤緩存,所以容易OOM。
看UIL怎麼實現大圖片的下載的
private Bitmap tryLoadBitmap() throws TaskCancelledException {
Bitmap bitmap = null;
try {
File imageFile = configuration.diskCache.get(uri); //檢查硬盤緩存是否保存有
if (imageFile != null && imageFile.exists() && imageFile.length() > 0) { //有
L.d(LOG_LOAD_IMAGE_FROM_DISK_CACHE, memoryCacheKey);
loadedFrom = LoadedFrom.DISC_CACHE;
checkTaskNotActual();
bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath())); //從文件目錄中解析,最後會調用到BaseImageDownloader的getStreamFromFile方法,從文件中獲取流
}
if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) { //硬盤緩存中沒有,那麼從網絡中加載
L.d(LOG_LOAD_IMAGE_FROM_NETWORK, memoryCacheKey);
loadedFrom = LoadedFrom.NETWORK;
String imageUriForDecoding = uri;
if (options.isCacheOnDisk() && tryCacheImageOnDisk()) { //如果設置了緩存在硬盤緩存,那麼調用tryCacheImageOnDisk方法把圖片緩存在硬盤中
imageFile = configuration.diskCache.get(uri);
if (imageFile != null) {
imageUriForDecoding = Scheme.FILE.wrap(imageFile.getAbsolutePath()); //此時imageUriForDecoding字符串就是file://開頭了
}
}
checkTaskNotActual();
bitmap = decodeImage(imageUriForDecoding); //如果上面圖片緩存在了硬盤file://,那麼通過getStreamFromFile方法,從文件中獲取流。否則http://通過getStreamFromNetwork從網絡獲取輸入流
if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
fireFailEvent(FailType.DECODING_ERROR, null);
}
}
} catch (IllegalStateException e) {
fireFailEvent(FailType.NETWORK_DENIED, null);
} catch (TaskCancelledException e) {
throw e;
} catch (IOException e) {
L.e(e);
fireFailEvent(FailType.IO_ERROR, e);
} catch (OutOfMemoryError e) {
L.e(e);
fireFailEvent(FailType.OUT_OF_MEMORY, e);
} catch (Throwable e) {
L.e(e);
fireFailEvent(FailType.UNKNOWN, e);
}
return bitmap;
}
//解碼圖片,默認解碼器BaseImageDecoder
private Bitmap decodeImage(String imageUri) throws IOException {
ViewScaleType viewScaleType = imageAware.getScaleType();
ImageDecodingInfo decodingInfo = new ImageDecodingInfo(memoryCacheKey, imageUri, uri, targetSize, viewScaleType,
getDownloader(), options);
return decoder.decode(decodingInfo);
}
/** @return <b>true</b> - if image was downloaded successfully; <b>false</b> - otherwise */
private boolean tryCacheImageOnDisk() throws TaskCancelledException {
L.d(LOG_CACHE_IMAGE_ON_DISK, memoryCacheKey);
boolean loaded;
try {
loaded = downloadImage(); //下載圖片,同時把圖片緩存到硬盤緩存,這個方法在《監聽圖片下載進度怎麼做到的》中有解釋
if (loaded) {
int width = configuration.maxImageWidthForDiskCache;
int height = configuration.maxImageHeightForDiskCache;
if (width > 0 || height > 0) {
L.d(LOG_RESIZE_CACHED_IMAGE_FILE, memoryCacheKey);
resizeAndSaveImage(width, height); //根據設置的maxImageWidthForDiskCache,maxImageHeightForDiskCache重新裁剪保存到硬盤緩存,如果沒設置那麼保存的網絡下載的原圖
}
}
} catch (IOException e) {
L.e(e);
loaded = false;
}
return loaded;
}
//BaseImageDecoder的解碼方法
@Override
public Bitmap decode(ImageDecodingInfo decodingInfo) throws IOException {
Bitmap decodedBitmap;
ImageFileInfo imageInfo;
InputStream imageStream = getImageStream(decodingInfo); //不同的來源不同的流,可能是文件流file://,也可能是網絡流http://
if (imageStream == null) {
L.e(ERROR_NO_IMAGE_STREAM, decodingInfo.getImageKey());
return null;
}
try {
imageInfo = defineImageSizeAndRotation(imageStream, decodingInfo); //decodingInfo中把inJustDecodeBounds設置爲true,保存有options.outWidth/outHeight
imageStream = resetStream(imageStream, decodingInfo);
Options decodingOptions = prepareDecodingOptions(imageInfo.imageSize, decodingInfo); //decodingInfo中有imageview的實際寬高,然後和imageInfo中計算inSampleSize縮放值,其實prepareDecodingOptions方法內部來計算了圖片的ScaleType,這個就不解釋了,看源碼
decodedBitmap = BitmapFactory.decodeStream(imageStream, null, decodingOptions); //根據decodingOptions中從流中解析得到最後的bitmap
} finally {
IoUtils.closeSilently(imageStream);
}
if (decodedBitmap == null) {
L.e(ERROR_CANT_DECODE_IMAGE, decodingInfo.getImageKey());
} else {
decodedBitmap = considerExactScaleAndOrientatiton(decodedBitmap, decodingInfo, imageInfo.exif.rotation,
imageInfo.exif.flipHorizontal); //如果圖片有Exif參數,那麼調整該bitmap
}
return decodedBitmap;
}
總結一下:
如果硬盤緩存不存在,那麼先把網絡下載的圖片先通過流直接寫入硬盤緩存,默認保存的是原圖,但是如果設置了configuration.maxImageWidthForDiskCache/maxImageHeightForDiskCache
就會根據這個大小進行縮放重新保存到本地硬盤,保存完之後,然後再次從硬盤緩存中讀取文件輸入流,此時可以計算得到Options.inSampleSize的值,生成bitmap。
此時就沒有把原圖寫入byte[]內存,而是根據需要生成bitmap,有效的防止了OOM
2. listview,gridveiw做了哪些優化
listview等滾動中不加載圖片
listView.setOnScrollListener(new PauseOnScrollListener(ImageLoader.getInstance(), pauseOnScroll, pauseOnFling),onMyScrollListener);
實現了滾動中不加載圖片
//ImageLoaderEngine方法
void pause() {
paused.set(true);
}
void resume() {
paused.set(false);
synchronized (pauseLock) {
pauseLock.notifyAll();
}
}
//LoadAndDisplayImageTask的run方法首先會檢查是否需要被暫停
private boolean waitIfPaused() {
AtomicBoolean pause = engine.getPause();
if (pause.get()) {
synchronized (engine.getPauseLock()) {
if (pause.get()) {
L.d(LOG_WAITING_FOR_RESUME, memoryCacheKey);
try {
engine.getPauseLock().wait();
} catch (InterruptedException e) {
L.e(LOG_TASK_INTERRUPTED, memoryCacheKey);
return true;
}
L.d(LOG_RESUME_AFTER_PAUSE, memoryCacheKey);
}
}
}
return isTaskNotActual();
}
這樣實現了滾動不加載了,滾動的時候如果調用了pause
方法taskExecutor/taskExecutorForCachedImages線程池的線程,調用了wait
方法,所以都被阻塞在鎖池中。只有調用resume
方法,notifyAll
才被喚醒,所以繼續執行run方法,請求bitmap
注意:只是滾動中暫停了新的請求,停止滾動了恢復請求,如果之前被加入的還會繼續做請求,比如上一頁已經不可見的item如果已經添加進去了,還是會去加載。暫停的只是滾動中可見的
防止listview等圖片錯亂閃爍重複問題
不需要手動的對imageview對setTag(url),然後在getTag跟url判斷,因爲內部已經解決了這個問題
我們知道圖片錯亂閃爍重複問題的本質原因是異步加載及view對象被複用造成的,那麼看看UIL是怎麼解決這個問題的
//ImageLoader的displayImage方法中都會把
String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);
engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);
//ImageLoaderEngine中維護一個線程安全的Map(必須是線程安全的,因爲線程池會操作這個map),讓view和memoryCacheKey一一對應
private final Map<Integer, String> cacheKeysForImageAwares = Collections
.synchronizedMap(new HashMap<Integer, String>());
void prepareDisplayTaskFor(ImageAware imageAware, String memoryCacheKey) {
cacheKeysForImageAwares.put(imageAware.getId(), memoryCacheKey);
}
//ViewAware的getId方法,所以只要不是同一個view,那麼getId必定不同的,默認的hashcode實現是根據對象的地址來計算的,所以對象不是同一個,hashcode必然不一樣
@Override
public int getId() {
View view = viewRef.get();
return view == null ? super.hashCode() : view.hashCode();
}
//從內存緩存/硬盤緩存/網絡加載完bitmap之後,都會執行DisplayBitmapTask任務讓bitmap顯示在imageview上,其中會判斷是否
else if (isViewWasReused()) {
L.d(LOG_TASK_CANCELLED_IMAGEAWARE_REUSED, memoryCacheKey);
listener.onLoadingCancelled(imageUri, imageAware.getWrappedView());
}
/** Checks whether memory cache key (image URI) for current ImageAware is actual */
private boolean isViewWasReused() {
String currentCacheKey = engine.getLoadingUriForView(imageAware);
return !memoryCacheKey.equals(currentCacheKey);
}
比如此時滑到14項,但是此時複用了2項的view,那麼14項和2項複用同一個imageview,所以cacheKeysForImageAwares
保存的是這個view的id,還有14項的cachekey(第二項的cachekey被替換掉了),
所以當2項網絡請求完成之後,DisplayBitmapTask中imageLoadingInfo.memoryCacheKey是2項的cachekey,所以isViewWasReused
返回的肯定是false,那麼就不會去設置這個複用的view了,所以14項的view被正確顯示爲了14項的url圖片,就解決了listview等圖片錯亂閃爍重複問題了
3. 防止同一時間點的重複請求
volley中如果添加同一個請求,那麼會先請求第一個,然後再請求之後的,之後的用的肯定是硬盤緩存中的,但是這樣的話就造成了imageview的閃爍,因爲imageview被設置了多次。 UIL框架不會出現這個問題
ImageLoader.getInstance().displayImage(imageUrl, uilImageview, options);
ImageLoader.getInstance().displayImage(imageUrl, uilImageview, options);
打印結果:
D/ImageLoader: Start display image task [http://pic.nipic.com/2007-11-09/200711912453162_2.jpg_600x600]
D/ImageLoader: Start display image task [http://pic.nipic.com/2007-11-09/200711912453162_2.jpg_600x600]
D/ImageLoader: Image already is loading. Waiting… [http://pic.nipic.com/2007-11-09/200711912453162_2.jpg_600x600]
D/ImageLoader: Load image from network [http://pic.nipic.com/2007-11-09/200711912453162_2.jpg_600x600]
D/ImageLoader: Cache image on disk [http://pic.nipic.com/2007-11-09/200711912453162_2.jpg_600x600]
D/ImageLoader: Cache image in memory [http://pic.nipic.com/2007-11-09/200711912453162_2.jpg_600x600]
D/ImageLoader: Display image in ImageAware (loaded from NETWORK) [http://pic.nipic.com/2007-11-09/200711912453162_2.jpg_600x600]
D/ImageLoader: …Get cached bitmap from memory after waiting. [http://pic.nipic.com/2007-11-09/200711912453162_2.jpg_600x600]
D/ImageLoader: ImageAware is reused for another image. Task is cancelled. [http://pic.nipic.com/2007-11-09/200711912453162_2.jpg_600x600]
只會加載一個
原理:同一個時間uri相同,那麼使用的就是同一個ReentrantLock
//ImageLoaderEngine中
private final Map<String, ReentrantLock> uriLocks = new WeakHashMap<String, ReentrantLock>();
ReentrantLock getLockForUri(String uri) {
ReentrantLock lock = uriLocks.get(uri);
if (lock == null) {
lock = new ReentrantLock();
uriLocks.put(uri, lock);
}
return lock;
}
ReentrantLock loadFromUriLock = imageLoadingInfo.loadFromUriLock;
L.d(LOG_START_DISPLAY_IMAGE_TASK, memoryCacheKey);
if (loadFromUriLock.isLocked()) { //如果被鎖定了,那麼阻塞直到鎖被釋放
L.d(LOG_WAITING_FOR_IMAGE_LOADED, memoryCacheKey);
}
loadFromUriLock.lock(); //加鎖
try {
......
bmp = configuration.memoryCache.get(memoryCacheKey);
if (bmp == null || bmp.isRecycled()) {
bmp = tryLoadBitmap();
if (bmp == null) return; // listener callback already was fired
checkTaskNotActual();
checkTaskInterrupted();
if (options.shouldPreProcess()) {
L.d(LOG_PREPROCESS_IMAGE, memoryCacheKey);
bmp = options.getPreProcessor().process(bmp);
if (bmp == null) {
L.e(ERROR_PRE_PROCESSOR_NULL, memoryCacheKey);
}
}
if (bmp != null && options.isCacheInMemory()) {
L.d(LOG_CACHE_IMAGE_IN_MEMORY, memoryCacheKey);
configuration.memoryCache.put(memoryCacheKey, bmp); //加入內存緩存
}
} else {
loadedFrom = LoadedFrom.MEMORY_CACHE;
L.d(LOG_GET_IMAGE_FROM_MEMORY_CACHE_AFTER_WAITING, memoryCacheKey);
}
......
} catch (TaskCancelledException e) {
fireCancelEvent();
return;
} finally {
loadFromUriLock.unlock(); //釋放鎖
}
DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);
runTask(displayBitmapTask, syncLoading, handler, engine);
//DisplayBitmapTask的run方法
@Override
public void run() {
if (imageAware.isCollected()) {
L.d(LOG_TASK_CANCELLED_IMAGEAWARE_COLLECTED, memoryCacheKey);
listener.onLoadingCancelled(imageUri, imageAware.getWrappedView());
} else if (isViewWasReused()) {
L.d(LOG_TASK_CANCELLED_IMAGEAWARE_REUSED, memoryCacheKey);
listener.onLoadingCancelled(imageUri, imageAware.getWrappedView());
} else {
L.d(LOG_DISPLAY_IMAGE_IN_IMAGEAWARE, loadedFrom, memoryCacheKey);
displayer.display(bitmap, imageAware, loadedFrom);
//cacheKeysForImageAwares中移除了該imageAware,所以下一個請求的String currentCacheKey = engine.getLoadingUriForView(imageAware);爲null,所以isViewWasReused爲true
engine.cancelDisplayTaskFor(imageAware);
listener.onLoadingComplete(imageUri, imageAware.getWrappedView(), bitmap);
}
}
第二個displayimage的時候,因爲使用的是同一個ReentrantLock,同時還沒被釋放,所以被阻塞在這裏了,loadFromUriLock.unlock()
之後纔可以執行,然後直接從內存緩存中讀取
,但是此時的話DisplayBitmapTask中isViewWasReused
就是true了,所以不會重新設置imageview,有效的防止重複請求閃爍問題。 但如果不是同一個view就不一樣了,必須view和url同時一樣。
什麼時候會重新顯示呢? 那必須是DisplayBitmapTask執行完後再執行顯示同一個view,url就會重新顯示了
線程同步方案可以使用ReentrantLock替代synchronized,最新JDK建議這麼做,effect java中也這樣建議,線程同步問題提供了另一種方案
4. 內部圖片下載使用了httpclient還是HttpURLConnection?
默認使用BaseImageDownloader圖片下載器,可以看到內部是使用HttpURLConnection來實現的,並沒有向volley一樣根據api版本來分別實現
//BaseImageDownloader中 case HTTP: case HTTPS:
protected InputStream getStreamFromNetwork(String imageUri, Object extra) throws IOException {
HttpURLConnection conn = createConnection(imageUri, extra);
int redirectCount = 0;
while (conn.getResponseCode() / 100 == 3 && redirectCount < MAX_REDIRECT_COUNT) {
conn = createConnection(conn.getHeaderField("Location"), extra);
redirectCount++;
}
InputStream imageStream;
try {
imageStream = conn.getInputStream();
} catch (IOException e) {
// Read all data to allow reuse connection (http://bit.ly/1ad35PY)
IoUtils.readAndCloseStream(conn.getErrorStream());
throw e;
}
if (!shouldBeProcessed(conn)) {
IoUtils.closeSilently(imageStream);
throw new IOException("Image request failed with response code " + conn.getResponseCode());
}
return new ContentLengthInputStream(new BufferedInputStream(imageStream, BUFFER_SIZE), conn.getContentLength());
}
protected HttpURLConnection createConnection(String url, Object extra) throws IOException {
String encodedUrl = Uri.encode(url, ALLOWED_URI_CHARS);
HttpURLConnection conn = (HttpURLConnection) new URL(encodedUrl).openConnection();
conn.setConnectTimeout(connectTimeout);
conn.setReadTimeout(readTimeout);
return conn;
}
BaseImageDownloader(Context context, int connectTimeout, int readTimeout)
設置連接超時時間和讀超時時間
5. 監聽圖片下載進度怎麼做到的
怎麼使用
ImageLoader.getInstance().displayImage(imageUrl, uilImageview, options, new SimpleImageLoadingListener() {
@Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
}
}, new ImageLoadingProgressListener() {
@Override
public void onProgressUpdate(String imageUri, View view, int current, int total) {
Log.d("ImageLoader", "current:" + current + " total:" + total);
}
});
實現原理
只有在請求網絡成功然後寫入硬盤緩存的時候纔會調用onProgressUpdate,原理簡單來說就是把網絡得到的InputStream不斷的寫入文件OutputStream,這個過程中監聽每次寫入的字節數,從而回調onProgressUpdate
方法,所以如果如果圖片從內存/硬盤緩存讀取或者壓根不寫入硬盤緩存cacheOnDisk(false)
,那麼是不會回調onProgressUpdate
源碼
//LoadAndDisplayImageTask的downloadImage方法表示就是從網路下載圖片了
private boolean downloadImage() throws IOException {
InputStream is = getDownloader().getStream(uri, options.getExtraForDownloader()); //發起網絡請求,返回圖片的InputStream
if (is == null) {
L.e(ERROR_NO_IMAGE_STREAM, memoryCacheKey);
return false;
} else {
try {
return configuration.diskCache.save(uri, is, this); //根據設置的硬盤緩存策略保存到硬盤,默認是LruDiskCache
} finally {
IoUtils.closeSilently(is);
}
}
}
//LruDiskCache的save方法
@Override
public boolean save(String imageUri, InputStream imageStream, IoUtils.CopyListener listener) throws IOException {
DiskLruCache.Editor editor = cache.edit(getKey(imageUri));
if (editor == null) {
return false;
}
OutputStream os = new BufferedOutputStream(editor.newOutputStream(0), bufferSize); //buffer輸出流,輸出到文件
boolean copied = false;
try {
copied = IoUtils.copyStream(imageStream, os, listener, bufferSize); //實際實現輸入流到輸出流的轉換
} finally {
IoUtils.closeSilently(os);
if (copied) {
editor.commit();
} else {
editor.abort();
}
}
return copied;
}
//IoUtils.copyStream方法
public static boolean copyStream(InputStream is, OutputStream os, CopyListener listener, int bufferSize)
throws IOException {
int current = 0;
int total = is.available(); //獲取InputStream的總大小
if (total <= 0) {
total = DEFAULT_IMAGE_TOTAL_SIZE;
}
final byte[] bytes = new byte[bufferSize];
int count;
if (shouldStopLoading(listener, current, total)) return false;
while ((count = is.read(bytes, 0, bufferSize)) != -1) {
os.write(bytes, 0, count);
current += count; //目前讀取了的字節數
if (shouldStopLoading(listener, current, total)) return false;
}
os.flush();
return true;
}
//shouldStopLoading方法用來判讀是否停止,可以看到這裏的策略,如果比如調用了imageloader.stop(),imageview被gc回收了,此時onBytesCopied就返回true,但是如果下載進度大於75%,那就不停止寫到硬盤緩存,否則停止寫入硬盤緩存,這樣是合理的。
private static boolean shouldStopLoading(CopyListener listener, int current, int total) {
if (listener != null) {
boolean shouldContinue = listener.onBytesCopied(current, total); //回調LoadAndDisplayImageTask的onBytesCopied方法
if (!shouldContinue) {
if (100 * current / total < CONTINUE_LOADING_PERCENTAGE) {
return true; // if loaded more than 75% then continue loading anyway
}
}
}
return false;
}
//LoadAndDisplayImageTask方法
@Override
public boolean onBytesCopied(int current, int total) {
return syncLoading || fireProgressEvent(current, total);
}
/** @return <b>true</b> - if loading should be continued; <b>false</b> - if loading should be interrupted */
private boolean fireProgressEvent(final int current, final int total) {
if (isTaskInterrupted() || isTaskNotActual()) return false; //判斷是否需要停止
if (progressListener != null) {
Runnable r = new Runnable() {
@Override
public void run() {
progressListener.onProgressUpdate(uri, imageAware.getWrappedView(), current, total); //回調progressListener的onProgressUpdate方法
}
};
runTask(r, false, handler, engine);
}
return true;
}
6. 線程池管理
ImageLoaderEngine
實現線程池
線程池,默認3個線程池,其中兩個線程池默認3個固定線程,另一個是newCachedThreadPool線程池負責分發
private Executor taskExecutor;
private Executor taskExecutorForCachedImages;
//newFixedThreadPool類型 線程池中默認3個線程
private Executor taskDistributor;
//newCachedThreadPool類型 excute一次runnable就開啓一個線程,60s後該線程自動結束
taskDistributor用來分發的,如果硬盤緩存中有那麼交給taskExecutorForCachedImages線程池,否則交給taskExecutor線程池去做網路請求
線程池阻塞隊列類型FIFO, LIFO
默認FIFO,先進先出,就是說先發起請求的先處理,ImageLoaderConfiguration.tasksProcessingOrder()方法可以改變隊列類型
BlockingQueue<Runnable> taskQueue = lifo ? new LIFOLinkedBlockingDeque<Runnable>() : new LinkedBlockingQueue<Runnable>();
7. 圖片大小,圖片scaletype的使用
硬盤緩存,緩存的是原圖,除非設置了configuration.maxImageWidthForDiskCache/maxImageHeightForDiskCache,硬盤緩存的文件名,是通過FileNameGenerator根據imageUri生成的key,默認使用該uri的hashcode作爲key
內存緩存,如果設置了denyCacheImageMultipleSizesInMemory,String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize)
,那麼無視了targetSize,只根據uri保存一次了。否則默認情況會就算uri一樣,但是targetSize不一樣的話,還是會內存緩存多次的
protected Options prepareDecodingOptions(ImageSize imageSize, ImageDecodingInfo decodingInfo) {
ImageScaleType scaleType = decodingInfo.getImageScaleType();
int scale;
if (scaleType == ImageScaleType.NONE) {
scale = 1;
} else if (scaleType == ImageScaleType.NONE_SAFE) {
scale = ImageSizeUtils.computeMinImageSampleSize(imageSize);
} else {
ImageSize targetSize = decodingInfo.getTargetSize();
boolean powerOf2 = scaleType == ImageScaleType.IN_SAMPLE_POWER_OF_2; //默認是IN_SAMPLE_POWER_OF_2,壓縮選項是2的倍數
scale = ImageSizeUtils.computeImageSampleSize(imageSize, targetSize, decodingInfo.getViewScaleType(), powerOf2);
}
if (scale > 1 && loggingEnabled) {
L.d(LOG_SUBSAMPLE_IMAGE, imageSize, imageSize.scaleDown(scale), scale, decodingInfo.getImageKey());
}
Options decodingOptions = decodingInfo.getDecodingOptions();
decodingOptions.inSampleSize = scale;
return decodingOptions;
}
/**
* srcSize(100x100), targetSize(10x10), powerOf2Scale = true -> sampleSize = 8
* srcSize(100x100), targetSize(10x10), powerOf2Scale = false -> sampleSize = 10
*
* srcSize(100x100), targetSize(20x40), viewScaleType = FIT_INSIDE -> sampleSize = 5
* srcSize(100x100), targetSize(20x40), viewScaleType = CROP -> sampleSize = 2
*/
public static int computeImageSampleSize(ImageSize srcSize, ImageSize targetSize, ViewScaleType viewScaleType,
boolean powerOf2Scale) {
final int srcWidth = srcSize.getWidth();
final int srcHeight = srcSize.getHeight();
final int targetWidth = targetSize.getWidth();
final int targetHeight = targetSize.getHeight();
int scale = 1;
switch (viewScaleType) {
case FIT_INSIDE: //默認,因爲imageveiw的mScaleType默認是FIT_XY, 所以此時被轉換爲FIT_INSIDE
if (powerOf2Scale) {
final int halfWidth = srcWidth / 2;
final int halfHeight = srcHeight / 2;
while ((halfWidth / scale) > targetWidth || (halfHeight / scale) > targetHeight) { // ||運算符
scale *= 2;
}
} else {
scale = Math.max(srcWidth / targetWidth, srcHeight / targetHeight); // max取大
}
break;
case CROP:
if (powerOf2Scale) {
final int halfWidth = srcWidth / 2;
final int halfHeight = srcHeight / 2;
while ((halfWidth / scale) > targetWidth && (halfHeight / scale) > targetHeight) { // &&運算符
scale *= 2;
}
} else {
scale = Math.min(srcWidth / targetWidth, srcHeight / targetHeight); // min取小
}
break;
}
if (scale < 1) { //不對原圖進行放大處理(豈不是更容易OOM了),只有壓縮,inSampleSize>1 壓縮
scale = 1;
}
scale = considerMaxTextureSize(srcWidth, srcHeight, scale, powerOf2Scale);
return scale;
}
設置爲wrap_content,默認就是屏幕寬高,就是上面代碼的targetSize=1080x1776
Start display image task [http://pic.nipic.com/2007-11-09/200711912453162_2.jpg_1080x1776]
舉個典型的例子
<ImageView
android:layout_gravity="center_horizontal"
android:id="@+id/uil_imageview"
android:background="#ffcc0000"
android:layout_width="150dp"
android:layout_height="200dp"/>
原圖的大小爲srcSize:200*200 targetSize:450*600 所以scale=1不進行縮放,但是ImageView的scaleType的默認是FIX_XY,所以最後的顯示還是會把寬放大到450,高放大到450,填不滿高。居中對齊
<ImageView
android:layout_gravity="center_horizontal"
android:id="@+id/uil_imageview"
android:background="#ffcc0000"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
原圖的大小爲srcSize:3216*2028 targetSize:1080*1776 最後scale爲2,圖片就會壓縮兩倍,生成bitmap,然後再根據imageview的默認是FIX_XY進行顯示
總結: 只會對原圖進行壓縮不會放大,達到有效防止OOM的目的
8. 怎麼處理低速網絡的情況
問題描述:
http://code.google.com/p/android/issues/detail?id=6066
調用ImageLoader的handleSlowNetwork()方法,那麼http/https請求就會進入SlowNetworkImageDownloader的getStream方法對imageStream進行進一步處理,return new FlushedInputStream(imageStream);
具體爲什麼這麼做,暫時也不是很清楚
9. 缺點
一定程度上沒有遵從http協議,參考volley實現,硬盤緩存沒有設置過期時間,volley中硬盤緩存的過期時間是根據響應頭中的字段來設置的。但是UIL如果沒有清空內存緩存/硬盤緩存,那麼請求的圖片一定不是最新的,除非displayImage的時候把DisplayImageOptions設置爲cacheInMemory(false),cacheOnDisk(false),或者不設置這兩個(默認不使用緩存),這樣使用的纔是網絡上最新的圖片
清空緩存:
ImageLoader.getInstance().clearMemoryCache();
ImageLoader.getInstance().clearDiskCache();