android图片的二级缓存,让你不再担心图片加载时的OOM

来源:互联网 发布:中国自然数据库 编辑:程序博客网 时间:2024/04/30 14:31

众所周知,在listview中加载多图时容易造成OOM,不过解决的办法由很多种,郭神的博客中也有很多类似的博客,我参照他的博客和群里的小伙伴七七(他提供的demo),写了这篇图片二级缓存的博客,其实图片的二级缓存在很多的框架中早已帮你封装好了,但是知其然,也要知其所以然。

主activity我就不写了下面我会提供demo,主要就是两图片的网址传给adapter中,下面着重就是adapter中的代码

public class MyAdapter extends BaseAdapter implements OnScrollListener {    private List<Bean> mList;    private LayoutInflater mInflater;    // 一级缓存    private LruCache<String, Bitmap> mCache;    // 二级缓存    private DiskLruCache mDiskLruCache;    // 当前显示在界面上的元素序号    private int mStart;    private int mEnd;    // 配合元素序号的URL图片链接    private String[] mUrls;    private ListView mListView;    // 异步任务栈    private Set<MyAsyncTask> mSet;    // 首次预加载标记    private boolean mFirst;    public MyAdapter(Context context, List<Bean> list, ListView listView) {        this.mList = list;        this.mInflater = LayoutInflater.from(context);        this.mListView = listView;        mListView.setOnScrollListener(this);        mSet = new HashSet<MyAsyncTask>();        mFirst = true;        mUrls = new String[list.size()];        for (int i = 0; i < list.size(); i++) {            mUrls[i] = list.get(i).getImageUrl();        }        // 一级缓存初始化        long memory = Runtime.getRuntime().maxMemory();        int maxSize = (int) (memory / 8);        mCache = new LruCache<String, Bitmap>(maxSize) {            @Override            protected int sizeOf(String key, Bitmap value) {                return value.getByteCount();            }        };        // 二级缓存初始化        try {            // 获取图片缓存路径            File disCacheDir = DiskLruCache.getDiskCacheDir(context, "bitmap");            if (!disCacheDir.exists()) {                disCacheDir.mkdirs();            }            // 创建DiskLruCache实例,初始化缓存数据            mDiskLruCache = DiskLruCache.open(disCacheDir,                    DiskLruCache.getAppVersion(context), 1, 10 * 1024 * 1024);        } catch (IOException e) {            e.printStackTrace();        }    }    /**     * baseAdapter区域     */    @Override    public int getCount() {        return mList.size();    }    @Override    public Object getItem(int position) {        return mList.get(position);    }    @Override    public long getItemId(int position) {        return position;    }    @SuppressLint("InflateParams")    @Override    public View getView(int position, View convertView, ViewGroup parent) {        if (convertView == null) {            convertView = mInflater.inflate(R.layout.item, null);        }        Bean bean = mList.get(position);        // 绑定viewholder        TextView title = ViewHolder.get(convertView, R.id.title);        TextView content = ViewHolder.get(convertView, R.id.content);        ImageView image = ViewHolder.get(convertView, R.id.imageView);        // 设置标记        image.setTag(bean.getImageUrl());        // 只从一级缓存里面取        Bitmap bitmap = getBitmapToCache(bean.getImageUrl());        if (bitmap == null) {            image.setImageResource(R.drawable.ic_launcher);        } else {            image.setImageBitmap(bitmap);        }        title.setText(bean.getTitle());        content.setText(bean.getContent());        return convertView;    }    /**     * 一级缓存     *     * @param key     * @param value     */    // 添加到缓存    public void putBitmapToCache(String key, Bitmap value) {        mCache.put(key, value);    }    // 从缓存中取    public Bitmap getBitmapToCache(String key) {        return mCache.get(key);    }    // 将缓存记录同步到journal文件中    //方便每次判断硬盘中数据是否存在    public void fluchCache() {        if (mDiskLruCache != null) {            try {                mDiskLruCache.flush();            } catch (IOException e) {                e.printStackTrace();            }        }    }    /**     * 二级缓存     *     * 从二级缓存中获取图片, 如果二级缓存中都没有,     * 那就去网络上拉去图片并存入本地,     * 然后解析成bitmap存入一级缓存 然后显示出来     *     * @param url     * @return     */    public Bitmap getBitmapToDisCache(String url) {        Bitmap bitmap = null;        OutputStream os = null;        Snapshot snapshot = null;        FileInputStream fis = null;        FileDescriptor fileDescriptor = null;        // MD5加密        String key = HashKeyForMD5.hashKeyForDisk(url);        try {            // 从硬盘中检查journal中有没有key记录            snapshot = mDiskLruCache.get(key);            // 如果没有,则准备从网络中获取            // 首先判断网络连接是否正常,打开情况下才网络获取,否则显示默认图片            if (snapshot == null) {                DiskLruCache.Editor editor = mDiskLruCache.edit(key);                if (editor != null) {                    os = editor.newOutputStream(0);                    // 下载到本地                    if (loadImage(url, os)) {                        editor.commit();                    } else {                        editor.abort();                    }                }            }            // 从网络上下载好图片到硬盘后,再次从硬盘中获取            snapshot = mDiskLruCache.get(key);            if (snapshot != null) {                fis = (FileInputStream) snapshot.getInputStream(0);                fileDescriptor = fis.getFD();            }            // 然后解析读取的数据成bitmap            if (fileDescriptor != null) {                bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor);            }            // 如果解析成功            if (bitmap != null) {                // 添加到一级缓存                putBitmapToCache(url, bitmap);            }            return bitmap;        } catch (Exception e) {            e.printStackTrace();        } finally {            try {                if (os != null) {                    os.close();                }                if (snapshot != null) {                    snapshot.close();                }                if (fis != null) {                    fis.close();                }            } catch (Exception e2) {                e2.printStackTrace();            }        }        return bitmap;    }    /**     * 从网络中拉去图片并存入本地     *     * @param url     * @param os     */    private boolean loadImage(String url, OutputStream os) {        InputStream is = null;        BufferedInputStream bis = null;        BufferedOutputStream bos = null;        try {            is = new URL(url).openStream();            bis = new BufferedInputStream(is);            bos = new BufferedOutputStream(os);            int len = 0;            byte[] buf = new byte[1024*8];            while ((len = bis.read(buf)) != -1) {                bos.write(buf, 0, len);            }            return true;        } catch (MalformedURLException e) {            e.printStackTrace();        } catch (IOException e) {            e.printStackTrace();        } finally {            try {                if (is != null) {                    is.close();                }                if (bos!=null) {                    bos.close();                }                if (bis!=null) {                    bis.close();                }            } catch (Exception e2) {                e2.printStackTrace();            }        }        return false;    }    /**     * 异步加载线程     *     * @author asus     *     */    class MyAsyncTask extends AsyncTask<String, Void, Bitmap> {        private ImageView image;        private String url;        public MyAsyncTask(ImageView image, String url) {            this.image = image;            this.url = url;        }        @Override        protected Bitmap doInBackground(String... params) {            // 从一级缓存中取            Bitmap bitmap = getBitmapToCache(url);            if (bitmap == null) {                // 从二级缓存中取                bitmap = getBitmapToDisCache(url);            }            return bitmap;        }        // UI线程        @Override        protected void onPostExecute(Bitmap result) {            super.onPostExecute(result);            if (image.getTag().equals(url)) {                image.setImageBitmap(result);            }        }    }    /**     *     * 滑动监听     */    // 加载可见项的图片    public void firstToEnd(int first, int end) {        for (int i = first; i < end; i++) {            String url = mUrls[i];            // 从ListView中根据tag获取到imageView            ImageView image = (ImageView) mListView.findViewWithTag(url);            // 下载            MyAsyncTask myTask = new MyAsyncTask(image, url);            // 添加到线程管理器            mSet.add(myTask);            myTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);        }    }    // 取消所以加载    public void cancelAllTask() {        if (mSet != null) {            for (MyAsyncTask task : mSet) {                task.cancel(false);            }        }    }    // 滑动状态改变    @Override    public void onScrollStateChanged(AbsListView view, int scrollState) {        if (scrollState == SCROLL_STATE_IDLE) {            // 加载可见项            firstToEnd(mStart, mEnd);        } else {            // 停止所以的下载任务            cancelAllTask();        }    }    @Override    public void onScroll(AbsListView view, int firstVisibleItem,            int visibleItemCount, int totalItemCount) {        mStart = firstVisibleItem;        mEnd = visibleItemCount + firstVisibleItem;        // 首次启动预加载        if (mFirst && visibleItemCount > 0) {            firstToEnd(mStart, mEnd);            mFirst = false;        }    }}

上面就是adapter的详细代码了,接下来分析一下
首先这个adapter接口了OnScrollListener,当点击程序进去时,首先会执行onSrcoll中的方法

if (mFirst && visibleItemCount > 0) {            firstToEnd(mStart, mEnd);            mFirst = false;        }

判断当前程序是不是第一次开始,并且item数不为0,然后

// 加载可见项的图片    public void firstToEnd(int first, int end) {        for (int i = first; i < end; i++) {            String url = mUrls[i];            // 从ListView中根据tag获取到imageView            ImageView image = (ImageView) mListView.findViewWithTag(url);            // 下载            MyAsyncTask myTask = new MyAsyncTask(image, url);            // 添加到线程管理器            mSet.add(myTask);            myTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);        }    }

然后开启线程,将当先item所对应的url图片添加到线程管理器中开始下载
接下来在getView中

// 设置标记        image.setTag(bean.getImageUrl());        // 只从一级缓存里面取        Bitmap bitmap = getBitmapToCache(bean.getImageUrl());        if (bitmap == null) {            image.setImageResource(R.drawable.ic_launcher);        } else {            image.setImageBitmap(bitmap);        }
// 从缓存中取    public Bitmap getBitmapToCache(String key) {        return mCache.get(key);    }

先给图片用容器设置一个标志,这是防止图片错乱,这里的话可以去参照郭神的防止图片错乱那篇博客,里面很详细的介绍了各种方法。
放置图片时,我们先从内存中寻找有没有这张图片的缓存,有的话,填进去,没有的话,从SD卡的缓存中寻找,这就是二级缓存。
下面我们分析是如何把下载图片并且存进内存和sd卡中的

    // 从一级缓存中取            Bitmap bitmap = getBitmapToCache(url);            if (bitmap == null) {                // 从二级缓存中取                bitmap = getBitmapToDisCache(url);            }

在线程中,我们先根据传进来的url判断当前的一级缓存(内存)和二级缓存(SD卡)中有没有当前url对应的图片,如果有,就返回,没有的话,下载后返回。

    String key = HashKeyForMD5.hashKeyForDisk(url);        try {            // 从硬盘中检查journal中有没有key记录            snapshot = mDiskLruCache.get(key);            // 如果没有,则准备从网络中获取            // 首先判断网络连接是否正常,打开情况下才网络获取,否则显示默认图片            if (snapshot == null) {                DiskLruCache.Editor editor = mDiskLruCache.edit(key);                if (editor != null) {                    os = editor.newOutputStream(0);                    // 下载到本地                    if (loadImage(url, os)) {                        editor.commit();                    } else {                        editor.abort();                    }                }            }            // 从网络上下载好图片到硬盘后,再次从硬盘中获取            snapshot = mDiskLruCache.get(key);            if (snapshot != null) {                fis = (FileInputStream) snapshot.getInputStream(0);                fileDescriptor = fis.getFD();            }            // 然后解析读取的数据成bitmap            if (fileDescriptor != null) {                bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor);            }            // 如果解析成功            if (bitmap != null) {                // 添加到一级缓存                putBitmapToCache(url, bitmap);            }            return bitmap;

上面的过程就是将图片下载下来,然后存进SD卡和内存,然后返回给UI去处理,这里的SD卡缓存是开源框架DiskLruCache,原谅我还暂时理解不了其中的内容,不过这个你可以去hongyang的博客中,他里面有一篇关于这个的详细讲解,虽然我没看的太懂。
以上就是总的流程了,首先预加载首页图片,加载图片时会先从一级二级缓存中找,如果没有,就下载图片并将之存入缓存中,这个在上面的loadImage方法中,我就不贴了。最后为了防止快速滑动中图片加载的各种问题,这里设置了一个滑动时停止加载图片,停止滑动时加载当前页面的图片,有效的防止了图片的OOM和错乱问题。

// 滑动状态改变    @Override    public void onScrollStateChanged(AbsListView view, int scrollState) {        if (scrollState == SCROLL_STATE_IDLE) {            // 加载可见项            firstToEnd(mStart, mEnd);        } else {            // 停止所以的下载任务            cancelAllTask();        }    }

如上,滑动时停止线程,停止时加载图片。
下面上一张效果图
这里写图片描述
demo下载地址:http://download.csdn.net/download/verzqli/9330971

3 0