A Bitmap is a Bitmap made up of pixels. Bitmaps are compressed in JPG and PNG formats. When JPG and PNG files need to be displayed on the controls on the phone, they are parsed into bitmaps and drawn to the View. In general, you want to avoid excessive memory usage when processing images, since memory on mobile devices is limited. So how much memory does it take to load an image? What is the caching strategy for loading images in terms of efficiency?

I. Loading of Bitmap

1.1 Bitmap Memory Usage

The original calculation formula is as follows:

Bitmap memory = resolution * pixel size

  • Image resolution may not be the resolution of the original image. For example, pictures are stored in folders of different DPI in Res, and the resolution is converted from the original resolution. For example, hdPI and XHDPI, the resolution after conversion is different. Converted resolution = original resolution * (DPI of the device/DPI of the directory). Other cases, such as disk, file, stream, etc., are processed according to the original resolution. In addition, this logic is native system BitmapFactory logic, if you use the image library directly, the internal logic may not convert the resolution, such as Glide does not convert the Res image resolution.
  • The pixel sizes are ARGB8888 (4B) and RGB565 (2B).

How to calculate the memory size of an image in Android

1.2 Efficient loading of Bitmap

Bitmap loading, through the system provided by the BitmapFactory four methods: decodeFile, decodeResource, decodeStream, decodeByteArray, corresponding processing from the file, resources, stream, byte array to load Bitmap.

How do you optimize loading? As can be seen from the formula, there are two methods to reduce the memory used when loading images into bitmaps:

  • Reduce the pixel size: If you can change the image format from ARGB8888 to RGB565, the memory footprint will be reduced by half, but it will be reduced. But it does not support transparency and reduces picture quality. Open source libraries generally support alternate formats as well.
  • Reduce resolution: usually the resolution of the picture is much larger than the resolution of the control view, after loading the view cannot display the original resolution, so reducing the resolution will not affect the display effect of the picture.

Bitmapfactory. Options also provides a method for reducing resolution as follows:

  1. The BitmapFactory. Options. InJustDecodeBounds is set to true, and loading pictures. (Only load original width and height information, lightweight operation)
  2. Get the original width and height information: options.outWidth, options.outHeight
  3. Set the sampling rate options.inSampleSize. The sampling rate is calculated based on the original width and height information and the size of the view.
  4. The BitmapFactory. Options. InJustDecodeBounds set to false, and loading pictures.

The code is as follows:

    private void initView(a) {
        // r.map. blue is placed in XXH (480dpi) of Res. The test phone dPI is also 480

        //1, inJustDecodeBounds set to true and load the image
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(getResources(), R.mipmap.blue, options);

        // get the original width and height information
        int outWidth = options.outWidth;
        int outHeight = options.outHeight;

        Log.i(TAG, "initView: outWidth="+outWidth+", outHeight ="+outHeight);

        //3. Calculate the size of the original width and height information and view and set the sampling rate
        ViewGroup.LayoutParams layoutParams = ivBitamp.getLayoutParams();
        int inSampleSize = getInSampleSize(layoutParams.width, layoutParams.height, outWidth, outHeight);
        options.inSampleSize = inSampleSize;

        Log.i(TAG, "initView: inSampleSize="+options.inSampleSize);

        // Set inJustDecodeBounds to false and load images
        options.inJustDecodeBounds = false;
        Bitmap bitmap= BitmapFactory.decodeResource(getResources(), R.mipmap.blue, options);

        Log.i(TAG, "initView: size="+bitmap.getByteCount());

        int density = bitmap.getDensity();
        Log.i(TAG, "initView: density="+density);
        Log.i(TAG, "initView: original size="+337*222*4);
        Log.i(TAG, "initView: calculated size="+ (337/inSampleSize) *(222/inSampleSize)* density/480 *4);

        // Draw to view
        ivBitamp.setImageBitmap(bitmap);
    }

    /** * Calculate the sampling rate *@paramWidth View *@paramHeight View height *@paramOutWidth Image original width *@paramOutHeight image original high *@return* /
    private int getInSampleSize(int width, int height, int outWidth, int outHeight) {
        int inSampleSize = 1;
        if (outWidth>width || outHeight>height){
            int halfWidth = outWidth / 2;
            int halfHeight = outHeight / 2;
            // Ensure that the width and height after sampling are not less than the target fast height, otherwise it will stretch and blur
            while (halfWidth/inSampleSize >=width
                    && halfHeight/inSampleSize>=height){
                // The sampling rate is usually an exponent of 2
                inSampleSize *=2; }}returninSampleSize; }}Copy the code

Caching strategy in Android

Caching policies are widely used in Android. Using caching can save traffic and improve efficiency.

When an image is loaded, it is typically loaded from the network and cached on a storage device so that you don’t have to request the network the next time. It also usually caches a copy into memory, so that next time you can fetch it directly from memory, which is much faster than retrieving it from the storage device. Therefore, it is usually fetched from the memory first, and the storage device is fetched when there is no memory, and the network is requested only when there is no memory. This is the so-called “three-level cache”. This policy applies to other file types as well.

Operations in the cache policy include adding cache, obtaining cache, and deleting cache. Add and get are easier to understand. What does delete cache mean? Because the cache size is limited, such as mobile device memory and device storage are limited, can not be added without limit, can only limit a maximum cache, when the maximum will be deleted part of the cache. But which part of the cache is deleted? Delete cache is the oldest, and if it’s used a lot, it’s not perfect, but it’s also a caching algorithm.

At present, the classic cache algorithm is LRU (Least Recently Used), which is Used Least Recently. When the cache is full, the least recently used caches are deleted first. There are two kinds of cache using LRU algorithm, LruCache and DiskLruCache. LruCache uses memory cache, and DiskLruCache implements disk cache.

2.1 LruCache

LruCache is a generic class and can be used as follows: to provide the maximum cache capacity, create an instance of LruCache and override its sizeOf method to calculate the sizeOf the cached object. The maximum size must be the same as the size of the cache object.

    private void testLruCache(a) {
        // Maximum memory of the current process, in M
        long maxMemory = Runtime.getRuntime().maxMemory() / 1024 / 1024;
        // Take 1/8 of the process memory
        int cacheMaxSize = (int) (maxMemory/8);
        // Create a Bitmap instance
        mBitmapLruCache = new LruCache<String, Bitmap>(cacheMaxSize){
            @Override
            protected int sizeOf(String key, Bitmap value) {
                // The size of the cache object bitmap, in M
                return value.getByteCount()/1024/1024;
            }

            @Override
            protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
                // is called when old caches are removed, where you can do things like recycle.
                // Evicted is true, which means it is being removed because it is almost full to make room}};// Add cache
        mBitmapLruCache.put("1",mBitmap);

        // Get the cache
        Bitmap bitmap = mBitmapLruCache.get("1");
        ivBitamp.setImageBitmap(bitmap);

        // Delete the cache, usually not used, because it will automatically delete the least recently used cache when it is almost full, which is its core function
        mBitmapLruCache.remove("1");
    }
Copy the code

So how does LruCache remove “least recently used”? Look at the code for LruCache:

public class LruCache<K.V> {
	// This map stores cached objects with strong references
    private final LinkedHashMap<K, V> map;
    // Current cache size (in units)
    private int size;
    // Maximum cache size (in units)
    private intmaxSize; .public LruCache(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        this.maxSize = maxSize;
        //LinkedHashMap is sorted by access order, so get and put put the k-V to be stored at the end of the queue
        this.map = new LinkedHashMap<K, V>(0.0.75 f.true);
    }
    
    /** * gets the cache and puts the k-v at the end of the list */
    public final V get(K key) {
        if (key == null) {
            throw new NullPointerException("key == null");
        }

        V mapValue;
        // Get is thread-safe
        synchronized (this) {
        	// afterNodeAccess is moved to the end of the LinkedHashMap
            mapValue = map.get(key);
            if(mapValue ! =null) {
                hitCount++;
                returnmapValue; } missCount++; }... }/** * cache key-value, value will exist at the end of the queue *@returnThe same key used to store value */
    public final V put(K key, V value) {
        if (key == null || value == null) {
        	// Null keys and null values are not allowed
            throw new NullPointerException("key == null || value == null");
        }

        V previous;
        // The put operation is thread safe
        synchronized (this) {
            putCount++;
            size += safeSizeOf(key, value);
            // Strong references are stored in the map (which is not passively reclaimed by the system) and, because it is a LinkedHashMap, are placed at the end of the queue
            previous = map.put(key, value);
            if(previous ! =null) {
            	// If you already have this key, adjust the current cache size after replacing itsize -= safeSizeOf(key, previous); }}if(previous ! =null) {
            entryRemoved(false, key, previous, value);
        }
        // Resize
        trimToSize(maxSize);
        return previous;
    }

    /** * Compare the current cache size and the maximum capacity to determine whether to delete */
    private void trimToSize(int maxSize) {
        while (true) {
            K key;
            V value;
            synchronized (this) {
                if (size < 0|| (map.isEmpty() && size ! =0)) {
                    throw new IllegalStateException(getClass().getName()
                            + ".sizeOf() is reporting inconsistent results!");
                }

                if (size <= maxSize) {
                	// The size does not exceed the maximum
                    break;
                }
                
                // The capacity has reached its maximum
                
                // The last item to be traversed is the one that has not been accessed recently.
                Map.Entry<K, V> toEvict = null;
                for (Map.Entry<K, V> entry : map.entrySet()) {
                    toEvict = entry;
                }
                // END LAYOUTLIB CHANGE

                if (toEvict == null) {
                    break;
                }

                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);
                size -= safeSizeOf(key, value);
                evictionCount++;
            }
			// The first argument to this callback is true to make room
            entryRemoved(true, key, value, null); }}protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}... }Copy the code

From the above code and comments, it can be seen that LruCache’s algorithm is implemented by setting the access order of the LinkedHashMap. Because access is sequential, both get and PUT adjust k-V to the end of the list. When the cache is about to be full, the LinkedHashMap is traversed, with the last one not recently used because it is in access order mode, and then deleted.

2.2 DiskLruCache

DiskLruCache implements disk caching, so it requires read and write permissions for device storage. Generally, images are requested from the network and cached to disk, so network permissions are required.

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
Copy the code

DiskLruCache, not officially provided, so needs to introduce dependencies:

implementation 'com. Jakewharton: disklrucache: 2.0.2'
Copy the code
  • DiskLruCache is created using the open method instead of new, passing in the cache directory and the maximum cache capacity.
  • The cache is added through the Editor, the cache object Editor. The key passed in the image URL calls DiskLruCache’s Edit method to get the Editor (which returns null if the cache is being edited) from which the file output stream can be written to the file system.
  • DiskLruCache’s get method can be used to get a SnapShot. SnapShoty can be used to get the file input stream. In this way, BitmapFactory can be used to get a bitmap.
  • The remove method of DiskLruCache can delete the cache corresponding to the key.

By looking at the source code, it is found that LinkedHashMap also maintains the access order of LinkedHashMap, which is consistent with LruCache in principle. It’s just a bit complicated to use, because it involves reading and writing files.

Specific usage and points to note are as follows:

    private void testDiskLruCache(String urlString) {
        long maxSize = 50*1024*1024;

        try {
            // Create DiskLruCache
            // The first parameter is the directory to store, select the external cache directory (this directory will also be deleted if the APP is uninstalled);
            // The second version is usually set to 1; The number of values in the cache node is usually 1.
            // The fourth is the maximum cache capacity
            mDiskLruCache = DiskLruCache.open(getExternalCacheDir(), 1.1, maxSize);

            // Add the cache: 1, through the Editor, the image URL into the key, through the edit method to get the Editor, and then get the output stream, can write to the file system.
            DiskLruCache.Editor editor = mDiskLruCache.edit(hashKeyFormUrl(urlString));
            if(editor ! =null) {
                // Index takes 0 (because valueCount takes 1)
                OutputStream outputStream = editor.newOutputStream(0);
                boolean downSuccess = downloadPictureToStream(urlString, outputStream);
                if (downSuccess) {
                    //2, edit commit, release the editor
                    editor.commit();
                }else {
                    editor.abort();
                }
                // write to file system, check current cache size, then write to filemDiskLruCache.flush(); }}catch (IOException e) {
            e.printStackTrace();
        }

        // Get the cache
        try {
            String key = hashKeyFormUrl(urlString);
            DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
            FileInputStream inputStream = (FileInputStream)snapshot.getInputStream(0);
// Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
// mIvBitamp.setImageBitmap(bitmap);

            // Note that the file input stream is an ordered file stream, and two decodeStreams during sampling affect the file stream's civilian attributes, resulting in the second decode being null
            // To solve this problem, file descriptors can be used
            FileDescriptor fd = inputStream.getFD();

            // Sample loading (bitmap efficient loading)
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds=true;
            BitmapFactory.decodeFileDescriptor(fd,null,options);
            ViewGroup.LayoutParams layoutParams = mIvBitamp.getLayoutParams();
            options.inSampleSize = getInSampleSize(layoutParams.width, layoutParams.height, options.outWidth, options.outHeight);
            options.inJustDecodeBounds = false;
            Bitmap bitmap = BitmapFactory.decodeFileDescriptor(fd, null, options);

            // Store it in the content cache and draw it to the view. (Next time get it from memory cache, if not get it from disk cache, if not get it from network -" level 3 cache ")
            mBitmapLruCache.put(key,bitmap);

            runOnUiThread(new Runnable() {
                @Override
                public void run(a) { mIvBitamp.setImageBitmap(mBitmapLruCache.get(key)); }}); }catch(IOException e) { e.printStackTrace(); }}/** * Download image to file input stream */
    private boolean downloadPictureToStream(String urlString, OutputStream outputStream) {
        URL url = null;
        HttpURLConnection urlConnection = null;
        BufferedInputStream in = null;
        BufferedOutputStream out = null;
        try {
            url = new URL(urlString);
            urlConnection = (HttpURLConnection) url.openConnection();
            in = new BufferedInputStream(urlConnection.getInputStream());
            out = new BufferedOutputStream(outputStream);

            int b;
            while((b=in.read()) ! = -1) {
                // Write the file input stream
                out.write(b);
            }
            return true;
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(urlConnection ! =null) {
                urlConnection.disconnect();
            }
            try {
                if(in ! =null) {in.close(); }if(out ! =null) {out.close();}
            } catch(IOException e) { e.printStackTrace(); }}return false;
    }

    /** * convert the url of the image to the key, use MD5 */
    private String hashKeyFormUrl(String url) {
        try {
            MessageDigest digest = MessageDigest.getInstance("MD5");
            return byteToHexString(digest.digest(url.getBytes()));
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return null;
    }

    private String byteToHexString(byte[] bytes) {
        StringBuffer stringBuffer = new StringBuffer();
        for (int i = 0; i < bytes.length; i++) {
            String hex = Integer.toHexString(0XFF & bytes[i]);
            if (hex.length()==1) {
                stringBuffer.append(0);
            }
            stringBuffer.append(hex);
        }
        return stringBuffer.toString();
    }
Copy the code

Third, ImageLoader

The efficient loading, LruCache and DiskLruCache of Bitmap mentioned above are essential function points of an image loading framework. Let’s wrap an ImageLoader. Let’s start with the main points of the implementation:

  • Image compression is sampling loading
  • Memory cache, LruCache
  • Disk cache, DiskLruCache
  • Network obtain, request the network URL
  • Load synchronously and external child threads execute synchronously
  • Load asynchronously, and the ImageLoader internal thread executes asynchronously

Instructions,

  1. “Level 3 cache” logic: load from memory cache first, if there is a return bitmap drawing image to view, if not from disk cache to get; The disk cache returns the bitmap and caches it to the memory cache if it does not, and requests the network if it does not. When the network request comes back, it is cached to the disk cache and retrieved from the disk cache.
  2. Synchronous loading is performed in the external child thread. There is no open thread inside the synchronous loading method, so the loading process is time-consuming and blocks the external child thread. After the loading is complete, it needs to cut to the main thread and draw to the View.
  3. Asynchronous loading, which can be performed externally on any thread, because the internal implementation is loaded on the child thread (thread pool), and the internal implementation is cut to the main thread by Handler, just passing in the View and drawing the Bitmap directly to the view.

Detailed as follows

public class ImageLoader {

    private static final String TAG = "ImageLoader";

    private static final long KEEP_ALIVE_TIME = 10L;

    private static final int CPU_COUNT =  Runtime.getRuntime().availableProcessors();

    private static final int CORE_THREAD_SIZE = CPU_COUNT + 1;

    private static final int THREAD_SIZE = CPU_COUNT * 2 + 1;

    private static final int VIEW_TAG_URL = R.id.view_tag_url;

    private static final Object object = new Object();


    private ThreadPoolExecutor mExecutor;

    private Handler mMainHandler;


    private Context mApplicationContext;

    private static volatile ImageLoader mImageLoader;

    private LruCache<String, Bitmap> mLruCache;

    private DiskLruCache mDiskLruCache;

    /** * Maximum disk cache capacity,50M */
    private static final long DISK_LRU_CACHE_MAX_SIZE = 50 * 1024 * 1024;

    /** * The maximum memory of the current process, 1/8 of the process memory
    private static final long MEMORY_CACHE_MAX_SIZE = Runtime.getRuntime().maxMemory() / 8;


    public ImageLoader(Context context) {
        if (context == null) {
            throw new RuntimeException("context can not be null !");
        }
        mApplicationContext = context.getApplicationContext();

        initLruCache();
        initDiskLruCache();
        initAsyncLoad();
    }

    public static ImageLoader with(Context context){
        if (mImageLoader == null) {
            synchronized (object) {
                if (mImageLoader == null) {
                    mImageLoader = newImageLoader(context); }}}return mImageLoader;
    }

    private void initAsyncLoad(a) {
        mExecutor = new ThreadPoolExecutor(CORE_THREAD_SIZE, THREAD_SIZE,
                KEEP_ALIVE_TIME, TimeUnit.SECONDS,
                new LinkedBlockingQueue<Runnable>(), new ThreadFactory() {
            private final AtomicInteger count = new AtomicInteger(1);
            @Override
            public Thread newThread(Runnable runnable) {
                return new Thread(runnable, "load bitmap thread "+ count.getAndIncrement()); }}); mMainHandler =new Handler(Looper.getMainLooper()){
            @Override
            public void handleMessage(@NonNull Message msg) {
                LoadResult result = (LoadResult) msg.obj;
                ImageView imageView = result.imageView;
                Bitmap bitmap = result.bitmap;
                String url = result.url;
                if (imageView == null || bitmap == null) {
                    return;
                }

                // This judgment is to avoid ImageView reuse in the list caused by the image mismatch problem
                if (url.equals(imageView.getTag(VIEW_TAG_URL))) {
                    imageView.setImageBitmap(bitmap);
                }else {
                    Log.w(TAG, "HandleMessage: Set image bitmap, but URL has changed,ignore!"); }}}; }private void initLruCache(a) {

        mLruCache = new LruCache<String, Bitmap>((int) MEMORY_CACHE_MAX_SIZE){
            @Override
            protected int sizeOf(String key, Bitmap value) {
                // The size of the cache object bitmap, in units consistent with MEMORY_CACHE_MAX_SIZE
                return value.getByteCount();
            }

            @Override
            protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
                // is called when old caches are removed, where you can do things like recycle.}}; }private void initDiskLruCache(a) {

        File externalCacheDir = mApplicationContext.getExternalCacheDir();
        if(externalCacheDir ! =null) {
            long usableSpace = externalCacheDir.getUsableSpace();
            if (usableSpace < DISK_LRU_CACHE_MAX_SIZE){
                // There is not enough space left
                Log.e(TAG, "initDiskLruCache: "+"UsableSpace="+usableSpace+", not enough(target 50M), cannot creat diskLruCache!);
                return; }}// Create DiskLruCache
            // The first parameter is the directory to store, select the external cache directory (this directory will also be deleted if the APP is uninstalled);
            // The second version is usually set to 1; The number of values in the cache node is usually 1.
            // The fourth is the maximum cache capacity
        try {
            this.mDiskLruCache = DiskLruCache.open(mApplicationContext.getExternalCacheDir(), 1.1, DISK_LRU_CACHE_MAX_SIZE);
        } catch (IOException e) {
            e.printStackTrace();
            Log.e(TAG, "initDiskLruCache: "+e.getMessage()); }}/** * Cache bitmap to memory *@param url url
     * @param bitmap bitmap
     */
    private void addBitmapMemoryCache(String url, Bitmap bitmap) {
        String key = UrlKeyTransformer.transform(url);
        if (mLruCache.get(key) == null&& bitmap ! =null) { mLruCache.put(key,bitmap); }}/** * Load bitmap * from memory cache@param url url
     * @return* /
    private Bitmap loadFromMemoryCache(String url) {
        return mLruCache.get(UrlKeyTransformer.transform(url));
    }


    /** * Load the bitmap from the disk cache (and add it to the memory cache) *@param url url
     * @paramRequestWidth Specifies the required width *@paramRequestHeight requires high *@return bitmap
     */
    private Bitmap loadFromDiskCache(String url, int requestWidth, int requestHeight) throws IOException {
        if (Looper.myLooper()==Looper.getMainLooper()) {
            Log.w(TAG, LoadFromDiskCache from Main Thread may cause block!);
        }

        if (mDiskLruCache == null) {
            return null;
        }
        DiskLruCache.Snapshot snapshot = null;
        String key = UrlKeyTransformer.transform(url);
        snapshot = mDiskLruCache.get(key);
        if(snapshot ! =null) {
            FileInputStream inputStream = (FileInputStream)snapshot.getInputStream(0);

            //Bitmap bitmap = BitmapFactory.decodeStream(inputStream);

            // The file input stream is an ordered file stream. Two decodeStreams during sampling affect the position properties of the file stream.
            // The file descriptor can be used to solve this problem.
            FileDescriptor fd = inputStream.getFD();
            Bitmap bitmap = BitmapSampleDecodeUtil.decodeFileDescriptor(fd, requestWidth, requestHeight);
            addBitmapMemoryCache(url,bitmap);
            return bitmap;
        }

        return null;
    }


    /** * Load images from the network to disk cache (and then load samples from disk) *@param urlString urlString
     * @paramRequestWidth Specifies the required width *@paramRequestHeight requires high *@return Bitmap
     */
    private Bitmap loadFromHttp(String urlString, int requestWidth, int requestHeight) throws IOException {
        // Thread check, not the main thread
        if (Looper.myLooper()==Looper.getMainLooper()) {
            throw new RuntimeException("Do not loadFromHttp from Main Thread!");
        }

        if (mDiskLruCache == null) {
            return null;
        }

        DiskLruCache.Editor editor = null;
        editor = mDiskLruCache.edit(UrlKeyTransformer.transform(urlString));
        if(editor ! =null) {
            OutputStream outputStream = editor.newOutputStream(0);
            if (downloadBitmapToStreamFromHttp(urlString, outputStream)) {
                editor.commit();
            }else {
                editor.abort();
            }
            mDiskLruCache.flush();
        }

        return loadFromDiskCache(urlString, requestWidth, requestHeight);
    }

    /** * Download images from the network to the file input stream *@param urlString
     * @param outputStream
     * @return* /
    private boolean downloadBitmapToStreamFromHttp(String urlString, OutputStream outputStream) {
        URL url = null;
        HttpURLConnection urlConnection = null;
        BufferedInputStream in = null;
        BufferedOutputStream out = null;
        try {
            url = new URL(urlString);
            urlConnection = (HttpURLConnection) url.openConnection();
            in = new BufferedInputStream(urlConnection.getInputStream());
            out = new BufferedOutputStream(outputStream);

            int b;
            while((b=in.read()) ! = -1) {
                // Write the file input stream
                out.write(b);
            }
            return true;
        } catch (IOException e) {
            e.printStackTrace();
            Log.e(TAG, "downloadBitmapToStreamFromHttp,failed : "+e.getMessage());
        }finally {
            if(urlConnection ! =null) {
                urlConnection.disconnect();
            }
            IoUtil.close(in);
            IoUtil.close(out);
        }
        return false;
    }

    /** * Download bitmap directly from network (no caching, no sampling) *@param urlString
     * @return* /
    private Bitmap downloadBitmapFromUrlDirectly(String urlString) {
        URL url;
        HttpURLConnection urlConnection = null;
        BufferedInputStream bufferedInputStream = null;
        try {
            url = new URL(urlString);
            urlConnection = (HttpURLConnection) url.openConnection();
            bufferedInputStream = new BufferedInputStream(urlConnection.getInputStream());
            return BitmapFactory.decodeStream(bufferedInputStream);
        } catch (IOException e) {
            e.printStackTrace();
            Log.e(TAG, "downloadBitmapFromUrlDirectly,failed : "+e.getMessage());
        }finally {
            if(urlConnection ! =null) {
                urlConnection.disconnect();
            }
            IoUtil.close(bufferedInputStream);
        }
        return null;
    }

    public Bitmap loadBitmap(String url){
        return loadBitmap(url,0.0);
    }

    /** * Synchronous loading of bitmap ** cannot be performed on the main thread. When loading, the bitmap is first obtained from the memory cache. If there is one, the bitmap is returned. If there is no one, the bitmap is obtained from the disk cache. * The disk cache returns the bitmap and caches it to the memory cache if it does, and requests the network if it does not; * When the network request comes back, it is cached to the disk cache and retrieved from the disk cache. * *@return Bitmap
     */
    public Bitmap loadBitmap(String url, int requestWidth, int requestHeight){

        Bitmap bitmap = loadFromMemoryCache(url);
        if(bitmap ! =null) {
            Log.d(TAG, "loadBitmap: loadFromMemoryCache, url:"+url);
            return bitmap;
        }

        try {
            bitmap = loadFromDiskCache(url, requestWidth, requestHeight);
        } catch (IOException e) {
            e.printStackTrace();
        }
        if(bitmap ! =null) {
            Log.d(TAG, "loadBitmap: loadFromDiskCache, url:"+url);
            return bitmap;
        }

        try {
            bitmap = loadFromHttp(url, requestWidth, requestHeight);
        } catch (IOException e) {
            e.printStackTrace();
        }
        if(bitmap ! =null){
            Log.d(TAG, "loadBitmap: loadFromHttp, url:"+url);
            return bitmap;
        }

        if (mDiskLruCache == null) {
            Log.d(TAG, "LoadBitmap: diskLruCache is null, load bitmap from url directly!");
            bitmap = downloadBitmapFromUrlDirectly(url);
        }

        return bitmap;
    }

    public void loadBitmapAsync(final String url, final ImageView imageView){
        loadBitmapAsync(url,imageView,0.0);
    }

    /** * Asynchronously loading a bitmap * External execution can be performed on any thread, since the internal implementation is loaded on the child thread (thread pool), * and the internal implementation is cut to the main thread by Handler, just passing in the View and drawing the bitmap directly into the view. *@param url
     * @param imageView
     * @param requestWidth
     * @param requestHeight
     */
    public void loadBitmapAsync(final String url, final ImageView imageView, final int requestWidth, final int requestHeight){
        if (url == null || url.isEmpty() || imageView == null) {
            return;
        }

        // Marks the url of the image to draw from the current imageView
        imageView.setTag(VIEW_TAG_URL, url);

        mExecutor.execute(new Runnable() {
            @Override
            public void run(a) {

                Bitmap loadBitmap = loadBitmap(url, requestWidth, requestHeight);

                Message message = Message.obtain();
                message.obj = newLoadResult(loadBitmap, url, imageView); mMainHandler.sendMessage(message); }}); }}Copy the code
/** * Bitmap sampling compression loading tool *@author hufeiyang
 */
public class BitmapSampleDecodeUtil {

    private static final String TAG = "BitmapSampleDecodeUtil";

    /** * Sample the resource image *@param resources resources
     * @paramResourcesId Resource ID *@paramRequestWidth View width *@paramRequestHeight View's height *@returnSampled bitmap */
    public static Bitmap decodeSampleResources(Resources resources, int resourcesId, int requestWidth, int requestHeight){
        if (resources == null || resourcesId<=0) {
            return null;
        }

        //1, inJustDecodeBounds set to true and load the image
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(resources, resourcesId, options);

        // get the original width and height information
        int outWidth = options.outWidth;
        int outHeight = options.outHeight;

        //3. Calculate the size of the original width and height information and view and set the sampling rate
        options.inSampleSize = getInSampleSize(requestWidth, requestHeight, outWidth, outHeight);

        // Set inJustDecodeBounds to false and load images
        options.inJustDecodeBounds = false;

        return BitmapFactory.decodeResource(resources, resourcesId, options);
    }

    /** * Sample loading of file descriptors *@param fileDescriptor fileDescriptor
     * @paramRequestWidth View width *@paramNote that the file input stream is an ordered file stream, and the two decodeStreams sampled affect the file stream's civilian attributes, resulting in the second decode being null. * To solve this problem, use this method to load the file descriptor of the file stream. * /
    public static Bitmap decodeFileDescriptor(FileDescriptor fileDescriptor, int requestWidth, int requestHeight){

        if (fileDescriptor == null) {
            return null;
        }

        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds=true;
        BitmapFactory.decodeFileDescriptor(fileDescriptor,null,options);
        options.inSampleSize = getInSampleSize(requestWidth, requestHeight, options.outWidth, options.outHeight);
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
    }

    /** * Calculate the sampling rate *@paramWidth View *@paramHeight View height *@paramOutWidth Image original width *@paramOutHeight image original high *@return* /
    private static int getInSampleSize(int width, int height, int outWidth, int outHeight) {
        int inSampleSize = 1;

        if (width==0 || height ==0) {return inSampleSize;
        }

        if (outWidth>width || outHeight>height){
            int halfWidth = outWidth / 2;
            int halfHeight = outHeight / 2;
            // Ensure that the width and height after sampling are not less than the target fast height, otherwise it will stretch and blur
            while (halfWidth/inSampleSize >=width
                    && halfHeight/inSampleSize>=height){
                inSampleSize *=2;
            }
        }

        Log.d(TAG, "getInSampleSize: inSampleSize="+inSampleSize);
        returninSampleSize; }}Copy the code
/** * the url of the image is changed to key *@author hufeiyang
 */
public class UrlKeyTransformer {

    /** * the url of the image is changed to key *@param url
     * @returnMD5 converted key */
    public static String transform(String url) {
        if (url == null || url.isEmpty()) {
            return null;
        }
        try {
            MessageDigest digest = MessageDigest.getInstance("MD5");
            return byteToHexString(digest.digest(url.getBytes()));
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return null;
    }

    private static String byteToHexString(byte[] bytes) {
        StringBuffer stringBuffer = new StringBuffer();
        for (int i = 0; i < bytes.length; i++) {
            String hex = Integer.toHexString(0XFF & bytes[i]);
            if (hex.length()==1) {
                stringBuffer.append(0);
            }
            stringBuffer.append(hex);
        }
        returnstringBuffer.toString(); }}Copy the code
public class LoadResult {

    public Bitmap bitmap;

    /** * The url of the bitmap */
    public String url;

    public ImageView imageView;


    public LoadResult(Bitmap bitmap, String url, ImageView imageView) {
        this.bitmap = bitmap;
        this.url = url;
        this.imageView = imageView; }}Copy the code

GitHub address: SimpleImageLoader

If you like this article, please help to like, bookmark and share it. Thanks!

Welcome to my public account: