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

    xiaoxiao2021-03-26  25

    前言

    在上一篇中,描述了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参数配置,加载过程中的状态处理等加入了自己的很多理解。走读一遍源码实现,会有不小的收获。

    转载请注明原文地址: https://ju.6miu.com/read-660647.html

    最新回复(0)