A, definitions,

The Open Closed Principle: Objects (classes, modules, functions, etc.) in software should be Open to extension, but Closed to modification.

Second, the use of scenes

Looking back at the image loader code in the Single Responsibility Principle of Android Design Mode, the memory cache does solve the problem of loading images from the network each time, but when the application restarts, the original loaded images are lost and have to be redownloaded, resulting in slow loading and traffic consumption.

At this time, Xiao Ming considers the introduction of SDCard cache, so that the downloaded pictures will be cached to the local, even if the application restarts, there is no need to re-download.

So Xiao Ming started coding.

/** * Image loader */
public class ImageLoader {

    // Image cache
    private final ImageLruCache imageLruCache = new ImageLruCache();
    / / SDCard cache
    private final ImageSDCardCache imageSDCardCache = new ImageSDCardCache();
    // Thread pool, the number of threads is the number of CPUS
    private final ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    //Ui Handler
    private final Handler uiHandler = new Handler(Looper.getMainLooper());
    // Whether to use SDCard cache
    private boolean useSDCardCache = false;

    /** * Set whether to use SDCard cache **@param useSDCardCache
     */
    public void setUseSDCardCache(boolean useSDCardCache) {
        this.useSDCardCache = useSDCardCache;
    }

    /** * Show pictures **@param imageUrl
     * @param imageView
     */
    public void displayImage(String imageUrl, ImageView imageView) {
        Bitmap bitmap = useSDCardCache ? imageSDCardCache.get(imageView.getContext(), imageUrl) : imageLruCache.get(imageUrl);
        if(bitmap ! =null) {
            imageView.setImageBitmap(bitmap);
            return;
        }
        imageView.setTag(imageUrl);
        executorService.submit(new Runnable() {
            @Override
            public void run(a) {
                Bitmap bitmap = downloadImage(imageUrl);
                if(bitmap ! =null) {
                    if (imageView.getTag().equals(imageUrl)) {
                        postToImageView(imageView, bitmap);
                    }
                    if (useSDCardCache) {
                        imageSDCardCache.put(imageView.getContext(), imageUrl, bitmap);
                    } else{ imageLruCache.put(imageUrl, bitmap); }}}}); }/** * Update to ImageView **@param imageView
     * @param bitmap
     */
    private void postToImageView(ImageView imageView, Bitmap bitmap) {
        uiHandler.post(new Runnable() {
            @Override
            public void run(a) { imageView.setImageBitmap(bitmap); }}); }/** * Download the image **@param imageUrl
     * @return* /
    private Bitmap downloadImage(String imageUrl) {
        HttpURLConnection connection = null;
        try {
            URL url = new URL(imageUrl);
            connection = (HttpURLConnection) url.openConnection();
            if (connection.getResponseCode() == 200) {
                // Ignore the image compression process...
                returnBitmapFactory.decodeStream(connection.getInputStream()); }}catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(connection ! =null) { connection.disconnect(); }}return null; }}/** * Image cache */
public class ImageLruCache {

    // Image cache
    private final LruCache<String, Bitmap> imageLruCache;

    public ImageLruCache(a) {
        // The maximum memory available
        final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        // Use 1/4 of the available memory as the image cache
        final int imageLruCacheSize = maxMemory / 4;
        imageLruCache = new LruCache<String, Bitmap>(imageLruCacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getRowBytes() * value.getHeight() / 1024; }}; }/** * Save the image **@param imageUrl
     * @param bitmap
     */
    public void put(String imageUrl, Bitmap bitmap) {
        imageLruCache.put(imageUrl, bitmap);
    }

    /** * Take out pictures **@param imageUrl
     * @return* /
    public Bitmap get(String imageUrl) {
        returnimageLruCache.get(imageUrl); }}/** * SDCard cache */
public class ImageSDCardCache {

    public Bitmap get(Context context, String imageUrl) {
        return BitmapFactory.decodeFile(context.getCacheDir().getAbsolutePath() + imageUrl);
    }

    public void put(Context context, String imageUrl, Bitmap bitmap) {
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(context.getCacheDir().getAbsolutePath() + imageUrl);
            bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(fos ! =null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
Copy the code

The setUseSDCardCache method allows the user to set different caches. Xiao Ming is satisfied with this and submits it to the supervisor for code review.

Director: The idea is right, but there is still a problem, is the use of memory cache users can not use SDCard cache. What users need is a combination of the two strategies, first using the memory cache first, then using the SDCard cache if there is no image in the memory cache, and then obtaining the image from the network if there is no image in the SDCard cache.

Xiaoming (brain as if waking up from a dream) : I will optimize right away.

So Xiao Ming started coding.

/** * Image loader */
public class ImageLoader {

    // Image cache
    private final ImageLruCache imageLruCache = new ImageLruCache();
    / / SDCard cache
    private final ImageSDCardCache imageSDCardCache = new ImageSDCardCache();
    / / double cache
    private final DoubleCache doubleCache = new DoubleCache();
    // Thread pool, the number of threads is the number of CPUS
    private final ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    //Ui Handler
    private final Handler uiHandler = new Handler(Looper.getMainLooper());
    // Whether to use SDCard cache
    private boolean useSDCardCache = false;
    // Whether to use dual cache
    private boolean useDoubleCache = false;

    /** * Set whether to use SDCard cache **@param useSDCardCache
     */
    public void setUseSDCardCache(boolean useSDCardCache) {
        this.useSDCardCache = useSDCardCache;
    }

    /** whether to use double cache **@param useDoubleCache
     */
    public void setUseDoubleCache(boolean useDoubleCache) {
        this.useDoubleCache = useDoubleCache;
    }

    /** * Show pictures **@param imageUrl
     * @param imageView
     */
    public void displayImage(String imageUrl, ImageView imageView) {
        Bitmap bitmap = null;
        if (useDoubleCache) {
            bitmap = doubleCache.get(imageView.getContext(), imageUrl);
        } else if (useSDCardCache) {
            bitmap = imageSDCardCache.get(imageView.getContext(), imageUrl);
        } else {
            bitmap = imageLruCache.get(imageUrl);
        }
        if(bitmap ! =null) {
            imageView.setImageBitmap(bitmap);
            return;
        }
        imageView.setTag(imageUrl);
        executorService.submit(new Runnable() {
            @Override
            public void run(a) {
                Bitmap bitmap = downloadImage(imageUrl);
                if(bitmap ! =null) {
                    if (imageView.getTag().equals(imageUrl)) {
                        postToImageView(imageView, bitmap);
                    }
                    if (useDoubleCache) {
                        doubleCache.put(imageView.getContext(), imageUrl, bitmap);
                    } else if (useSDCardCache) {
                        imageSDCardCache.put(imageView.getContext(), imageUrl, bitmap);
                    } else{ imageLruCache.put(imageUrl, bitmap); }}}}); }/** * Update to ImageView **@param imageView
     * @param bitmap
     */
    private void postToImageView(ImageView imageView, Bitmap bitmap) {
        uiHandler.post(new Runnable() {
            @Override
            public void run(a) { imageView.setImageBitmap(bitmap); }}); }/** * Download the image **@param imageUrl
     * @return* /
    private Bitmap downloadImage(String imageUrl) {
        HttpURLConnection connection = null;
        try {
            URL url = new URL(imageUrl);
            connection = (HttpURLConnection) url.openConnection();
            if (connection.getResponseCode() == 200) {
                // Ignore the image compression process...
                returnBitmapFactory.decodeStream(connection.getInputStream()); }}catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(connection ! =null) { connection.disconnect(); }}return null; }}/** * Image cache */
public class ImageLruCache {

    // Image cache
    private final LruCache<String, Bitmap> imageLruCache;

    public ImageLruCache(a) {
        // The maximum memory available
        final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        // Use 1/4 of the available memory as the image cache
        final int imageLruCacheSize = maxMemory / 4;
        imageLruCache = new LruCache<String, Bitmap>(imageLruCacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getRowBytes() * value.getHeight() / 1024; }}; }/** * Save the image **@param imageUrl
     * @param bitmap
     */
    public void put(String imageUrl, Bitmap bitmap) {
        imageLruCache.put(imageUrl, bitmap);
    }

    /** * Take out pictures **@param imageUrl
     * @return* /
    public Bitmap get(String imageUrl) {
        returnimageLruCache.get(imageUrl); }}/** * SDCard cache */
public class ImageSDCardCache {

    public Bitmap get(Context context, String imageUrl) {
        return BitmapFactory.decodeFile(context.getCacheDir().getAbsolutePath() + imageUrl);
    }

    public void put(Context context, String imageUrl, Bitmap bitmap) {
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(context.getCacheDir().getAbsolutePath() + imageUrl);
            bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(fos ! =null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

/** * Double cache */
public class DoubleCache {

    // Image cache
    private final ImageLruCache imageLruCache = new ImageLruCache();
    / / SDCard cache
    private final ImageSDCardCache imageSDCardCache = new ImageSDCardCache();

    public Bitmap get(Context context, String imageUrl) {
        Bitmap bitmap = imageLruCache.get(imageUrl);
        if (bitmap == null) {
            bitmap = imageSDCardCache.get(context, imageUrl);
        }
        return bitmap;
    }

    public void put(Context context, String imageUrl, Bitmap bitmap) { imageLruCache.put(imageUrl, bitmap); imageSDCardCache.put(context, imageUrl, bitmap); }}Copy the code

Xiao Ming through a few modifications to complete this function, immediately feel their Android development handy.

Director: You have to change the original code every time you add a new cache, which makes it easy to introduce bugs, and the code logic becomes more complex. With this design, the user cannot accomplish custom caching.

Xiao Ming was so confused that the supervisor had to handle the knife himself.

/** * Image loader */
public class ImageLoader {

    // Image cache
    private IImageCache imageCache = new ImageLruCache();
    // Thread pool, the number of threads is the number of CPUS
    private final ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    //Ui Handler
    private final Handler uiHandler = new Handler(Looper.getMainLooper());

    /** * Set image cache **@param imageCache
     */
    public void setImageCache(IImageCache imageCache) {
        this.imageCache = imageCache;
    }

    /** * Show pictures **@param imageUrl
     * @param imageView
     */
    public void displayImage(String imageUrl, ImageView imageView) {
        Bitmap bitmap = imageCache.get(imageView.getContext(), imageUrl);
        if(bitmap ! =null) {
            imageView.setImageBitmap(bitmap);
            return;
        }
        imageView.setTag(imageUrl);
        executorService.submit(new Runnable() {
            @Override
            public void run(a) {
                Bitmap bitmap = downloadImage(imageUrl);
                if(bitmap ! =null) {
                    if(imageView.getTag().equals(imageUrl)) { postToImageView(imageView, bitmap); } imageCache.put(imageView.getContext(), imageUrl, bitmap); }}}); }/** * Update to ImageView **@param imageView
     * @param bitmap
     */
    private void postToImageView(ImageView imageView, Bitmap bitmap) {
        uiHandler.post(new Runnable() {
            @Override
            public void run(a) { imageView.setImageBitmap(bitmap); }}); }/** * Download the image **@param imageUrl
     * @return* /
    private Bitmap downloadImage(String imageUrl) {
        HttpURLConnection connection = null;
        try {
            URL url = new URL(imageUrl);
            connection = (HttpURLConnection) url.openConnection();
            if (connection.getResponseCode() == 200) {
                // Ignore the image compression process...
                returnBitmapFactory.decodeStream(connection.getInputStream()); }}catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(connection ! =null) { connection.disconnect(); }}return null; }}/** * Abstract cache interface */
public interface IImageCache {

    /** * Save the image **@param context
     * @param imageUrl
     * @return* /
    Bitmap get(Context context, String imageUrl);

    /** * Take out pictures **@param context
     * @param imageUrl
     * @param bitmap
     */
    void put(Context context, String imageUrl, Bitmap bitmap);
}

/** * Image cache */
public class ImageLruCache implements IImageCache {

    // Image cache
    private final LruCache<String, Bitmap> imageLruCache;

    public ImageLruCache(a) {
        // The maximum memory available
        final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        // Use 1/4 of the available memory as the image cache
        final int imageLruCacheSize = maxMemory / 4;
        imageLruCache = new LruCache<String, Bitmap>(imageLruCacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getRowBytes() * value.getHeight() / 1024; }}; }@Override
    public Bitmap get(Context context, String imageUrl) {
        return imageLruCache.get(imageUrl);
    }

    @Override
    public void put(Context context, String imageUrl, Bitmap bitmap) { imageLruCache.put(imageUrl, bitmap); }}/** * SDCard cache */
public class ImageSDCardCache implements IImageCache{

    @Override
    public Bitmap get(Context context, String imageUrl) {
        return BitmapFactory.decodeFile(context.getCacheDir().getAbsolutePath() + imageUrl);
    }

    @Override
    public void put(Context context, String imageUrl, Bitmap bitmap) {
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(context.getCacheDir().getAbsolutePath() + imageUrl);
            bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(fos ! =null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

/** * Double cache */
public class DoubleCache implements IImageCache {

    // Image cache
    private final ImageLruCache imageLruCache = new ImageLruCache();
    / / SDCard cache
    private final ImageSDCardCache imageSDCardCache = new ImageSDCardCache();

    @Override
    public Bitmap get(Context context, String imageUrl) {
        Bitmap bitmap = imageLruCache.get(context, imageUrl);
        if (bitmap == null) {
            bitmap = imageSDCardCache.get(context, imageUrl);
        }
        return bitmap;
    }

    @Override
    public void put(Context context, String imageUrl, Bitmap bitmap) { imageLruCache.put(context, imageUrl, bitmap); imageSDCardCache.put(context, imageUrl, bitmap); }}Copy the code

In the above code, the user can inject different cache implementations through the setImageCache method, because all caches have a feature, that is, the IImageCache interface, which makes ImageLoader more extensible.

Xiao Ming then had an Epiphany.

Third, summary

When software needs to change, try to do so by extending it, rather than by modifying existing code. “Should try” doesn’t mean you can’t change the original class, but you should refactor as soon as possible when the original code “smells rotten.”