Android图片缓存

来源:互联网 发布:类似keynote的软件 编辑:程序博客网 时间:2024/06/03 19:09

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

目前常用的一种算法是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照片墙,高效加载图片,敬请期待。

原创粉丝点击