zoukankan      html  css  js  c++  java
  • Android-Universal-Image-Loader完全解析---加载流程

    我们拿最常用的函数来分析:

    总体概况包含三个过程:保存图片,加载图片,显示图片。

    displayImage(String uri, ImageView imageView)
    

    所有的displayImage函数都是重写的下面的函数(参数最多)

    public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
    			ImageSize targetSize, ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
    		checkConfiguration();
    		if (imageAware == null) {
    			throw new IllegalArgumentException(ERROR_WRONG_ARGUMENTS);
    		}
    		if (listener == null) {
    			listener = defaultListener;
    		}
    		if (options == null) {
    			options = configuration.defaultDisplayImageOptions;
    		}
    
    		if (TextUtils.isEmpty(uri)) {
    			engine.cancelDisplayTaskFor(imageAware);
    			listener.onLoadingStarted(uri, imageAware.getWrappedView());
    			if (options.shouldShowImageForEmptyUri()) {
    				imageAware.setImageDrawable(options.getImageForEmptyUri(configuration.resources));
    			} else {
    				imageAware.setImageDrawable(null);
    			}
    			listener.onLoadingComplete(uri, imageAware.getWrappedView(), null);
    			return;
    		}
    
    		if (targetSize == null) {
    			targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize());
    		}
    		String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);
    		engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);
    
    		listener.onLoadingStarted(uri, imageAware.getWrappedView());
    
    		Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);
    		if (bmp != null && !bmp.isRecycled()) {
    			L.d(LOG_LOAD_IMAGE_FROM_MEMORY_CACHE, memoryCacheKey);
    
    			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);
    			}
    		} else {
    			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);
    			}
    		}
    	}
    

    一.分析入参

    * @param uri              Image URI (i.e. "http://site.com/image.png", "file:///mnt/sdcard/image.png")
    * @param imageAware {@linkplain com.nostra13.universalimageloader.core.imageaware.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 com.nostra13.universalimageloader.core.listener.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.

    uri:Image路径,支持网络路径,本地路径,drawable路径等

    imageAware:Bitmap显示的容器,这个类是个接口,扩展性极好,凡是实现接口的容器ImageLoader都可以支持,不只局限于ImageVIew

    public interface ImageAware {
    	
    	int getWidth();
    
    	
    	int getHeight();
    
    	ViewScaleType getScaleType();
    
    	
    	View getWrappedView();
    
    	boolean isCollected();
    
    
    	int getId();
    
    	boolean setImageDrawable(Drawable drawable);
    
    	boolean setImageBitmap(Bitmap bitmap);
    }
    

      displayImage(String uri, ImageView imageView)参数ImageView是如何转化成ImageAware的?

    public void displayImage(String uri, ImageView imageView) {
    		displayImage(uri, new ImageViewAware(imageView), null, null, null);
    }
    

      看下ImageViewAware的实现

    public class ImageViewAware extends ViewAware {
    
    	/**
    	 * Constructor. <br />
    	 * References {@link #ImageViewAware(android.widget.ImageView, boolean) ImageViewAware(imageView, true)}.
    	 *
    	 * @param imageView {@link android.widget.ImageView ImageView} to work with
    	 */
    	public ImageViewAware(ImageView imageView) {
    		super(imageView);
    	}
    
    	/**
    	 * Constructor
    	 *
    	 * @param imageView           {@link android.widget.ImageView ImageView} to work with
    	 * @param checkActualViewSize <b>true</b> - then {@link #getWidth()} and {@link #getHeight()} will check actual
    	 *                            size of ImageView. It can cause known issues like
    	 *                            <a href="https://github.com/nostra13/Android-Universal-Image-Loader/issues/376">this</a>.
    	 *                            But it helps to save memory because memory cache keeps bitmaps of actual (less in
    	 *                            general) size.
    	 *                            <p/>
    	 *                            <b>false</b> - then {@link #getWidth()} and {@link #getHeight()} will <b>NOT</b>
    	 *                            consider actual size of ImageView, just layout parameters. <br /> If you set 'false'
    	 *                            it's recommended 'android:layout_width' and 'android:layout_height' (or
    	 *                            'android:maxWidth' and 'android:maxHeight') are set with concrete values. It helps to
    	 *                            save memory.
    	 *                            <p/>
    	 */
    	public ImageViewAware(ImageView imageView, boolean checkActualViewSize) {
    		super(imageView, checkActualViewSize);
    	}
    
    	/**
    	 * {@inheritDoc}
    	 * <br />
    	 * 3) Get <b>maxWidth</b>.
    	 */
    	@Override
    	public int getWidth() {
    		int width = super.getWidth();
    		if (width <= 0) {
    			ImageView imageView = (ImageView) viewRef.get();
    			if (imageView != null) {
    				width = getImageViewFieldValue(imageView, "mMaxWidth"); // Check maxWidth parameter
    			}
    		}
    		return width;
    	}
    
    	/**
    	 * {@inheritDoc}
    	 * <br />
    	 * 3) Get <b>maxHeight</b>
    	 */
    	@Override
    	public int getHeight() {
    		int height = super.getHeight();
    		if (height <= 0) {
    			ImageView imageView = (ImageView) viewRef.get();
    			if (imageView != null) {
    				height = getImageViewFieldValue(imageView, "mMaxHeight"); // Check maxHeight parameter
    			}
    		}
    		return height;
    	}
    
    	@Override
    	public ViewScaleType getScaleType() {
    		ImageView imageView = (ImageView) viewRef.get();
    		if (imageView != null) {
    			return ViewScaleType.fromImageView(imageView);
    		}
    		return super.getScaleType();
    	}
    
    	@Override
    	public ImageView getWrappedView() {
    		return (ImageView) super.getWrappedView();
    	}
    
    	@Override
    	protected void setImageDrawableInto(Drawable drawable, View view) {
    		((ImageView) view).setImageDrawable(drawable);
    		if (drawable instanceof AnimationDrawable) {
    			((AnimationDrawable)drawable).start();
    		}
    	}
    
    	@Override
    	protected void setImageBitmapInto(Bitmap bitmap, View view) {
    		((ImageView) view).setImageBitmap(bitmap);
    	}
    
    	private static int getImageViewFieldValue(Object object, String fieldName) {
    		int value = 0;
    		try {
    			Field field = ImageView.class.getDeclaredField(fieldName);
    			field.setAccessible(true);
    			int fieldValue = (Integer) field.get(object);
    			if (fieldValue > 0 && fieldValue < Integer.MAX_VALUE) {
    				value = fieldValue;
    			}
    		} catch (Exception e) {
    			L.e(e);
    		}
    		return value;
    	}
    }
    

      ViewAware的实现

    public abstract class ViewAware implements ImageAware {
    
    	public static final String WARN_CANT_SET_DRAWABLE = "Can't set a drawable into view. You should call ImageLoader on UI thread for it.";
    	public static final String WARN_CANT_SET_BITMAP = "Can't set a bitmap into view. You should call ImageLoader on UI thread for it.";
    
    	protected Reference<View> viewRef;
    	protected boolean checkActualViewSize;
    
    	/**
    	 * Constructor. <br />
    	 * References {@link #ViewAware(android.view.View, boolean) ImageViewAware(imageView, true)}.
    	 *
    	 * @param view {@link android.view.View View} to work with
    	 */
    	public ViewAware(View view) {
    		this(view, true);
    	}
    
    	/**
    	 * Constructor
    	 *
    	 * @param view                {@link android.view.View View} to work with
    	 * @param checkActualViewSize <b>true</b> - then {@link #getWidth()} and {@link #getHeight()} will check actual
    	 *                            size of View. It can cause known issues like
    	 *                            <a href="https://github.com/nostra13/Android-Universal-Image-Loader/issues/376">this</a>.
    	 *                            But it helps to save memory because memory cache keeps bitmaps of actual (less in
    	 *                            general) size.
    	 *                            <p/>
    	 *                            <b>false</b> - then {@link #getWidth()} and {@link #getHeight()} will <b>NOT</b>
    	 *                            consider actual size of View, just layout parameters. <br /> If you set 'false'
    	 *                            it's recommended 'android:layout_width' and 'android:layout_height' (or
    	 *                            'android:maxWidth' and 'android:maxHeight') are set with concrete values. It helps to
    	 *                            save memory.
    	 */
    	public ViewAware(View view, boolean checkActualViewSize) {
    		if (view == null) throw new IllegalArgumentException("view must not be null");
    
    		this.viewRef = new WeakReference<View>(view);
    		this.checkActualViewSize = checkActualViewSize;
    	}
    
    	/**
    	 * {@inheritDoc}
    	 * <p/>
    	 * Width is defined by target {@link android.view.View view} parameters, configuration
    	 * parameters or device display dimensions.<br />
    	 * Size computing algorithm (go by steps until get non-zero value):<br />
    	 * 1) Get the actual drawn <b>getWidth()</b> of the View<br />
    	 * 2) Get <b>layout_width</b>
    	 */
    	@Override
    	public int getWidth() {
    		View view = viewRef.get();
    		if (view != null) {
    			final ViewGroup.LayoutParams params = view.getLayoutParams();
    			int width = 0;
    			if (checkActualViewSize && params != null && params.width != ViewGroup.LayoutParams.WRAP_CONTENT) {
    				width = view.getWidth(); // Get actual image width
    			}
    			if (width <= 0 && params != null) width = params.width; // Get layout width parameter
    			return width;
    		}
    		return 0;
    	}
    
    	/**
    	 * {@inheritDoc}
    	 * <p/>
    	 * Height is defined by target {@link android.view.View view} parameters, configuration
    	 * parameters or device display dimensions.<br />
    	 * Size computing algorithm (go by steps until get non-zero value):<br />
    	 * 1) Get the actual drawn <b>getHeight()</b> of the View<br />
    	 * 2) Get <b>layout_height</b>
    	 */
    	@Override
    	public int getHeight() {
    		View view = viewRef.get();
    		if (view != null) {
    			final ViewGroup.LayoutParams params = view.getLayoutParams();
    			int height = 0;
    			if (checkActualViewSize && params != null && params.height != ViewGroup.LayoutParams.WRAP_CONTENT) {
    				height = view.getHeight(); // Get actual image height
    			}
    			if (height <= 0 && params != null) height = params.height; // Get layout height parameter
    			return height;
    		}
    		return 0;
    	}
    
    	@Override
    	public ViewScaleType getScaleType() {
    		return ViewScaleType.CROP;
    	}
    
    	@Override
    	public View getWrappedView() {
    		return viewRef.get();
    	}
    
    	@Override
    	public boolean isCollected() {
    		return viewRef.get() == null;
    	}
    
    	@Override
    	public int getId() {
    		View view = viewRef.get();
    		return view == null ? super.hashCode() : view.hashCode();
    	}
    
    	@Override
    	public boolean setImageDrawable(Drawable drawable) {
    		if (Looper.myLooper() == Looper.getMainLooper()) {
    			View view = viewRef.get();
    			if (view != null) {
    				setImageDrawableInto(drawable, view);
    				return true;
    			}
    		} else {
    			L.w(WARN_CANT_SET_DRAWABLE);
    		}
    		return false;
    	}
    
    	@Override
    	public boolean setImageBitmap(Bitmap bitmap) {
    		if (Looper.myLooper() == Looper.getMainLooper()) {
    			View view = viewRef.get();
    			if (view != null) {
    				setImageBitmapInto(bitmap, view);
    				return true;
    			}
    		} else {
    			L.w(WARN_CANT_SET_BITMAP);
    		}
    		return false;
    	}
    
    	/**
    	 * Should set drawable into incoming view. Incoming view is guaranteed not null.<br />
    	 * This method is called on UI thread.
    	 */
    	protected abstract void setImageDrawableInto(Drawable drawable, View view);
    
    	/**
    	 * Should set Bitmap into incoming view. Incoming view is guaranteed not null.< br />
    	 * This method is called on UI thread.
    	 */
    	protected abstract void setImageBitmapInto(Bitmap bitmap, View view);
    }

      其中 boolean isCollected() 是否被gc回收 注意实现函数。

    options参数:显示的一些配置,后续会统一讲解

    imageSize参数:加载图片最大宽高

    ImageLoadingListener,ImageLoadingProgressListener加载过程中的监听器

    二.加载过程(保存,加载,显示)

    1.一些参数的校验和初始化

    其中:ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize());  

    targetSize(最大宽高)如果为空会取容器的宽高(避免图片太大内存溢出)。

    2.接下来开始加载图片。

    首先由路径生成内存缓存的唯一Key值memoryCacheKey ,且缓存起来。格式:[imageUri]_[width]x[height] 由此可见同一图片不同宽高缓存的是两个Key。

    接着根据key获取缓存中的bitmap值。

    如果本地缓存有则直接显示 其中 options.shouldPostProcess()==true表示后期需要Bitmap处理的情况,具体Bitmap处理逻辑需要配置 BitmapProcessor postProcessor去处理,实现 process方法,如切图等等耗时操作放在线程中去处理,此类如下:

    public interface BitmapProcessor {
    	/**
    	 * Makes some processing of incoming bitmap.<br />
    	 * This method is executing on additional thread (not on UI thread).<br />
    	 * <b>Note:</b> If this processor is used as {@linkplain DisplayImageOptions.Builder#preProcessor(BitmapProcessor)
    	 * pre-processor} then don't forget {@linkplain Bitmap#recycle() to recycle} incoming bitmap if you return a new
    	 * created one.
    	 *
    	 * @param bitmap Original {@linkplain Bitmap bitmap}
    	 * @return Processed {@linkplain Bitmap bitmap}
    	 */
    	Bitmap process(Bitmap bitmap);
    }

     如果无本地缓存去加载,其中如果配置加载图片则显示 

    imageAware.setImageDrawable(options.getImageOnLoading(configuration.resources));

    接着我们分析比较关键在加载代码:

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

     engine.submit 就是线程池中加入线程异步处理,如果配置不限制线程数量默认最大线程数。我们直接看run方法(图片加载过程)

    public void run() {
    		if (waitIfPaused()) return;
    		if (delayIfNeed()) return;
    
    		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();
    		Bitmap bmp;
    		try {
    			checkTaskNotActual();
    
    			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);
    			}
    
    			if (bmp != null && options.shouldPostProcess()) {
    				L.d(LOG_POSTPROCESS_IMAGE, memoryCacheKey);
    				bmp = options.getPostProcessor().process(bmp);
    				if (bmp == null) {
    					L.e(ERROR_POST_PROCESSOR_NULL, memoryCacheKey);
    				}
    			}
    			checkTaskNotActual();
    			checkTaskInterrupted();
    		} catch (TaskCancelledException e) {
    			fireCancelEvent();
    			return;
    		} finally {
    			loadFromUriLock.unlock();
    		}
    
    		DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);
    		runTask(displayBitmapTask, syncLoading, handler, engine);
    	}
    

    if (waitIfPaused()) return; 如果当前暂停加载无限期wait,开关在ImageLoader的pause()和resume()方法,比如快速滑动时暂停加载,停止滑动时恢复

    if (delayIfNeed()) return; 延迟显示选项 开关和延迟时间配置在 option里

    ReentrantLock loadFromUriLock 用到了同步锁 同步锁的创建在 ImageLoaderEngine中

    ReentrantLock getLockForUri(String uri) {
    		ReentrantLock lock = uriLocks.get(uri);
    		if (lock == null) {
    			lock = new ReentrantLock();
    			uriLocks.put(uri, lock);
    		}
    		return lock;
    	}
    

    可以看到同一路径bitmap的加载需要同步操作

    接着看,从缓存中取缓存没有执行tryLoadBitmap加载Bitmap文件,看 tryLoadBitmap 源码如下:

    从本地磁盘找如果找到去解析(加载本地文件),找不到执行缓存到本地磁盘操作(存文件)

    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()));
    			}
    			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()) {
    					imageFile = configuration.diskCache.get(uri);
    					if (imageFile != null) {
    						imageUriForDecoding = Scheme.FILE.wrap(imageFile.getAbsolutePath());
    					}
    				}
    
    				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;
    	}

     如何存文件? tryCacheImageOnDisk

    	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;
    	}
    将路径uri直接转流存文件 downloadImage

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

     其中 resizeAndSaveImage 是什么意思? 大图片超过全局配置的最大宽高,进行压缩保存覆盖,节省空间

    	/** Decodes image file into Bitmap, resize it and save it back */
    	private boolean resizeAndSaveImage(int maxWidth, int maxHeight) throws IOException {
    		// Decode image file, compress and re-save it
    		boolean saved = false;
    		File targetFile = configuration.diskCache.get(uri);
    		if (targetFile != null && targetFile.exists()) {
    			ImageSize targetImageSize = new ImageSize(maxWidth, maxHeight);
    			DisplayImageOptions specialOptions = new DisplayImageOptions.Builder().cloneFrom(options)
    					.imageScaleType(ImageScaleType.IN_SAMPLE_INT).build();
    			ImageDecodingInfo decodingInfo = new ImageDecodingInfo(memoryCacheKey,
    					Scheme.FILE.wrap(targetFile.getAbsolutePath()), uri, targetImageSize, ViewScaleType.FIT_INSIDE,
    					getDownloader(), specialOptions);
    			Bitmap bmp = decoder.decode(decodingInfo);
    			if (bmp != null && configuration.processorForDiskCache != null) {
    				L.d(LOG_PROCESS_IMAGE_BEFORE_CACHE_ON_DISK, memoryCacheKey);
    				bmp = configuration.processorForDiskCache.process(bmp);
    				if (bmp == null) {
    					L.e(ERROR_PROCESSOR_FOR_DISK_CACHE_NULL, memoryCacheKey);
    				}
    			}
    			if (bmp != null) {
    				saved = configuration.diskCache.save(uri, bmp);
    				bmp.recycle();
    			}
    		}
    		return saved;
    	}

    从缓存中存在文件或者保存完文件会执行加载bitmap过程,bitmap = decodeImage(imageUriForDecoding);
    	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);
    	}
    

     ImageDecoder 是给接口 默认用的是 BaseImageDecoder,我们看下 decode 方法

    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;
    	}
    

    获取输入流

    defineImageSizeAndRotation解析旋转角度,如对于相册中的图片(ExifInfo对象)

    resetStream初始化流

    considerExactScaleAndOrientatiton 根据各种配置缩放图片,旋转图片

    protected Bitmap considerExactScaleAndOrientatiton(Bitmap subsampledBitmap, ImageDecodingInfo decodingInfo,
    			int rotation, boolean flipHorizontal) {
    		Matrix m = new Matrix();
    		// Scale to exact size if need
    		ImageScaleType scaleType = decodingInfo.getImageScaleType();
    		if (scaleType == ImageScaleType.EXACTLY || scaleType == ImageScaleType.EXACTLY_STRETCHED) {
    			ImageSize srcSize = new ImageSize(subsampledBitmap.getWidth(), subsampledBitmap.getHeight(), rotation);
    			float scale = ImageSizeUtils.computeImageScale(srcSize, decodingInfo.getTargetSize(), decodingInfo
    					.getViewScaleType(), scaleType == ImageScaleType.EXACTLY_STRETCHED);
    			if (Float.compare(scale, 1f) != 0) {
    				m.setScale(scale, scale);
    
    				if (loggingEnabled) {
    					L.d(LOG_SCALE_IMAGE, srcSize, srcSize.scale(scale), scale, decodingInfo.getImageKey());
    				}
    			}
    		}
    		// Flip bitmap if need
    		if (flipHorizontal) {
    			m.postScale(-1, 1);
    
    			if (loggingEnabled) L.d(LOG_FLIP_IMAGE, decodingInfo.getImageKey());
    		}
    		// Rotate bitmap if need
    		if (rotation != 0) {
    			m.postRotate(rotation);
    
    			if (loggingEnabled) L.d(LOG_ROTATE_IMAGE, rotation, decodingInfo.getImageKey());
    		}
    
    		Bitmap finalBitmap = Bitmap.createBitmap(subsampledBitmap, 0, 0, subsampledBitmap.getWidth(), subsampledBitmap
    				.getHeight(), m, true);
    		if (finalBitmap != subsampledBitmap) {
    			subsampledBitmap.recycle();
    		}
    		return finalBitmap;
    	}
    

      

    3.最后显示图片

    DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);

    runTask(displayBitmapTask, syncLoading, handler, engine); 

    保证在UI线程中执行

    	static void runTask(Runnable r, boolean sync, Handler handler, ImageLoaderEngine engine) {
    		if (sync) {
    			r.run();
    		} else if (handler == null) {
    			engine.fireCallback(r);
    		} else {
    			handler.post(r);
    		}
    	}
    

      看显示task的实现 displayBitmapTask.run

    	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);
    			engine.cancelDisplayTaskFor(imageAware);
    			listener.onLoadingComplete(imageUri, imageAware.getWrappedView(), bitmap);
    		}
    	}
    

      BitmapDisplayer 是个接口,可以各种扩展显示不同样式

    public interface BitmapDisplayer {
    	/**
    	 * Displays bitmap in {@link com.nostra13.universalimageloader.core.imageaware.ImageAware}.
    	 * <b>NOTE:</b> This method is called on UI thread so it's strongly recommended not to do any heavy work in it.
    	 *
    	 * @param bitmap     Source bitmap
    	 * @param imageAware {@linkplain com.nostra13.universalimageloader.core.imageaware.ImageAware Image aware view} to
    	 *                   display Bitmap
    	 * @param loadedFrom Source of loaded image
    	 */
    	void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom);
    }
    

      如自带的实现圆角效果

    public class RoundedBitmapDisplayer implements BitmapDisplayer {
    
    	protected final int cornerRadius;
    	protected final int margin;
    
    	public RoundedBitmapDisplayer(int cornerRadiusPixels) {
    		this(cornerRadiusPixels, 0);
    	}
    
    	public RoundedBitmapDisplayer(int cornerRadiusPixels, int marginPixels) {
    		this.cornerRadius = cornerRadiusPixels;
    		this.margin = marginPixels;
    	}
    
    	@Override
    	public void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom) {
    		if (!(imageAware instanceof ImageViewAware)) {
    			throw new IllegalArgumentException("ImageAware should wrap ImageView. ImageViewAware is expected.");
    		}
    
    		imageAware.setImageDrawable(new RoundedDrawable(bitmap, cornerRadius, margin));
    	}
    
    	public static class RoundedDrawable extends Drawable {
    
    		protected final float cornerRadius;
    		protected final int margin;
    
    		protected final RectF mRect = new RectF(),
    				mBitmapRect;
    		protected final BitmapShader bitmapShader;
    		protected final Paint paint;
    
    		public RoundedDrawable(Bitmap bitmap, int cornerRadius, int margin) {
    			this.cornerRadius = cornerRadius;
    			this.margin = margin;
    
    			bitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
    			mBitmapRect = new RectF (margin, margin, bitmap.getWidth() - margin, bitmap.getHeight() - margin);
    			
    			paint = new Paint();
    			paint.setAntiAlias(true);
    			paint.setShader(bitmapShader);
    			paint.setFilterBitmap(true);
    			paint.setDither(true);
    		}
    
    		@Override
    		protected void onBoundsChange(Rect bounds) {
    			super.onBoundsChange(bounds);
    			mRect.set(margin, margin, bounds.width() - margin, bounds.height() - margin);
    			
    			// Resize the original bitmap to fit the new bound
    			Matrix shaderMatrix = new Matrix();
    			shaderMatrix.setRectToRect(mBitmapRect, mRect, Matrix.ScaleToFit.FILL);
    			bitmapShader.setLocalMatrix(shaderMatrix);
    			
    		}
    
    		@Override
    		public void draw(Canvas canvas) {
    			canvas.drawRoundRect(mRect, cornerRadius, cornerRadius, paint);
    		}
    
    		@Override
    		public int getOpacity() {
    			return PixelFormat.TRANSLUCENT;
    		}
    
    		@Override
    		public void setAlpha(int alpha) {
    			paint.setAlpha(alpha);
    		}
    
    		@Override
    		public void setColorFilter(ColorFilter cf) {
    			paint.setColorFilter(cf);
    		}
    	}
    }
    

      

    到此over了,流程通了。

    ----------------------------------------分割线-----------------------------------------------
    总结:
    1.保存图片:路径转流直接存文件,如果文件过大直接裁剪覆盖文件
    2.加载图片:先看检查内存中是否缓存,未缓存拿本地缓存,根据配置裁剪和角度旋转
    3.显示:根据配置定制不同的显示



  • 相关阅读:
    添加gitignore文件后使其生效
    git提交时如何忽略一些文件
    苹果:Safari 4 下载量三天内达 1100 万次
    Linux Multitouch 技术展示
    HTML 5 来了,不需要 Flash 插件的 Youtube
    JSR 299(Java EE 平台的上下文与依赖注入)最终建议草案
    JSR 330(Java 的依赖注入)通过 JCP 接受
    HTML 5 来了,不需要 Flash 插件的 Youtube
    22 条经典的编程引言
    JSR 330(Java 的依赖注入)通过 JCP 接受
  • 原文地址:https://www.cnblogs.com/wjw334/p/7280039.html
Copyright © 2011-2022 走看看