Universal-Image-Loader系列2-源碼分析

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();

發佈了78 篇原創文章 · 獲贊 9 · 訪問量 14萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章