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

来源:互联网 发布:qq加速升级软件 编辑:程序博客网 时间:2024/06/05 06:02

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
原创粉丝点击