Complete parsing of Android open-source framework Universal-Image-Loader (iii) --- source code interpretation, loaderandroid
Reprinted please indicate this article from xiaanming blog (http://blog.csdn.net/xiaanming/article/details/39057201), please respect others' hard work results, thank you!
This article is mainly to show you how to interpret this powerful image loading framework from the source code point of view. I haven't written an article for a long time, and I feel a lot unfamiliar. It is more than three months away from the previous article, I am very busy. I have to look at and understand a lot of things in my work, and I am also lazy. I don't feel as passionate as I used to be, I want to keep up with my previous passion. The so-called good memory is not as bad as a pen. Sometimes I will read what I have previously written. I think it takes longer to write down my knowledge than to keep it in my mind, today, I will read the source code of this framework. I feel that this image loading framework is indeed well written and I have learned a lot after reading the code. I hope you can take a look at the complete parsing of the Android open-source framework Universal-Image-Loader (1) --- basic introduction and use, and full parsing of the Android open-source framework Universal-Image-Loader (2) --- detailed explanation of the image Cache Policy. I hope you can stick to it and it will definitely benefit you.
ImageView mImageView = (ImageView) findViewById (R. id. image); String imageUrl = "https://lh6.googleusercontent.com/-55osAWw3x0Q/URquUtcFr5I/AAAAAAAAAbs/rWlj1RUKrYI/s1024/A%252520Photographer.jpg"; // display the image configuration DisplayImageOptions = new DisplayImageOptions. builder (). showImageOnLoading (R. drawable. ic_stub ). showImageOnFail (R. drawable. ic_error ). cacheInMemory (true ). cacheOnDisk (true ). bitmapConfig (Bitmap. config. RGB_565 ). build (); ImageLoader. getInstance (). displayImage (imageUrl, mImageView, options );
Most of the time, we use the above Code to load images. Let's take a look at it first.
public void displayImage(String uri, ImageView imageView, DisplayImageOptions options) {displayImage(uri, new ImageViewAware(imageView), options, null, null);}
From the code above, we can see that it will convert ImageView to ImageViewAware. What is the main function of ImageViewAware? This class is mainly used to wrap the ImageView and change the strong reference of the ImageView into a weak reference. When the memory is insufficient, the ImageView object can be better recycled, and the width and height of the ImageView can be obtained. This allows us to crop the Image Based on the width and height of the ImageView to reduce memory usage.
Next let's take a look at the specific displayImage method. Because this method has a lot of code, I will read it separately.
checkConfiguration();if (imageAware == null) {throw new IllegalArgumentException(ERROR_WRONG_ARGUMENTS);}if (listener == null) {listener = emptyListener;}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;}
The Code in line 1st checks whether ImageLoaderConfiguration is initialized. This Initialization is performed in the Application.
Line 12-21 mainly deals with processing when the url is empty. In line 3 of the Code, there is a HashMap in ImageLoaderEngine to record the loading task, when an image is loaded, the ImageView id and image url are added to the HashMap. After the image is loaded, it is removed. Then, the imageResForEmptyUri image of DisplayImageOptions is set to ImageView, finally, the ImageLoadingListener interface is called back to tell it that the task has been completed.
ImageSize 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);}}
Row 3 encapsulates the width and height of the ImageView into an ImageSize object. If the width and height of the ImageView are 0, the width and height of the mobile phone screen are used as the width and height of the ImageView, when we use ListView and GridView to load images, the width obtained on the first page is 0, so the screen width and height of the mobile phone used on the first page are the size of the control.
Row 3 retrieves Bitmap objects from the memory cache. We can configure the memory cache logic in ImageLoaderConfiguration. The LruMemoryCache is used by default. This class is described in the previous article.
There is a judgment in row 11th. If postProcessor is set in DisplayImageOptions, it enters the true logic. However, the default value of postProcessor is null. The BitmapProcessor interface is mainly used to process Bitmap, this framework does not provide the corresponding implementation. If we have our own requirements, we can implement the BitmapProcessor interface by ourselves (for example, set the image to a circle)
Line 22nd-23 sets Bitmap to ImageView. Here we can configure the display requirement displayer in DisplayImageOptions. By default, SimpleBitmapDisplayer is used to directly set Bitmap to ImageView, we can configure other display logics. Here we provide the FadeInBitmapDisplayer (transparency from 0-1) RoundedBitmapDisplayer (four corners are arcs) and then callback to the ImageLoadingListener interface.
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);}
This code mainly indicates that Bitmap is not in the memory cache. bitmap objects are obtained from files or networks and a LoadAndDisplayImageTask object is instantiated. LoadAndDisplayImageTask implements Runnable. If isSyncLoading is set to true, directly execute the run method of LoadAndDisplayImageTask, which indicates synchronization. The default value is false. Submit LoadAndDisplayImageTask to the thread pool object.
Next, let's take a look at LoadAndDisplayImageTask's run (). This class is still quite complex. We will analyze it for a while.
if (waitIfPaused()) return;if (delayIfNeed()) return;
If true is returned for waitIfPaused () and delayIfNeed (), it is returned directly from the run () method without executing the following logic. Next let's take a look at waitIfPaused ()
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();}
Why is this method used? The main reason is that when we use ListView and GridView to load images, sometimes to slide more smoothly, we will choose not to load the image when the fingers slide or suddenly slide, So we proposed such a method, so how to use it? The PauseOnScrollListener class is used here. It is very simple to use ListView. setOnScrollListener (new PauseOnScrollListener (pauseOnScroll, pauseOnFling), pauseOnScroll controls our slow sliding ListView, whether the GridView stops image loading, pauseOnFling controls the violent sliding ListView, and whether the GridView stops image loading
In addition, the return value of this method is determined by isTaskNotActual (). Let's take a look at the source code of isTaskNotActual ().
private boolean isTaskNotActual() {return isViewCollected() || isViewReused();}
IsViewCollected () is used to determine whether our ImageView has been recycled by the garbage collector. If it has been recycled, run () of the LoadAndDisplayImageTask method will return directly, and isViewReused () will judge whether the ImageView has been reused, the run () method is reused and returns directly. Why is the isViewReused () method used? The main feature is ListView. In the GridView, We will reuse the item object. If we load the ListView first, when the first page of the GridView is uploaded, We will scroll quickly before the first page is fully loaded, the isViewReused () method prevents these invisible items from loading images, and directly loads the images on the current interface.
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 firedcheckTaskNotActual();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();}
The Code in line 1st has a loadFromUriLock, which is a lock. The method for obtaining the lock is in the getLockForUri () method of the ImageLoaderEngine class.
ReentrantLock getLockForUri(String uri) {ReentrantLock lock = uriLocks.get(uri);if (lock == null) {lock = new ReentrantLock();uriLocks.put(uri, lock);}return lock;}
We can see from the above that the Lock Object corresponds to the image url. Why? You still have some questions. I don't know if you have considered a scenario. If an item in a ListView is being acquired, at this time, we will roll this item out of the interface and then it will be rolled in again. If there is no lock after it is rolled in, the item will be loaded again, if the image is rolled frequently in a short period of time, the request image is sent to the network multiple times. Therefore, a ReentrantLock object is matched according to the image Url, the request with the same Url will wait for the first line. After the image is loaded, the ReentrantLock will be released, and the requests with the same Url will continue to execute the code below 7th lines.
In the first row, they will first get it from the memory cache. If the memory cache is not executing the following logic, therefore, ReentrantLock is used to prevent repeated image requests from the network.
The method tryLoadBitmap () in the second row is indeed a little long. I will tell you that the logic here is to first obtain the Bitmap object from the file cache, if you have not obtained the bitmap from the network and saved it in the file system, let's analyze it.
File imageFile = configuration.diskCache.get(uri);if (imageFile != null && imageFile.exists()) {L.d(LOG_LOAD_IMAGE_FROM_DISK_CACHE, memoryCacheKey);loadedFrom = LoadedFrom.DISC_CACHE;checkTaskNotActual();bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));}
First, check whether the file is cached. If yes, call the decodeImage () method to decode the image. In this method, call the decode () method of the BaseImageDecoder class, based on the width and height of the ImageView, ScaleType crops the image. I will not introduce the specific code. Let's take a look at it. Next we will look at the tryLoadBitmap () method.
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);}}
Row 1st indicates that the Bitmap obtained from the File Cache is null, or the width and height are 0. You can get Bitmap on the network and check whether the isCacheOnDisk of DisplayImageOptions is configured in the code of row 6th, indicates whether to save the Bitmap object in the file system. Generally, we need to set it to true. The default value is false. Note that the tryCacheImageOnDisk () method is executed, pull images from the server and save them to a local file.
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); // 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());return configuration.diskCache.save(uri, is, this);}
The downloadImage () method of Row 3 downloads images and keeps them in the File Cache. It calls back the download and save Bitmap progress to IoUtils. in the onBytesCopied (int current, int total) method of the CopyListener interface, we can set the ImageLoadingProgressListener interface to get the progress of image download and storage. The image saved in the file system is the source image.
Line 16-17 obtains whether ImageLoaderConfiguration sets the image size stored in the file system. If maxImageWidthForDiskCache and maxImageHeightForDiskCache are set, the resizeAndSaveImage () method is called to crop the image and then the original image is replaced, save the cropped image to the file system. Some people have asked me before how to save the thumbnail because the image saved in the file system is an original image, you only need to set maxImageWidthForDiskCache and maxImageHeightForDiskCache when instantiating ImageLoaderConfiguration in the Application.
if (bmp == null) return; // listener callback already was firedcheckTaskNotActual();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);}
Next, let's make it simple. If you want to process Bitmap in lines 6-12, you need to implement it by yourself. In lines 14-17, you need to save the image to the memory cache.
DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);runTask(displayBitmapTask, syncLoading, handler, engine);
The last two lines of code are a display task. You can directly view the run () method of the DisplayBitmapTask class.
@Overridepublic 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);}}
If the ImageView is recycled or reused, it calls back to the ImageLoadingListener interface. Otherwise, BitmapDisplayer is called to display Bitmap.
The article has been written here. I don't know if you have any further understanding of this open-source framework. The design of this open-source framework is flexible, and many design modes are used, such as the builder mode, the decoration mode, proxy mode, and Policy mode facilitate the expansion and implementation of the functions we want. This is what we will explain today, if you do not understand this framework, leave a message below. I will try my best to answer this question.
About the android Image cache framework Universal-Image-Loader
The reason for the disappearance is to release resources. You can look at the source code and customize it as needed.
The android system is open-source, and the system code can be seen. Will the source code of the software be seen?
Product 2 on the first floor
On the third floor of the second floor, the system software is also open-source, but third-party software may not be open-source.