Android图片缓存

在移动设备上,用户访问网络需要消耗宝贵的流量,因此缓存策略就变得尤为重要。例如当用户第一次从网络加载图片后,就将其缓存到存储设备上,这样当下一次使用到这张图片时就不再从网络中获取。很多时候往往还在内存中缓存一份。这样,当用户加载图片时,首先会从内存中获取,如果内存中没有,那么就从存储设备中获取,最后才从网络下下载这张图片。

目前常用的一种算法是LRU(Least Recently Used),近期最少使用算法,采用LRU算法的缓存有两种:LruCache和DiskLruCache,分别用来实现内存缓存 和存储设备缓存。那么就先来使用第一种,从内存中获取图片。

LruCache

LruCache是一个泛型类,它内部采用一个LinkedHashMap以强引用的方式存储外界缓存对象,其提供get和put的方法来完成缓存的获取和添加操作。

下面的ImageCache类完成了LruCache的创建过程,和实现了缓存的取get方法和缓存的存put方法。

/**
 * 图片缓存类
 * Created by lhc on 2017/7/31.
 */

public class ImageCache {
    //图片缓存
    LruCache<String, Bitmap> mImageCache;

    public ImageCache(){
        initImageCache();
    }

    private void initImageCache() {
        int maxMemory= (int) (Runtime.getRuntime().maxMemory()/1024);
        int cacheSize = maxMemory / 4;
        mImageCache = new LruCache<String,Bitmap>(cacheSize){
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                return bitmap.getRowBytes()*bitmap.getHeight()/1024;
            }
        };
    }

    public void put(String url, Bitmap bitmap){
        mImageCache.put(url,bitmap);
    }

    public Bitmap get(String url){
        return mImageCache.get(url);
    }
}

接着便是图片加载的类,这里使用到同步的方式加载图片,在加载图片的时候,先判断内存中是否有,如果有则从内存中获取,如果没有再从网络中获取,这里使用到线程池开启新的线程下载图片,然后再在主线程中得到设置图片,更新UI。

**
 * 图片加载类
 * Created by lhc on 2017/7/27.
 */

public class ImageLoader {
    Handler mUiHandle = new Handler(Looper.getMainLooper());
    ImageCache mImageCache;
    ExecutorService executorService;

    public ImageLoader(){
        mImageCache= new ImageCache();
        //只有核心线程并且这些线程不会被回收,能更加快速的响应外界的请求
        executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    }
    /**
     * 显示图片  
     * @param url
     * @param imageView
     */
    public void displayImage(final String url, final ImageView imageView) {
        //从内存中获取
        Bitmap bitmap = mImageCache.get(url);
        if (bitmap != null) {
            Log.e("ImageLoader", "从内存中获取");
            imageView.setImageBitmap(bitmap);
            return;
        }
        imageView.setTag(url);
        executorService.submit(new Runnable() {
            @Override
            public void run() {
                Bitmap bitmap = downloadImage(url);
                if (bitmap == null) {
                    return;
                }
                //更新url
                if (imageView.getTag().equals(url)) {
                    Log.e("ImageLoader", "从网络获取");
                    updateImageView(imageView, bitmap);
                }
                mImageCache.put(url, bitmap);
            }
        });
    }

    /**
     * 显示图片 在主线程  
     *
     * @param imageView
     * @param bitmap
     */
    private void updateImageView(final ImageView imageView, final Bitmap bitmap) {
        mUiHandle.post(new Runnable() {
            @Override
            public void run() {
                imageView.setImageBitmap(bitmap);
            }
        });
    }

    /**
     * 下载图片 从网络
     *
     * @param imageurl
     * @return
     */
    private Bitmap downloadImage(String imageurl) {
        Bitmap bitmap = null;
        HttpURLConnection conn = null;
        InputStream is = null;
        try {
            URL url = new URL(imageurl);
            conn = (HttpURLConnection) url.openConnection();
            is = conn.getInputStream();
            bitmap = BitmapFactory.decodeStream(is);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            conn.disconnect();
            try {
                if (is != null)
                    is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return bitmap;
    }

}

最后是我们的测试类,在onCreate方法中new一个ImageLoader,点击Button获取图片,可以发现,第一次从网络下获取,第二次从内存中获取。

public class MainActivity extends AppCompatActivity {
    ImageLoader loader;
    private final String url = "http://www.zhlzw.com/UploadFiles/Article_UploadFiles/201204/20120412123929231.jpg";
    private ImageView imageView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        imageView = (ImageView) findViewById(R.id.imageVIew);

        loader = new ImageLoader();
    }

    public void button(View view) {
        loader.displayImage(url,imageView);
    }
}

DiskLruCache

DiskLruCache用于实现存储设备缓存,Disk不属于Android SDK一部分,它的源码可以可以从这里获得

下面的类完成了DiskLruCache的创建以及从缓存中获取,存入缓存的方法。Disk的创建通过DiskLruCache.open(cachefile,appVersion,valueCount,maxSize)静态方法获得。要存入缓存要通过Editor完成,要获得缓存要通过diskLruCache得到Snapshot对象,再得到输入流,从而获得图片。

注意这里存入缓存要通过访问网络得到输入流,然后存入文件的输出流中,要在子线程中进行,而设置图片更改UI要在主线程中,因此使用到了AsyncTask.

注意添加访问网络以及读写权限。

public class ImageDiskCache {
    DiskLruCache diskLruCache;

    public ImageDiskCache(Context context) {
        try {
            File file = getDiskCacheDir(context, "bitmap");
            if (!file.exists()) {
                file.mkdirs();
            }
            diskLruCache = DiskLruCache.open(file, 1, 1, 10 * 1024 * 1024);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 根据url存入缓存
     *
     * @param urlstring
     */
    private void put(final String urlstring) {
        try {
            String key = hashKeyForDisk(urlstring);
            DiskLruCache.Editor editor = diskLruCache.edit(key);
            if (editor != null) {
                OutputStream os = editor.newOutputStream(0);
                if (downloadUrlToStream(urlstring, os)) {
                    editor.commit();
                } else {
                    editor.abort();
                }
            }
            //这里记得刷新一下,同步到journal文件
            diskLruCache.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    /**
     * 设置图片  异步线程
     *
     * @param urlstring
     * @return
     */
    public void get(final String urlstring, final ImageView imageView) {
        imageView.setTag(urlstring);
        new AsyncTask<String, Void, Bitmap>() {
            @Override
            protected Bitmap doInBackground(String... params) {
                Bitmap bitmap = null;
                try {
                    String key = hashKeyForDisk(urlstring);
                    DiskLruCache.Snapshot snapshot = diskLruCache.get(key);
                    if (snapshot == null) {
                        //如果没有找到对应的缓存,则准备从网络上请求数据,并写入缓存
                        put(urlstring);
                        snapshot = diskLruCache.get(key);
                    }
                    InputStream is = snapshot.getInputStream(0);
                    bitmap = BitmapFactory.decodeStream(is);
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return bitmap;
            }

            @Override
            protected void onPostExecute(Bitmap bitmap) {
                if (imageView.getTag().equals(urlstring)) {
                    imageView.setImageBitmap(bitmap);
                }
            }
        }.execute();
    }


    /**
     * 删除指定url的缓存
     *
     * @param urlstring
     */
    public void remove(String urlstring) {
        try {
            String key = hashKeyForDisk(urlstring);
            diskLruCache.remove(key);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 删除全部缓存
     */
    public void delete() {
        try {
            diskLruCache.delete();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 必须运行在子线程
     * 当下载网络图片时候,图片就可以通过这个文件输入流写到文件系统
     *
     * @param urlstring
     * @param os
     * @return
     */
    private boolean downloadUrlToStream(final String urlstring, final OutputStream os) {
        HttpURLConnection conn = null;
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;
        try {
            URL url = new URL(urlstring);
            conn = (HttpURLConnection) url.openConnection();
            InputStream is = conn.getInputStream();
            bis = new BufferedInputStream(is, 8 * 1024);
            bos = new BufferedOutputStream(os, 8 * 1024);
            int b;
            while ((b = bis.read()) != -1) {
                bos.write(b);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            conn.disconnect();
            try {
                if (bis != null)
                    bis.close();
                if (bos != null)
                    bos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
        return false;
    }

    /**
     * 缓存存入的位置
     *
     * @param context
     * @param filename
     * @return
     */
    private File getDiskCacheDir(Context context, String filename) {
        String cacheFile;
        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
                || !Environment.isExternalStorageRemovable()) {
            cacheFile = context.getExternalCacheDir().getPath();
        } else {
            cacheFile = context.getCacheDir().getPath();
        }
        return new File(cacheFile);
    }

    /**
     * 把url转换成key,因为图片的url很可能存在特殊字符
     * 一般使用url的md5值作为key
     *
     * @param url
     * @return
     */
    private String hashKeyForDisk(String url) {
        String cacheKey = null;
        try {
            MessageDigest messageDigest = MessageDigest.getInstance("MD5");
            messageDigest.update(url.getBytes());
            cacheKey = byteToHexString(messageDigest.digest());
        } catch (NoSuchAlgorithmException e) {
            cacheKey = String.valueOf(url.hashCode());
        }
        return cacheKey;
    }

    /**
     * 将字节转成十进制
     *
     * @param digest
     * @return
     */
    private String byteToHexString(byte[] digest) {
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < digest.length; i++) {
            String hex = Integer.toHexString(0xff & digest[i]);
            if (hex.length() == 1) {
                builder.append('0');
            }
            builder.append(hex);
        }
        return builder.toString();
    }
}

然后 在主activity通过 diskCache.get(url,imageView);就能实现硬盘缓存了。

总结

在以上,分别实现了内存缓存和硬盘缓存,在实际开发过程中,往往使用两者相结合的方式,能更高效的加载图片,节省用户流量。使用例子见下一篇,Android照片墙,高效加载图片,敬请期待。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

KindSuper_liu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值