Picture Loading Frame Picture Loading Frame Selection Previous

Posted by scotte on Mon, 18 Nov 2019 03:35:07 +0100

Ali P7 Mobile Internet Architect Advanced Video (Updated Daily) Free Learning Click: https://space.bilibili.com/474380680
This article will illustrate the selection of the picture loading framework through Universal-Image-Loader parsing:

1. [Introduction and use of Universal-Image-Loader parsing]

Basic Introduction

I believe that when you normally make Android applications, you will more or less come into contact with the problem of loading pictures asynchronously or a large number of pictures. Loading pictures often encounters many problems, such as confusion of pictures, OOM and so on. For novices, these problems will be more difficult to solve, so there are many open source picture loading frameworks emerged as the times require.The name is Universal-Image-Loader. I believe many friends have heard about or used this powerful picture loading framework. Today this article is a basic introduction and use of this framework, mainly to help those who have not used it.The project exists on Github Android-Universal-Image-Loader Let's first see what the characteristics of this open source inventory are

  • Multithreaded download of pictures from network, file system, project folder assets and drawable medium
  • Supports arbitrary configuration of ImageLoader, such as thread pools, picture downloaders, memory caching policies, hard disk caching policies, picture display options, and other configurations
  • Supports memory caching of pictures, file system caching or SD card caching
  • Supports monitoring of the picture download process
  • Clip Bitmap according to the size of the control (ImageView) to reduce Bitmap's excessive memory usage
  • Better control over the loading process of pictures, such as pausing the loading of pictures, restarting the loading of pictures, commonly used in ListView,GridView, pausing the loading of pictures during sliding, loading pictures when stopping sliding
  • Provides loading pictures on a slower network

Of course, the features listed above may not be complete. To understand some other features, we can only slowly discover them through our use.

ImageLoaderConfiguration

Configuration parameters for the image loader ImageLoader, using Builder mode.
Common configuration properties are

//Get the built-in memory directory/data/data/.../cache from StorageUtils
File cacheDir = StorageUtils.getCacheDirectory(context);  
ImageLoaderConfiguration config = new ImageLoaderConfiguration  
                .Builder(getApplicationContext())  
                .memoryCacheExtraOptions(480, 800) //That is, the maximum length and width of each cache file saved  
                .threadPoolSize(3) //Number of loads in thread pool  
                .threadPriority(Thread.NORM_PRIORITY - 2)  
                //Interpretation: When the same Uri takes pictures of different sizes and caches them in memory, only one is cached.Multiple identical pictures of different sizes are cached by default  
                .denyCacheImageMultipleSizesInMemory()  //Refuse to cache multiple pictures.
                .memoryCache(new WeakMemoryCache()) //Cache policy can be implemented through your own memory cache, which uses weak references. The disadvantage is that it is too easy to recycle, not very good!
                .memoryCacheSize(2 * 1024 * 1024) //Set the size of the memory cache 
                .diskCacheSize(50 * 1024 * 1024) //Set disk cache size 50M    
                .diskCacheFileNameGenerator(new Md5FileNameGenerator()) //Encrypt the URI name at save time with MD5  
                .tasksProcessingOrder(QueueProcessingType.LIFO) //Set the sort of work queue for picture download and display  
                .diskCacheFileCount(100) //Number of cached files  
                .diskCache(new UnlimitedDiskCache(cacheDir)) //Custom Cache Path  
                .defaultDisplayImageOptions(defaultOptions) //Display picture parameters, default: DisplayImageOptions.createSimple()
                .imageDownloader(new BaseImageDownloader(this, 5 * 1000, 30 * 1000)) // ConneTimeout (5 s), readTimeout (30 s) timeout  
                .writeDebugLogs() //Open debug log
                .build();//Start building  
//Configuration Use
ImageLoader.getInstance().init(configuration);  

You can set parameters for memory caching, hard disk caching, and so on.

Picture loading and display can be done after setting relevant parameters

Picture Loading

ImageLader provides several ways to load pictures, mainly these displayImage(), loadImage(),loadImageSync(), loadImageSync() methods are synchronous, Android 4.0 has a feature that network operations cannot be on the main thread, so we do not use the loadImageSync() method

loadimage() load picture

Let's use ImageLoader's loadImage() method to load the network picture first

final ImageView mImageView = (ImageView) findViewById(R.id.image);  
        String imageUrl = "https://lh6.googleusercontent.com/-55osAWw3x0Q/URquUtcFr5I/AAAAAAAAAbs/rWlj1RUKrYI/s1024/A%252520Photographer.jpg";  

        ImageLoader.getInstance().loadImage(imageUrl, new ImageLoadingListener() {  

            @Override  
            public void onLoadingStarted(String imageUri, View view) {  

            }  

            @Override  
            public void onLoadingFailed(String imageUri, View view,  
                    FailReason failReason) {  

            }  

            @Override  
            public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {  
                mImageView.setImageBitmap(loadedImage);  
            }  

            @Override  
            public void onLoadingCancelled(String imageUri, View view) {  

            }  
        });  

The url of the incoming picture and the ImageLoaderListener, set the loadedImage on the ImageView in the callback method onLoadingComplete(), if you think the incoming ImageLoaderListener is too complex, we can use the SimpleImageLoadingListener class, which provides an empty implementation of the ImageLoaderListener interface method using the default adapter mode

final ImageView mImageView = (ImageView) findViewById(R.id.image);  
        String imageUrl = "https://lh6.googleusercontent.com/-55osAWw3x0Q/URquUtcFr5I/AAAAAAAAAbs/rWlj1RUKrYI/s1024/A%252520Photographer.jpg";  

        ImageLoader.getInstance().loadImage(imageUrl, new SimpleImageLoadingListener(){  

            @Override  
            public void onLoadingComplete(String imageUri, View view,  
                    Bitmap loadedImage) {  
                super.onLoadingComplete(imageUri, view, loadedImage);  
                mImageView.setImageBitmap(loadedImage);  
            }  

        });  

What if we want to specify the size of the picture, which is fine, initialize an ImageSize object, specify the width and height of the picture, and the code is as follows

final ImageView mImageView = (ImageView) findViewById(R.id.image);  
        String imageUrl = "https://lh6.googleusercontent.com/-55osAWw3x0Q/URquUtcFr5I/AAAAAAAAAbs/rWlj1RUKrYI/s1024/A%252520Photographer.jpg";  

        ImageSize mImageSize = new ImageSize(100, 100);  

        ImageLoader.getInstance().loadImage(imageUrl, mImageSize, new SimpleImageLoadingListener(){  

            @Override  
            public void onLoadingComplete(String imageUri, View view,  
                    Bitmap loadedImage) {  
                super.onLoadingComplete(imageUri, view, loadedImage);  
                mImageView.setImageBitmap(loadedImage);  
            }  

        });  

The above is just a simple way to use ImageLoader to load web pictures. In actual development, we don't use it that way, so how do we usually use it?We'll use DisplayImageOptions, which configures some options for picture display, such as the pictures that ImageView displays when the pictures are loaded, whether memory caching is required, whether file caching is required, and so on.

DisplayImageOptions

You can configure some options for picture display, such as pictures shown in ImageView during loading, whether memory caching is required, whether file caching is required, etc. The configurations we can choose from are as follows

DisplayImageOptions options = new DisplayImageOptions.Builder()  
        .showImageOnLoading(R.drawable.ic_stub) // resource or drawable  
        .showImageForEmptyUri(R.drawable.ic_empty) // resource or drawable  
        .showImageOnFail(R.drawable.ic_error) // resource or drawable  
        .resetViewBeforeLoading(false)  // default  
        .delayBeforeLoading(1000)  
        .cacheInMemory(false) // default  
        .cacheOnDisk(false) // default  
        .preProcessor(...)  
        .postProcessor(...)  
        .extraForDownloader(...)  
        .considerExifParams(false) // default  
        .imageScaleType(ImageScaleType.IN_SAMPLE_POWER_OF_2) // default  
        .bitmapConfig(Bitmap.Config.ARGB_8888) // default  
        .decodingOptions(...)  
        .displayer(new SimpleBitmapDisplayer()) // default  
        .handler(new Handler()) // default  
        .build();  

You can set it according to the actual situation.

displayImage() Load Picture

Next, let's take a look at another way to load web pictures, displayImage(), with the following code

        String imageUrl = "https://lh6.googleusercontent.com/-55osAWw3x0Q/URquUtcFr5I/AAAAAAAAAbs/rWlj1RUKrYI/s1024/A%252520Photographer.jpg";  

        //Display picture configuration  
        DisplayImageOptions options = 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);  

You can see here that the ImageView is passed directly to set the display, and there is no need to listen to the post-settings, which is the difference between displayImage and loadImage.

Load pictures from other sources

Using the Universal-Image-Loader framework, you can load not only network pictures, but also pictures in the sd card, Content provider, etc. It is very simple to use, just change the url of the pictures a little, here is the picture to load the file system

We just need to wrap each image's source with a Scheme (except Content provider) and pass it as a url to the imageLoader, where the Universal-Image-Loader framework gets input streams based on different Schemes

        //Pictures come from files
        String imagePath = "/mnt/sdcard/image.png";  
        String imageUrl = Scheme.FILE.wrap(imagePath);  
        //Equivalent to file:/mnt/sdcard/image.png

        //Pictures from Content provider  
        String contentprividerUrl = "content://media/external/audio/albumart/13";  

        //Picture from assets  
        String assetsUrl = Scheme.ASSETS.wrap("image.png");  

        //Pictures from  
        String drawableUrl = Scheme.DRAWABLE.wrap("R.drawable.image"); 

Once you get the corresponding URL, you can call the display/loadImage method to display it.

GirdView,ListView loads pictures

Believe that most people use GridView, ListView to display a large number of pictures, but when we quickly slide GridView, ListView, we want to stop loading pictures, and when GridView, ListView stops sliding, we load pictures of the current interface. This framework also provides this feature, which is also very simple to use. It provides PauseOnScrollListener classTo control the ListView, the GridView stops loading pictures during sliding, which uses the proxy mode

listView.setOnScrollListener(new PauseOnScrollListener(imageLoader, pauseOnScroll, pauseOnFling));  

gridView.setOnScrollListener(new PauseOnScrollListener(imageLoader, pauseOnScroll, pauseOnFling));  

The first parameter is our picture loading object ImageLoader.
The second is to control whether loading pictures is suspended during the sliding process, if you need to suspend transferring true,
The third parameter controls whether the picture loads when the interface slides violently

OutOfMemoryError

Although this framework has a good caching mechanism to effectively avoid the generation of OOM, generally the probability of OOM generation is small, but it does not guarantee that OutOfMemoryError will never happen. This framework makes a simple catch for OutOfMemoryError and ensures that our programs will encounter OOM without crash, but if we use this framework OOM often, we shouldHow can I improve?

  • Reduce the number of threads in the thread pool, configure in ImageLoaderConfiguration (.threadPoolSize), recommended configuration 1-5
  • Configuring bitmapConfig in the DisplayImageOptions option is Bitmap.Config.RGB_565, because ARGB_8888 is the default and uses RGB_565 twice less memory than ARGB_8888
  • Configure the memory cache for pictures in ImageLoaderConfiguration to be memoryCache(new WeakMemoryCache()) or not to use memory cache
  • Set.imageScaleType(ImageScaleType.IN_SAMPLE_INT) or imageScaleType(ImageScaleType.EXACTLY) in the DisplayImageOptions option

2. [Universal-Image-Loader Resolve Internal Cache Principles]

For caches we know about, memory cache MemoryCache and hard disk cache DiscCache are commonly used.One read fast capacity is small, while the other read slow capacity is large.

For each type of cache you use, you can configure ImageLoaderConfiguration to set up the cache, or you can customize the appropriate cache yourself.

ImageLoaderConfiguration configuration = new ImageLoaderConfiguration.Builder(this)  
        .memoryCache(new WeakMemoryCache())  
        .build();  

For Universal-Image-Loader, its cache structure is also divided into memory cache MemoryCache and hard disk cache DiskCache.

MemoryCache memory cache

First look at the structure diagram to understand the structure of the memory cache in the UIL

The UML class graphics are not drawn as standard due to limited space.
For base class MemoryCache it is an interface that defines the method of putting, get pictures

public interface MemoryCache {
    ...
    boolean put(String key, Bitmap value);

    Bitmap get(String key);

    Bitmap remove(String key);

    Collection<String> keys();

    void clear();
}

They are all familiar methods, but for other classes

Let's look at it one by one

LruMemoryCache

This class is the default memory cache class for this open source framework, caching strong references to bitmap s.Direct implementation of MemoryCache method

public class LruMemoryCache implements MemoryCache {

    private final LinkedHashMap<String, Bitmap> map;
    //Maximum capacity
    private final int maxSize;
    /** Current Cache Size */
    private int size;
    public LruMemoryCache(int maxSize) {
        ...
        this.maxSize = maxSize;
        this.map = new LinkedHashMap<String, Bitmap>(0, 0.75f, true);
    }
    @Override
    public final Bitmap get(String key) {
        ...
        synchronized (this) {
            return map.get(key);
        }
    }
    @Override
    public final boolean put(String key, Bitmap value) {
        ...
        synchronized (this) {
            size += sizeOf(key, value);
            Bitmap previous = map.put(key, value);
            if (previous != null) {
                size -= sizeOf(key, previous);
            }
        }

        trimToSize(maxSize);
        return true;
    }

    /**
     * Lru Algorithm, removes the longest entries when the capacity exceeds the maximum cache capacity
     */
    private void trimToSize(int maxSize) {
        while (true) {
            String key;
            Bitmap value;
            synchronized (this) {
                if (size < 0 || (map.isEmpty() && size != 0)) {
                    throw new IllegalStateException(getClass().getName() + ".sizeOf() is reporting inconsistent results!");
                }

                if (size <= maxSize || map.isEmpty()) {
                    break;
                }

                Map.Entry<String, Bitmap> toEvict = map.entrySet().iterator().next();
                if (toEvict == null) {
                    break;
                }
                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);
                size -= sizeOf(key, value);
            }
        }
    }

    @Override
    public final Bitmap remove(String key) {
        ...
        synchronized (this) {
            Bitmap previous = map.remove(key);
            if (previous != null) {
                size -= sizeOf(key, previous);
            }
            return previous;
        }
    }
    ...
    //Return the byte size of the picture
    private int sizeOf(String key, Bitmap value) {
        return value.getRowBytes() * value.getHeight();
    }
    ...
}

The source code for LruMemoryCache is also simple, with a member variable LinkedHashMap <String inside, and direct saving here in Bitmap> map is a strongly referenced form.
Mainly look at get,put method.
For the get method, it is easier to return the corresponding picture directly according to the specified key.
For the put method, capacity needs to be considered.

@Override
    public final boolean put(String key, Bitmap value) {
        ...
        synchronized (this) {
            size += sizeOf(key, value);
            Bitmap previous = map.put(key, value);
            if (previous != null) {
                size -= sizeOf(key, previous);
            }
        }

        trimToSize(maxSize);
        return true;
    }

The put method first calls the sizeof method, which returns the byte size of the specified Bitmap, then size +=, which increases the total cache size, and then calls the trimToSize method to determine the cache capacity.

private void trimToSize(int maxSize) {
        while (true) {
            String key;
            Bitmap value;
            synchronized (this) {
                if (size < 0 || (map.isEmpty() && size != 0)) {
                    throw new IllegalStateException(getClass().getName() + ".sizeOf() is reporting inconsistent results!");
                }

                if (size <= maxSize || map.isEmpty()) {
                    break;
                }

                Map.Entry<String, Bitmap> toEvict = map.entrySet().iterator().next();
                if (toEvict == null) {
                    break;
                }
                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);
                size -= sizeOf(key, value);
            }
        }
    }

If the added size cache capacity <= maxSize maximum cache capacity, then break directly without decision processing.
If larger, remove the oldest unused directly.

You have to wonder, how on earth does it tell the oldest unused?Didn't you see the code?

Believe you might know if you know LinkedHashMap.
LinkedHashMap has implemented sequential storage by itself, either by default in the order in which elements are added or by enabling sequential storage in which the most recently read data comes first and the earliest read data comes last, and then it has a way to determine whether to delete the oldest data, returning false by default, that is, not deleting the data.It's common to store them in order, and it's rarely forgotten that they can also use methods that haven't been used recently.

//A constructor for LinkedHashMap that sorts the access order when the accessOrder parameter is true, with the most recent visits first and the earliest visits last
public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) {
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
}

//LinkedHashMap's own method to determine whether to delete the oldest element returns false by default, meaning that the older data is not deleted
//All we have to do is rewrite this method to delete old data when certain conditions are met
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
        return false;
}

Looking back at our previous LinkedHashMap creation

  this.map = new LinkedHashMap<String, Bitmap>(0, 0.75f, true);

Another example of use

That's clear.

BaseMemoryCache

BaseMemoryCache also implements the MemoryCache method, but it is an abstract class.
It is a base class of memory cache and implements the common methods of memory cache, but it provides a Non-Strongly referenced Reference as an extension to facilitate GC recycling and avoid OOM.

public abstract class BaseMemoryCache implements MemoryCache {

    /** Stores not strong references to objects */
    private final Map<String, Reference<Bitmap>> softMap = Collections.synchronizedMap(new HashMap<String, Reference<Bitmap>>());

    @Override
    public Bitmap get(String key) {
        Bitmap result = null;
        Reference<Bitmap> reference = softMap.get(key);
        if (reference != null) {
            result = reference.get();
        }
        return result;
    }

    @Override
    public boolean put(String key, Bitmap value) {
        softMap.put(key, createReference(value));
        return true;
    }

    @Override
    public Bitmap remove(String key) {
        Reference<Bitmap> bmpRef = softMap.remove(key);
        return bmpRef == null ? null : bmpRef.get();
    }

    /** Creates {@linkplain Reference not strong} reference of value */
    protected abstract Reference<Bitmap> createReference(Bitmap value);
}

The code is also simple, holding a Map <String, Reference <Bitmap> softMap in memory to hold Non-Strongly referenced objects, depending on the abstract method createReference it implements.

WeakMemoryCache

Let's see that one of its subclasses, WeakMemoryCache, inherits from BaseMemory and implements createReference

public class WeakMemoryCache extends BaseMemoryCache {
    @Override
    protected Reference<Bitmap> createReference(Bitmap value) {
        return new WeakReference<Bitmap>(value);
    }
}

It is obvious to save weak reference objects.

LimitedMemoryCache

Let's look at another subclass of LimitedMemoryCache, but it doesn't implement the createReference method in BaseMemoryCache. It's also an abstract class that encapsulates an abstract method based on BaseMemoryCache
protected abstract Bitmap removeNext(); used to handle situations where the cache capacity is insufficient.

public abstract class LimitedMemoryCache extends BaseMemoryCache {
    ...
    //Bitmap currently saved to count caches
    private final List<Bitmap> hardCache = Collections.synchronizedList(new LinkedList<Bitmap>());
    ...
    @Override
    public boolean put(String key, Bitmap value) {
        boolean putSuccessfully = false;
        // Try to add value to hard cache
        int valueSize = getSize(value);
        int sizeLimit = getSizeLimit();
        int curCacheSize = cacheSize.get();
        if (valueSize < sizeLimit) {
            while (curCacheSize + valueSize > sizeLimit) {
                Bitmap removedValue = removeNext();
                if (hardCache.remove(removedValue)) {
                    curCacheSize = cacheSize.addAndGet(-getSize(removedValue));
                }
            }
            hardCache.add(value);
            cacheSize.addAndGet(valueSize);

            putSuccessfully = true;
        }
        // Add value to soft cache
        super.put(key, value);
        return putSuccessfully;
    }

    @Override
    public Bitmap remove(String key) {
        Bitmap value = super.get(key);
        if (value != null) {
            if (hardCache.remove(value)) {
                cacheSize.addAndGet(-getSize(value));
            }
        }
        return super.remove(key);
    }
   ...
    protected abstract int getSize(Bitmap value);

    protected abstract Bitmap removeNext();
}

You can see that another List <Bitmap>in the LimitedMemoryCache holds strong references, and another Map <String, Reference <Bitmap> softMap in the BaseMemoryCache holds Bitmap. Why?

This is mainly because there is no cache restriction processing in BaseMemoryCache, it just encapsulates put,get that implements the basic Bitmap.When the cache capacity is limited, it needs to be handled by subclasses.

Let's look at the put method here, the key is

while (curCacheSize + valueSize > sizeLimit) {
                Bitmap removedValue = removeNext();
                if (hardCache.remove(removedValue)) {
                    curCacheSize = cacheSize.addAndGet(-getSize(removedValue));
                }
            }

When capacity is exceeded, the call to the abstract method removeNext is implemented by the subclass itself, and hardCache is removed, but the removal of softMap is not called at this time.

That is, for List <Bitmap>when its cache capacity exceeds, it removes the first object to alleviate capacity, but Bitmap stored in Map <String, Reference <Bitmap> softMap is not removed.
Wouldn't a softMap be infinite if it went down like this?

This is because Bitmap stored in Map <String, Reference <Bitmap> softMap is a weak reference, while strong references are stored in List <Bitmap>, and when memory is low, the GC first cleans up the objects in softMap.

FIFOLimitedMemoryCache

Let's look at FIFO LimitedMemoryCache, a subclass of LimitedMemoryCache, and see that FIFO is FIFO.

public class FIFOLimitedMemoryCache extends LimitedMemoryCache {

    private final List<Bitmap> queue = Collections.synchronizedList(new LinkedList<Bitmap>());
    ...
    @Override
    public boolean put(String key, Bitmap value) {
        if (super.put(key, value)) {
            queue.add(value);
            return true;
        } else {
            return false;
        }
    }

    @Override
    public Bitmap remove(String key) {
        Bitmap value = super.get(key);
        if (value != null) {
            queue.remove(value);
        }
        return super.remove(key);
    }
    ...
    @Override
    protected Bitmap removeNext() {
        return queue.remove(0);
    }

    @Override
    protected Reference<Bitmap> createReference(Bitmap value) {
        return new WeakReference<Bitmap>(value);
    }
}

You can see that there is also a List <Bitmap> queue here to save records, whereas removeNext returns the first element of the queue, FIFO-compliant.

LRULimitedMemoryCache

Another subclass, LRULimitedMemoryCache, has not been used recently.

public class LRULimitedMemoryCache extends LimitedMemoryCache {

    /** Cache providing Least-Recently-Used logic */
    private final Map<String, Bitmap> lruCache = Collections.synchronizedMap(new LinkedHashMap<String, Bitmap>(INITIAL_CAPACITY, LOAD_FACTOR, true));
   ...
    @Override
    protected Bitmap removeNext() {
        Bitmap mostLongUsedValue = null;
        synchronized (lruCache) {
            Iterator<Entry<String, Bitmap>> it = lruCache.entrySet().iterator();
            if (it.hasNext()) {
                Entry<String, Bitmap> entry = it.next();
                mostLongUsedValue = entry.getValue();
                it.remove();
            }
        }
        return mostLongUsedValue;
    }

    @Override
    protected Reference<Bitmap> createReference(Bitmap value) {
        return new WeakReference<Bitmap>(value);
    }
}

As you can see, LRU processing here uses LinkedHashMap, the third parameter in its construction method is true to indicate the use of LRU, and then removeNext returns that Bitmap.

Similarly, other subclasses are listed below, so they are not listed one by one.

MemoryCache Summary

1. Use only strong reference caches

  • LruMemoryCache (this class is the default memory cache class for this open source framework, caching strong references to bitmap s)

2. Caches that use a combination of strong and weak references have

  • UsingFreqLimitedMemoryCache (delete the least frequently used bitmap first if the total number of cached pictures exceeds the limit)
  • LRULimitedMemoryCache (this is also the lru algorithm used, unlike LruMemoryCache, which caches weak references to bitmap s)
  • FIFOLimitedMemoryCache
  • LargestLimitedMemoryCache (Delete the largest bitmap object first when the cache limit is exceeded)
  • LimitedAgeMemoryCache (delete bitmap s when they have been in the cache longer than we set)

3. Use only weak reference caches

  • WeakMemoryCache (This type of cache has no limit on the total size of bitmap s. The only drawback is that it is unstable and the cached pictures are easily recycled)

DiskCache hard disk cache

Let's also look at the structure first

DiskCache is actually designed to be the same as MooryCache, which is also an interface for the base class DiskCache

public interface DiskCache {
    //Return to the root directory of the hard disk cache
    File getDirectory();

    File get(String imageUri);

    boolean save(String imageUri, InputStream imageStream, IoUtils.CopyListener listener) throws IOException;

    boolean save(String imageUri, Bitmap bitmap) throws IOException;

    boolean remove(String imageUri);

    void close();

    void clear();
}

Look one by one

LruDiskCache

LruDiskCache implements the DiskCache interface directly and uses LRU algorithm for cache processing.
Before understanding LruDiskCache again, understand another class, DiskLruCache

final class DiskLruCache implements Closeable {
    static final String JOURNAL_FILE = "journal";
    static final String JOURNAL_FILE_TEMP = "journal.tmp";
    static final String JOURNAL_FILE_BACKUP = "journal.bkp";
    static final String MAGIC = "libcore.io.DiskLruCache";
    ...
    private final LinkedHashMap<String, Entry> lruEntries =
            new LinkedHashMap<String, Entry>(0, 0.75f, true);
    ...
    final ThreadPoolExecutor executorService =
            new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
    ...
    public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize, int maxFileCount)
            throws IOException {
        ...
    }
    ...
    public synchronized Snapshot get(String key) throws IOException {
        ...
    }
    ...
    private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {
        ...
    }
    /** A snapshot of the values for an entry. */
    public final class Snapshot implements Closeable {
        private final String key;
        private final long sequenceNumber;
        private File[] files;
        private final InputStream[] ins;
        private final long[] lengths;
        ...
    }
    ...
    public final class Editor {
        private final Entry entry;
        private final boolean[] written;
        private boolean hasErrors;
        private boolean committed;
        ...
    }
    ...
    private final class Entry {
        private final String key;

        private final long[] lengths;

        private boolean readable;

        private Editor currentEditor;

        private long sequenceNumber;
        ...
    }

This DiskLruCache is a long and complex file tool class for LruDiskCache.The cached data here is stored in a directory on the file system.
Also notice a member variable here
private final LinkedHashMap<String, Entry> lruEntries =new LinkedHashMap<String, Entry>(0, 0.75f, true);
You know this is for LRU processing.

Meanwhile, the value here is Entry, which encapsulates the editing of the current file, Ediotr and key.
Here, Editor encapsulates OutputStream, where files are written, and Snapshot encapsulates InputStream, where files are read.

Looking back at LruDiskCache

public class LruDiskCache implements DiskCache {
    protected DiskLruCache cache;
    private File reserveCacheDir;

    protected final FileNameGenerator fileNameGenerator;
    ...
    public LruDiskCache(File cacheDir, File reserveCacheDir, FileNameGenerator fileNameGenerator, long cacheMaxSize,
            int cacheMaxFileCount) throws IOException {
        ...
        this.reserveCacheDir = reserveCacheDir;
        this.fileNameGenerator = fileNameGenerator;
        initCache(cacheDir, reserveCacheDir, cacheMaxSize, cacheMaxFileCount);
    }

    private void initCache(File cacheDir, File reserveCacheDir, long cacheMaxSize, int cacheMaxFileCount)
        ...
            cache = DiskLruCache.open(cacheDir, 1, 1, cacheMaxSize, cacheMaxFileCount);
        ...
    }
    @Override
    public File get(String imageUri) {
        DiskLruCache.Snapshot snapshot = null;
        try {
            snapshot = cache.get(getKey(imageUri));
            return snapshot == null ? null : snapshot.getFile(0);
        } 
        ...
    }
    @Override
    public boolean save(String imageUri, Bitmap bitmap) throws IOException {
        DiskLruCache.Editor editor = cache.edit(getKey(imageUri));
        ...
        OutputStream os = new BufferedOutputStream(editor.newOutputStream(0), bufferSize);
        boolean savedSuccessfully = false;
        try {
            savedSuccessfully = bitmap.compress(compressFormat, compressQuality, os);
        }
        ...
        return savedSuccessfully;
    }

First, the LruDiskCache internal member variable has DiskLruCache and the directory where the files are saved, and so on. In its construction method, the DiskLruCache.open method is called to create the DiskLruCache object. In its open method, the corresponding file system is created according to the directory of the files.

Looking at its save method, call the getKey method first to convert the uri to the corresponding key, and in cache,edit

private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {
        ...
        Entry entry = lruEntries.get(key);
        if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null
                || entry.sequenceNumber != expectedSequenceNumber)) {
            return null; // Snapshot is stale.
        }
        if (entry == null) {
            entry = new Entry(key);
            lruEntries.put(key, entry);
        } else if (entry.currentEditor != null) {
            return null; // Another edit is in progress.
        }

        Editor editor = new Editor(entry);
        entry.currentEditor = editor;
        ...
        return editor;
    }

An Entry object is created to hold it, then saved after lruEntries, and an Editor is created for the current Entry to be written to the file.

Called after s

        OutputStream os = new BufferedOutputStream(editor.newOutputStream(0), bufferSize);

In editor.newOutputStream, a file is created from the current directory and key, then an output stream of the file is opened, and then Bitmap is written.

Similarly, look at LruDiskCache's get method

@Override
    public File get(String imageUri) {
        DiskLruCache.Snapshot snapshot = null;
        try {
            snapshot = cache.get(getKey(imageUri));
            return snapshot == null ? null : snapshot.getFile(0);
        } 
        ...
    }

Called cache,get

public synchronized Snapshot get(String key) throws IOException {
        . . . 
        Entry entry = lruEntries.get(key);
        ...
        File[] files = new File[valueCount];
        InputStream[] ins = new InputStream[valueCount];
        try {
            File file;
            for (int i = 0; i < valueCount; i++) {
                file = entry.getCleanFile(i);
                files[i] = file;
                ins[i] = new FileInputStream(file);
            }
        } 
        ...
        return new Snapshot(key, entry.sequenceNumber, files, ins, entry.lengths);
    }

In the get method, first get the corresponding Entry according to the key, then get the corresponding file to open the input stream, and then pass in to Snapshot.
In snapshot.getFile

/** Returns file with the value for {@code index}. */
        public File getFile(int index) {
            return files[index];
        }

The corresponding file is returned.

BaseDiskCache

BaseDiskCache also directly implements the DiskCache method, which is relatively simple to implement.

public abstract class BaseDiskCache implements DiskCache {
    ...
    protected final File cacheDir;
    protected final File reserveCacheDir;

    protected final FileNameGenerator fileNameGenerator;

    public BaseDiskCache(File cacheDir, File reserveCacheDir, FileNameGenerator fileNameGenerator) {
        ...
        this.cacheDir = cacheDir;
        this.reserveCacheDir = reserveCacheDir;
        this.fileNameGenerator = fileNameGenerator;
    }

    @Override
    public boolean save(String imageUri, Bitmap bitmap) throws IOException {
        File imageFile = getFile(imageUri);
        File tmpFile = new File(imageFile.getAbsolutePath() + TEMP_IMAGE_POSTFIX);
        OutputStream os = new BufferedOutputStream(new FileOutputStream(tmpFile), bufferSize);
        boolean savedSuccessfully = false;
        try {
            savedSuccessfully = bitmap.compress(compressFormat, compressQuality, os);
        } finally {
            IoUtils.closeSilently(os);
            if (savedSuccessfully && !tmpFile.renameTo(imageFile)) {
                savedSuccessfully = false;
            }
            if (!savedSuccessfully) {
                tmpFile.delete();
            }
        }
        bitmap.recycle();
        return savedSuccessfully;
    }

    @Override
    public File get(String imageUri) {
        return getFile(imageUri);
    }

    protected File getFile(String imageUri) {
        String fileName = fileNameGenerator.generate(imageUri);
        File dir = cacheDir;
        if (!cacheDir.exists() && !cacheDir.mkdirs()) {
            if (reserveCacheDir != null && (reserveCacheDir.exists() || reserveCacheDir.mkdirs())) {
                dir = reserveCacheDir;
            }
        }
        return new File(dir, fileName);
    }

Simple, open and retrieve according to the corresponding file.Neither LimitedAgeDiskCache nor UnlimitedDiskCache, its two subclasses, expanded.

3. Source Code Resolution of Universal-Image-Loader Resolution

When we configure ImageConfiguration and ImageLoader, we start calling

ImageLoader.getInstance().loadImage(...);   
ImageLoader.getInstance().displayImage(...);

One of these two methods is to display the picture.
First look at loadImage

public void loadImage(String uri, ImageSize targetImageSize, DisplayImageOptions options,
            ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
        checkConfiguration();
        if (targetImageSize == null) {
            targetImageSize = configuration.getMaxImageSize();
        }
        if (options == null) {
            options = configuration.defaultDisplayImageOptions;
        }

        NonViewAware imageAware = new NonViewAware(uri, targetImageSize, ViewScaleType.CROP);
        displayImage(uri, imageAware, options, listener, progressListener);
    }

First checkConfiguration was called to determine if ImageLoaderConfiguration was initialized
If the size of the ImageView is set, then it is set, but not the default Configuration size.
Set the last default options if DisplayImageOptions is not set
Then create a NonViewAware and call displayImage.
That is, the loadImage is ultimately called to displayImage.

ImageAware

Here NonViewAware implements the ImageAware interface.Let's start with a structure diagram

ImageAware is an interface that provides a series of internal ways to manipulate pictures.
For NonViewAware, it simply saves the necessary data inside the picture, such as its size, size, URI, ScaleType.Primarily encapsulated as ImageAware to call displayImage.

See how displayImage is used

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

This encapsulates ImageView as ImageViewAware and then calls displayImage, just like loadImage.
Here ImageViewAware inherits from ViewAware, which implements the ImageAware interface.
Unlike NonViewAware, ViewAware holds a member variable of Reference <View> viewRef internally, which is used to save a weak reference to the current ImageView so that the display picture can be set directly later.
Many methods of ViewAware depend on this View

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

You can then set the display in ImageViewAware.

Well, look back at the method they eventually called.
It's a bit long, so let's split it up and see

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;
        }
        ...//Look at the next section
    }

First check to see if there is an initialization setting ImageLoaderConfiguration, then throw an exception, and set a default value if there is no listener and DisplayImageOptions.

TextUtils.isEmpty(uri) is then called to determine if the current URI is empty
engine.cancelDisplayTaskFor(imageAware);
It is also understandable to start and end with a listener notification, mainly this engine.

This engine is ImageLoaderEngine and is primarily used to display a class of loaded pictures.
There is a HashMap in ImageLoaderEngine that records the task being loaded. When loading a picture, the id of the ImageView and the url of the image are added to the HashMap with dimensions and removed when loading is complete.

Next, look below

public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
            ImageSize targetSize, ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
        ...//Previous part
        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);
            }
        } 
        ...//Next Section
    }

Load the display when the URI is not empty.First get the only Key corresponding to the URI according to the uri, then call engine.prepareDisplayTaskFor (imageAware, memory CacheKey); record the currently loaded task, turn on the listener's start callback, and then call Bitmap BMP = configuration.memory Cache.get (memory CacheKey); to get the pictures in the memory cache, where the default memory cache is LruMemoryCache, beforeThe article is analyzed.

If the corresponding Bitmap exists in the cache, enter if
We enter the true logic if we set postProcessor in DisplayImageOptions, but the default postProcessor is null, and the BitmapProcessor interface mainly handles Bitmap. This framework does not give a corresponding implementation. If we have our own requirements, we can implement the BitmapProcessor interface by ourselves (for example, by setting the picture to a circle).

Then to line 27
Set Bitmap on ImageView, where we can configure the display requirement displayer in DisplayImageOptions. By default, Simple BitmapDisplayer is used. Set Bitmap directly on ImageView. We can configure other display logic. Here he provides FadeInBitmapDisplayer (transparency from 0-1) RoundedBitmapDisplayer (four corners are circular arcs), etc.It then calls back to the ImageLoadingListener interface.

We know that the difference between loadImage and displayImage is that loadImage relies on the returned Bitmap to set the display, while displayImage displays directly.The loadImage ultimately calls displayImage because of this display and imageAware

public final class SimpleBitmapDisplayer implements BitmapDisplayer {
    @Override
    public void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom) {
        imageAware.setImageBitmap(bitmap);
    }
}

ImageAware for loadImage is a method that NonImageAware does not handle setImageBitmap, whereas ImageViewAware for displayImage does.

Okay, go ahead, when the Bitmap retrieved from the memory cache is empty

public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
            ImageSize targetSize, ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
            ...//The first two parts
        //If Bitmap is empty
        } 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);
            }
        }
    }

If you need to set the display to show the loading picture, set the display.
ImageLoadingInfo is a class that loads task information for displaying pictures.
From it, a LoadAndDisplayImageTask class is created that implements Runnable.
If isSyncLoading is configured to true, execute the run method of LoadAndDisplayImageTask directly, indicating synchronization, defaulting to false, submitting LoadAndDisplayImageTask to the thread pool object

Next, let's look at run() of LoadAndDisplayImageTask. This class is still quite complex. Let's go through a series of analyses.

@Override
    public void run() {
        if (waitIfPaused()) return;
        if (delayIfNeed()) return;

        ...
    }

If waitIfPaused(), delayIfNeed() returns true, it returns directly from the run() method without executing the following logic. Let's first 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();
    }

What's the use of this method? It's mainly when we use ListView, GridView to load pictures. Sometimes in order to glide smoother, we choose not to load pictures when our fingers are sliding or sliding sharply. So what's the use of this method?The PauseOnScrollListener class is used here. With the very simple ListView.setOnScrollListener (new PauseOnScrollListener), pauseOnScroll controls whether we slowly slide ListView, whether GridView stops loading pictures, whether pauseOnFling stops sliding ListView aggressively, and whether GridView stops loading pictures.

Let's see how this PauseOnScrollListener handles

@Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        switch (scrollState) {
            case OnScrollListener.SCROLL_STATE_IDLE:
                imageLoader.resume();
                break;
            case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
                if (pauseOnScroll) {
                    imageLoader.pause();
                }
                break;
            case OnScrollListener.SCROLL_STATE_FLING:
                if (pauseOnFling) {
                    imageLoader.pause();
                }
                break;
        }
        if (externalListener != null) {
            externalListener.onScrollStateChanged(view, scrollState);
        }
    }

ImgeLoader.pause is called when sliding stops

public void pause() {
        engine.pause();
    }
...
void pause() {
        paused.set(true);
    }
//Here pause is
private final AtomicBoolean paused = new AtomicBoolean(false);

So calling pause.get returns true.

In addition, the return value of this method is determined by isTaskNotActual(), so let's look at the source code for isTaskNotActual().

private boolean isTaskNotActual() {
        return isViewCollected() || isViewReused();
    }

isViewCollected() is a judgment whether our ImageView is recycled by the garbage collector. If it is recycled, run() of the LoadAndDisplayImageTask method returns directly. isViewReused() determines whether the ImageView is reused and that the reused run() method returns directly. Why use isViewReused() method?Mainly ListView, GridView reuses item objects. If we load the ListView first page's pictures first, we scroll quickly before the first page's pictures are fully loaded. The isViewReused() method avoids these invisible items from loading pictures and loads the pictures of the current interface directly.

Looking back at the run method

@Override
    public void run() {
        ...
        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);
    }

The fourth line of code has a loadFromUriLock, which is a lock, and the method to get 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;  
    }  

As you can see from the above, this lock object corresponds to the URL of the picture. Why do you want to do this?Okay, you still don't know if you've considered a scene. If an item is in the process of getting a picture in a ListView, and we roll the item out of the interface then roll it in again. If there is no lock on it, the item will load the picture again, assuming that it scrolls very frequently in a very short period of time, then it willThere are multiple requests for pictures on the network, so here a ReentrantLock object is corresponding to the Url of the picture, so requests with the same Url will wait at line 10. When the picture is loaded, ReentrantLock is released and just those requests with the same Url will continue to execute the code below line 10.

Then on line 13, call checkTaskNotActual to determine if the current View is being recycled by the GC, and throw an exception.
The next 15 rows are fetched from the memory cache once, and if the following logic is not being executed in the memory cache, ReentrantLock is designed to avoid repeatedly requesting pictures from the network in this case.

The 17-line method tryLoadBitmap(), which is really a bit long, let me tell you first that the logic here is to get if there are Bitmap objects from the file cache, if not from the network, and then save the bitmap in the file system. Let's do some analysis

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);
                }
            }
        }
        ...
        return bitmap;
    }

The first line goes to the disk cache to get the picture. If the picture is already saved on disk, get the corresponding File path directly, call bitmap = decodeImage (Scheme.FILE.wrap (imageFile.getAbsolutePath()); and parse.

If it's not on disk, it's 12 lines and you're starting a network download.
At line 17, isCacheOnDisk is called to determine whether to keep it on disk, if the default is false, and if so, tryCacheImageOnDisk is called to download the picture and keep it on disk

private boolean tryCacheImageOnDisk() throws TaskCancelledException {
        L.d(LOG_CACHE_IMAGE_ON_DISK, memoryCacheKey);

        boolean loaded;
        try {
            loaded = downloadImage();
            ...
        } ...
        return loaded;
    }

Called downloadImage to download pictures

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

You can see that getDownloader().getStream is called here to download. It will not be expanded here, and you will see later
After downloading, it is saved to disk.
Before you come back

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

Here is a String variable, imageUriForDecoding, whose initial value is uri. If a disk cache is set, tryCacheImageOnDisk is called to download and keep the picture, while imageUriForDecoding is the path to the File file File.

ImgeUriForDecoding or uri if no disk cache is set.
The key is decodeImage, which loads pictures according to their uri.

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

Encapsulate the imageUri passed in (either the uri of the file or the uri of the image) into ImageDecodingInfo for parsing.
Here decoder is ImageDecode, and its default implementation class is BaseImageDecode

@Override
    public Bitmap decode(ImageDecodingInfo decodingInfo) throws IOException {
        Bitmap decodedBitmap;
        ImageFileInfo imageInfo;

        InputStream imageStream = getImageStream(decodingInfo);
        ...
}

Get input stream from getImageStream

protected InputStream getImageStream(ImageDecodingInfo decodingInfo) throws IOException {
        return decodingInfo.getDownloader().getStream(decodingInfo.getImageUri(), decodingInfo.getExtraForDownloader());
    }

The default implementation class for Downloader here is BaseImageDownloader

    @Override
    public InputStream getStream(String imageUri, Object extra) throws IOException {
        switch (Scheme.ofUri(imageUri)) {
            case HTTP:
            case HTTPS:
                return getStreamFromNetwork(imageUri, extra);
            case FILE:
                return getStreamFromFile(imageUri, extra);
            case CONTENT:
                return getStreamFromContent(imageUri, extra);
            case ASSETS:
                return getStreamFromAssets(imageUri, extra);
            case DRAWABLE:
                return getStreamFromDrawable(imageUri, extra);
            case UNKNOWN:
            default:
                return getStreamFromOtherSource(imageUri, extra);
        }
    }

You can see that there are many cases of read judgment that have been made here.The first article explains that UIL s can parse pictures based on different uri s, and that's why.
The previous download of pictures via tryCacheImageOnDisk was also based on this.This does not expand one by one.
Inside the web download pictures here are downloaded using HttpUrlConnection.

Back to the run method of the first LoadAndDisplayImageTask, when we get Bitmap, we get

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

These two codes are a display task
Look directly at the run() method of the DisplayBitmapTask class

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

Callback to the ImageLoadingListener interface if the ImageView is recycled or reused, otherwise call BitmapDisplayer to display Bitmap.At this point Bitmap has already shown that loading is complete and engine is called to remove the picture display task.

Of course at the top

public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
            ImageSize targetSize, ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
            ...
            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);
            }
        }
    }

If the display loading is asynchronous at this time, it is handled by the Executor thread pool of engine, which ultimately calls the run method of LoadAndDisplayImageTask to load the display.

The analysis of Universal-Image-Loader is finished here. From basic usage to memory model in load display, you can see that UIL is a flexible open source framework, such as builder mode, decoration mode, agent mode, strategy mode, etc. This makes it easy for us to expand, realize the functions we want, and, of course, give us more room for imagination.

Original Link: https://www.jianshu.com/p/cff58eddb4ae
Ali P7 Mobile Internet Architect Advanced Video (Updated Daily) Free Learning Click: https://space.bilibili.com/474380680

Topics: Android snapshot network Mobile