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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值