Universal-ImageLoader源码流程浅析之(二)--图片的加载流程

来源:互联网 发布:js 弹出div 编辑:程序博客网 时间:2024/06/05 01:18

前言

在上一篇中,描述了imageloader的配置属性。这里聊一聊实际的加载。

图片配置

当完成配置以后,实际代码中是这么进行图片加载的:
ImageLoader.displayImage(image.URL, imageview, ImageLoaderOption);

配置代码:

private static DisplayImageOptions optionsdelay = new DisplayImageOptions.Builder()            .showImageOnLoading(R.drawable.empty_photo) // resource or drawable            .showImageForEmptyUri(R.drawable.empty_photo) // resource or drawable            .showImageOnFail(R.drawable.empty_photo) // resource or drawable            .resetViewBeforeLoading(false)  // default            .delayBeforeLoading(100)            .cacheInMemory(true)            .cacheOnDisk(true)            .imageScaleType(ImageScaleType.EXACTLY)//是否压缩            .bitmapConfig(Bitmap.Config.RGB_565)//图像像素            .build();

DisplayImageOptions的源码较为简单,这里就不详述了。还是结合display的具体实现,看一看这些参数具体起什么作用吧。

displayImage

/**  * @param uri              Image URI (i.e. "http://site.com/image.png", "file:///mnt/sdcard/image.png")     * @param imageAware       {@linkplain ImageAware Image aware view}     *                         which should display image     * @param options          {@linkplain com.nostra13.universalimageloader.core.DisplayImageOptions Options} for image     *                         decoding and displaying. If <b>null</b> - default display image options     *                         {@linkplain ImageLoaderConfiguration.Builder#defaultDisplayImageOptions(DisplayImageOptions)     *                         from configuration} will be used.     * @param targetSize       {@linkplain ImageSize} Image target size. If <b>null</b> - size will depend on the view     * @param listener         {@linkplain ImageLoadingListener Listener} for image loading process. Listener fires     *                         events on UI thread if this method is called on UI thread.     * @param progressListener {@linkplain ImageLoadingProgressListener     *                         Listener} for image loading progress. Listener fires events on UI thread if this method     *                         is called on UI thread. Caching on disk should be enabled in     *                         {@linkplain com.nostra13.universalimageloader.core.DisplayImageOptions options} to make     *                         this listener work.     * @throws IllegalStateException    if {@link #init(ImageLoaderConfiguration)} method wasn't called before     * @throws IllegalArgumentException if passed <b>imageAware</b> is null     */public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,ImageSize targetSize, ImageLoadingListener listener, ImageLoadingProgressListener progressListener) 

targetsize

先看一下targetsize这个参数的获取

 if (targetSize == null) {            targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize());        }

默认不填写targerSize,targerSize是根据控件大小和配置的imagesize计算而得出的。

然后看一下getMaxImageSize()的获取方式,默认的即是屏幕的大小。

ImageSize getMaxImageSize() {        DisplayMetrics displayMetrics = resources.getDisplayMetrics();        int width = maxImageWidthForMemoryCache;        if (width <= 0) {            width = displayMetrics.widthPixels;        }        int height = maxImageHeightForMemoryCache;        if (height <= 0) {            height = displayMetrics.heightPixels;        }        return new ImageSize(width, height);    }    /* * @param maxImageWidthForMemoryCache  Maximum image width which will be used for memory saving during decoding         *                                     an image to {@link android.graphics.Bitmap Bitmap}. <b>Default value - device's screen width</b>         * @param maxImageHeightForMemoryCache Maximum image height which will be used for memory saving during decoding         *                                     an image to {@link android.graphics.Bitmap Bitmap}. <b>Default value</b> - device's screen height         */

回来再看defineTargetSizeForView的实现:

public static ImageSize defineTargetSizeForView(ImageAware imageAware, ImageSize maxImageSize) {        int width = imageAware.getWidth();        if (width <= 0) width = maxImageSize.getWidth();        int height = imageAware.getHeight();        if (height <= 0) height = maxImageSize.getHeight();        return new ImageSize(width, height);    }

这样我们就明白了targetsize的计算方式,我们接着继续往下看:

ImageLoaderEngine

ImageLoaderEngine(ImageLoaderConfiguration configuration) {        this.configuration = configuration;        taskExecutor = configuration.taskExecutor;        taskExecutorForCachedImages = configuration.taskExecutorForCachedImages;        taskDistributor = DefaultConfigurationFactory.createTaskDistributor();    }

这里说明一个很重要的ImageLoader的全局变量engine.
创建方法是上面的代码。
taskExecutor,taskExecutorForCachedImages可以参看上一篇的配置说明。

public static Executor createTaskDistributor() {        return Executors.newCachedThreadPool(createThreadFactory(Thread.NORM_PRIORITY, "uil-pool-d-"));    }

上文是taskDistributor创建的代码,后续将结合 taskDistributor的具体实现进行说明。
我们回来继续看 ImageLoader 的display流程:

 String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize); engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);

memoryCacheKey根据图片url和图片大小获取一个关键字。

private final Map<Integer, String> cacheKeysForImageAwares = Collections            .synchronizedMap(new HashMap<Integer, String>());/**     * Associates <b>memoryCacheKey</b> with <b>imageAware</b>. Then it helps to define image URI is loaded into View at     * exact moment.     */    void prepareDisplayTaskFor(ImageAware imageAware, String memoryCacheKey) {        cacheKeysForImageAwares.put(imageAware.getId(), memoryCacheKey);    }

接着往下读:

listener

listener.onLoadingStarted(uri, imageAware.getWrappedView());
@param listener {@linkplain ImageLoadingListener Listener} for image loading process. Listener fires
* events on UI thread if this method is called on UI thread.
这里简单看一下默认创建的listener.
public interface ImageLoadingListener 可以监听加载的状态。
默认创建的SimpleImageLoadingListener是一个空的listener,可以根据个人定制。

接着看:
Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);

memorycache中命中数据的流程

当成功从memorycache中查询到数据后,代码如下:

 if (options.shouldPostProcess()) {                ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,                        options, listener, progressListener, engine.getLockForUri(uri));                ProcessAndDisplayImageTask displayTask = new ProcessAndDisplayImageTask(engine, bmp, imageLoadingInfo,                        defineHandler(options));                if (options.isSyncLoading()) {                    displayTask.run();                } else {                    engine.submit(displayTask);                }            } else {                options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE);                listener.onLoadingComplete(uri, imageAware.getWrappedView(), bmp);            }

PostProcess的说明
* Sets bitmap processor which will be process bitmaps before they will be displayed in
* {@link com.nostra13.universalimageloader.core.imageaware.ImageAware image aware view} but
* after they’ll have been saved in memory cache.
*/
可以在图片显示之前,加入PostProcess进行一些操作,可以同步或者异步操作。
当未设置PostProcess,默认情况下,即在imageAware中使用setImageBitmap,设置bitmap完成设置。
options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE);
public final class SimpleBitmapDisplayer implements BitmapDisplayer {
@Override
public void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom) {
imageAware.setImageBitmap(bitmap);
}
}

图片不在内存中流程

 if (options.shouldShowImageOnLoading()) {                imageAware.setImageDrawable(options.getImageOnLoading(configuration.resources));//配置加载等待图,显示加载等待图片。            } else if (options.isResetViewBeforeLoading()) {                imageAware.setImageDrawable(null);            }            ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,                    options, listener, progressListener, engine.getLockForUri(uri));//将相关的信息保存记录            LoadAndDisplayImageTask displayTask = new LoadAndDisplayImageTask(engine, imageLoadingInfo,                    defineHandler(options));                    //关注重点,加载图片任务            if (options.isSyncLoading()) {                displayTask.run();            } else {                engine.submit(displayTask);                }

这里我们主要来看一下LoadAndDisplayImageTask这个任务的具体实现。

private Bitmap tryLoadBitmap() throws TaskCancelledException {        Bitmap bitmap = null;        try {            File imageFile = configuration.diskCache.get(uri);//从diskcache中获取图片            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()));                //从imageFile中获取bitmap.            }            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()) {                //从网络下载图片到disk。后文介绍tryCacheImageOnDisk方法                    imageFile = configuration.diskCache.get(uri);                    if (imageFile != null) {                        imageUriForDecoding = Scheme.FILE.wrap(imageFile.getAbsolutePath());                    }                }                //如果isCacheOnDisk==false,直接从uri decode.                checkTaskNotActual();                bitmap = decodeImage(imageUriForDecoding);                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;    }

上述代码中需要说明的单独说明一下。

decode解码器代码

decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));
decodeImage将文件解码成bitmap

ViewScaleType viewScaleType = imageAware.getScaleType();        ImageDecodingInfo decodingInfo = new ImageDecodingInfo(memoryCacheKey, imageUri, uri, targetSize, viewScaleType,                getDownloader(), options);        return decoder.decode(decodingInfo);

这部分代码就不做说明了,这部分可以参考google官方提供的imageFetcher的解释说明。

public Bitmap decode(ImageDecodingInfo decodingInfo) throws IOException {        Bitmap decodedBitmap;        ImageFileInfo imageInfo;        InputStream imageStream = getImageStream(decodingInfo);        if (imageStream == null) {            L.e(ERROR_NO_IMAGE_STREAM, decodingInfo.getImageKey());            return null;        }        try {            imageInfo = defineImageSizeAndRotation(imageStream, decodingInfo);            imageStream = resetStream(imageStream, decodingInfo);            Options decodingOptions = prepareDecodingOptions(imageInfo.imageSize, decodingInfo);            decodedBitmap = BitmapFactory.decodeStream(imageStream, null, decodingOptions);        } 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);        }        return decodedBitmap;    }

本地缓存网络加载图片函数tryCacheImageOnDisk

/** @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); // TODO : process boolean result                }            }        } catch (IOException e) {            L.e(e);            loaded = false;        }        return loaded;    }    private boolean downloadImage() throws IOException {        InputStream is = getDownloader().getStream(uri, options.getExtraForDownloader());        if (is == null) {            L.e(ERROR_NO_IMAGE_STREAM, memoryCacheKey);            return false;        } else {            try {                return configuration.diskCache.save(uri, is, this);            } finally {                IoUtils.closeSilently(is);            }        }    }

本地保存的图片默认是按照屏幕分辨率进行文件大小裁剪的。

如上就是图片显示的整体流程。

个人的理解

图片处理都是基于android官方提供的image Fetcher进行的处理。universal-imageloader加入了二级缓存机制(内存+disk)。对于decode option参数配置,加载过程中的状态处理等加入了自己的很多理解。走读一遍源码实现,会有不小的收获。

0 0
原创粉丝点击