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

众所周知,在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
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值