1. 程式人生 > >Universal-ImageLoader原始碼流程淺析之(二)--圖片的載入流程

Universal-ImageLoader原始碼流程淺析之(二)--圖片的載入流程

前言

在上一篇中,描述了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引數配置,載入過程中的狀態處理等加入了自己的很多理解。走讀一遍原始碼實現,會有不小的收穫。