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
- android图片的二级缓存,让你不再担心图片加载时的OOM
- android 图片的加载保存 与 二级缓存
- Android开发图片加载的二级缓存实现
- android 加载图片防止oom的处理
- Android加载图片,避免OOM的解决方案
- Android加载图片时出现OOM(OutOfMermary)的问题解决方案
- ANDROID图片加载时出现OOM的一般处理方法
- android 加载图片oom的好的解决方法
- 关于android gridview 加载大量图片的OOM问题解决方案
- Android加载图片引起的OOM解决方案(转)
- Android 加载打图片出现OOM异常的处理方式
- Android 解决加载图片过多出现oom--强大的Fresco
- Android加载一张3.4MB的图片,不出现OOM
- Android实现图片的加载与释放(解决OOM问题)
- Android加载超大图片并且不会OOM的策略
- Android 图片异步加载的体会,SoftReference已经不再适用
- Android 图片异步加载的体会,SoftReference已经不再适用
- android中加载图片时出现oom
- Linux的Shell脚本中IF流程控制语句的基本语法
- 电子或通信领域当前的主流技术及其社会需求调查报告
- java 向 mysql 插入汉字 Incorrect string value 解决办法
- 导入edustack的ova--版本为cypress的相关配置
- java中的增强型for循环
- android图片的二级缓存,让你不再担心图片加载时的OOM
- 希尔排序
- 【读书笔记】统计学:从数据到结论 第八章
- <%@ taglib prefix="s" uri="/struts-tags" %>地址问题
- 【Solved】XShell Vim、终端乱码问题
- Matlab随机数生成方法
- 寻找最近点对
- 计算机到底如何启动?(UEFI boot)
- 1105. Spiral Matrix (25)