前言

本文引自:http://www.xycoding.com/articles/2014/07/29/android-async-images-download/,作者不详

ImagesDownLoad源码下载:DEMO

接触android开发不久,近段时间需实现一个批量下载图片并显示的小功能。在网上搜索了一圈,发现国内外网上异步加载的例子太多太杂,要么是加载大图decode时报OOM异常,要么内存急剧上升不稳定。所以在前辈们的基础上,做了一些优化,特共享出来,欢迎大家指正。这里主要参见了以下两篇文章,非常感谢:

Android照片墙应用实现,再多的图片也不怕崩溃

Android 异步加载图片,使用LruCache和SD卡或手机缓存,效果非常的流畅

在巨人的肩膀上,主要优化了以下几个地方:

  1. 下载大图decode时,可根据View大小自动缩放图片,不在出现OOMSkImageDecoder::Factory returned null错误
  2. 图片下载失败时,可自定义失败重试次数
  3. 记录正在下载的任务,防止屏幕滚动时多次下载
  4. 缓存目录容量大于给定限制时,清空文件缓存
  5. 其他一些小问题

工具类Utils

Utils类主要封装了一些文件操作的基本方法,包括创建、删除、获取文件大小等。

public class Utils {private static final String Util_LOG = makeLogTag(Utils.class);public static String makeLogTag(Class<?> cls) {return cls.getName();}public static void showToast(Context context, String str) {Toast.makeText(context, str, Toast.LENGTH_SHORT).show();}/*** 检查是否存在SD卡* * @return*/public static boolean hasSdcard() {String state = Environment.getExternalStorageState();if (state.equals(Environment.MEDIA_MOUNTED)) {return true;} else {return false;}}/*** 创建目录* * @param context* @param dirName*            文件夹名称* @return*/public static File createFileDir(Context context, String dirName) {String filePath;// 如SD卡已存在,则存储;反之存在data目录下if (hasSdcard()) {// SD卡路径filePath = Environment.getExternalStorageDirectory()+ File.separator + dirName;} else {filePath = context.getCacheDir().getPath() + File.separator+ dirName;}File destDir = new File(filePath);if (!destDir.exists()) {boolean isCreate = destDir.mkdirs();Log.i(Util_LOG, filePath + " has created. " + isCreate);}return destDir;}/*** 删除文件(若为目录,则递归删除子目录和文件)* * @param file* @param delThisPath*            true代表删除参数指定file,false代表保留参数指定file*/public static void delFile(File file, boolean delThisPath) {if (!file.exists()) {return;}if (file.isDirectory()) {File[] subFiles = file.listFiles();if (subFiles != null) {int num = subFiles.length;// 删除子目录和文件for (int i = 0; i < num; i++) {delFile(subFiles[i], true);}}}if (delThisPath) {file.delete();}}/*** 获取文件大小,单位为byte(若为目录,则包括所有子目录和文件)* * @param file* @return*/public static long getFileSize(File file) {long size = 0;if (file.exists()) {if (file.isDirectory()) {File[] subFiles = file.listFiles();if (subFiles != null) {int num = subFiles.length;for (int i = 0; i < num; i++) {size += getFileSize(subFiles[i]);}}} else {size += file.length();}}return size;}/*** 保存Bitmap到指定目录* * @param dir*            目录* @param fileName*            文件名* @param bitmap* @throws IOException*/public static void savaBitmap(File dir, String fileName, Bitmap bitmap) {if (bitmap == null) {return;}File file = new File(dir, fileName);try {file.createNewFile();FileOutputStream fos = new FileOutputStream(file);bitmap.compress(CompressFormat.JPEG, 100, fos);fos.flush();fos.close();} catch (IOException e) {e.printStackTrace();}}/*** 判断某目录下文件是否存在* * @param dir*            目录* @param fileName*            文件名* @return*/public static boolean isFileExists(File dir, String fileName) {return new File(dir, fileName).exists();}
}

图片下载管理类ImageDownLoader

ImageDownLoader类主要是图片下载管理,包括缓存管理、异步下载管理等。

public class ImageDownLoader {private static final String ImageDownLoader_Log = Utils.makeLogTag(ImageDownLoader.class);/** 保存正在下载或等待下载的URL和相应失败下载次数(初始为0),防止滚动时多次下载 */private Hashtable<String, Integer> taskCollection;/** 缓存类 */private LruCache<String, Bitmap> lruCache;/** 线程池 */private ExecutorService threadPool;/** 缓存文件目录 (如无SD卡,则data目录下) */private File cacheFileDir;/** 缓存文件夹 */private static final String DIR_CACHE = "cache";/** 缓存文件夹最大容量限制(10M) */private static final long DIR_CACHE_LIMIT = 10 * 1024 * 1024;/** 图片下载失败重试次数 */private static final int IMAGE_DOWNLOAD_FAIL_TIMES = 2;public ImageDownLoader(Context context) {// 获取系统分配给每个应用程序的最大内存int maxMemory = (int) Runtime.getRuntime().maxMemory();// 给LruCache分配最大内存的1/8lruCache = new LruCache<String, Bitmap>(maxMemory / 8) {// 必须重写此方法,来测量Bitmap的大小@Overrideprotected int sizeOf(String key, Bitmap value) {return value.getRowBytes() * value.getHeight() / 1024;}};taskCollection = new Hashtable<String, Integer>();// 创建线程数threadPool = Executors.newFixedThreadPool(10);cacheFileDir = Utils.createFileDir(context, DIR_CACHE);}/*** 添加Bitmap到内存缓存* * @param key* @param bitmap*/private void addLruCache(String key, Bitmap bitmap) {if (getBitmapFromMemCache(key) == null && bitmap != null) {lruCache.put(key, bitmap);}}/*** 从内存缓存中获取Bitmap* * @param key* @return*/private Bitmap getBitmapFromMemCache(String key) {return lruCache.get(key);}/*** 异步下载图片,并按指定宽度和高度压缩图片* * @param url* @param width* @param height* @param listener*            图片下载完成后调用接口*/public void loadImage(final String url, final int width, final int height,AsyncImageLoaderListener listener) {Log.i(ImageDownLoader_Log, "download:" + url);final ImageHandler handler = new ImageHandler(listener);Runnable runnable = new Runnable() {@Overridepublic void run() {Bitmap bitmap = downloadImage(url, width, height);Message msg = handler.obtainMessage();msg.obj = bitmap;handler.sendMessage(msg);// 将Bitmap 加入内存缓存addLruCache(url, bitmap);// 加入文件缓存前,需判断缓存目录大小是否超过限制,超过则清空缓存再加入long cacheFileSize = Utils.getFileSize(cacheFileDir);if (cacheFileSize > DIR_CACHE_LIMIT) {Log.i(ImageDownLoader_Log, cacheFileDir+ " size has exceed limit." + cacheFileSize);Utils.delFile(cacheFileDir, false);taskCollection.clear();}// 缓存文件名称( 替换url中非字母和非数字的字符,防止系统误认为文件路径)String urlKey = url.replaceAll("[^\\w]", "");// 将Bitmap加入文件缓存Utils.savaBitmap(cacheFileDir, urlKey, bitmap);}};// 记录该url,防止滚动时多次下载,0代表该url下载失败次数taskCollection.put(url, 0);threadPool.execute(runnable);}/*** 获取Bitmap, 若内存缓存为空,则去文件缓存中获取* * @param url* @return 若缓存中没找到,则返回null*/public Bitmap getBitmapCache(String url) {// 去处url中特殊字符作为文件缓存的名称String urlKey = url.replaceAll("[^\\w]", "");if (getBitmapFromMemCache(url) != null) {return getBitmapFromMemCache(url);} else if (Utils.isFileExists(cacheFileDir, urlKey)&& Utils.getFileSize(new File(cacheFileDir, urlKey)) > 0) {// 从文件缓存中获取BitmapBitmap bitmap = BitmapFactory.decodeFile(cacheFileDir.getPath()+ File.separator + urlKey);// 将Bitmap 加入内存缓存addLruCache(url, bitmap);return bitmap;}return null;}/*** 下载图片,并按指定高度和宽度压缩* * @param url* @param width* @param height* @return*/private Bitmap downloadImage(String url, int width, int height) {Bitmap bitmap = null;HttpClient httpClient = new DefaultHttpClient();try {httpClient.getParams().setParameter(CoreProtocolPNames.PROTOCOL_VERSION, HttpVersion.HTTP_1_1);HttpPost httpPost = new HttpPost(url);HttpResponse httpResponse = httpClient.execute(httpPost);if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {HttpEntity entity = httpResponse.getEntity();//解决缩放大图时出现SkImageDecoder::Factory returned null错误byte[] byteIn = EntityUtils.toByteArray(entity);BitmapFactory.Options bmpFactoryOptions = new BitmapFactory.Options();bmpFactoryOptions.inJustDecodeBounds = true;BitmapFactory.decodeByteArray(byteIn, 0, byteIn.length,bmpFactoryOptions);int heightRatio = (int) Math.ceil(bmpFactoryOptions.outHeight/ height);int widthRatio = (int) Math.ceil(bmpFactoryOptions.outWidth/ width);if (heightRatio > 1 && widthRatio > 1) {bmpFactoryOptions.inSampleSize = heightRatio > widthRatio ? heightRatio: widthRatio;}bmpFactoryOptions.inJustDecodeBounds = false;bitmap = BitmapFactory.decodeByteArray(byteIn, 0,byteIn.length, bmpFactoryOptions);}} catch (ClientProtocolException e) {e.printStackTrace();} catch (ConnectTimeoutException e) {e.printStackTrace();} catch (Exception e) {e.printStackTrace();} finally {if (httpClient != null && httpClient.getConnectionManager() != null) {httpClient.getConnectionManager().shutdown();}}// 下载失败,再重新下载// 本例是图片下载失败则再次下载,可根据需要改变,比如记录下载失败的图片URL,在某个时刻再次下载if (taskCollection.get(url) != null) {int times = taskCollection.get(url);if (bitmap == null&& times < IMAGE_DOWNLOAD_FAIL_TIMES) {times++;taskCollection.put(url, times);bitmap = downloadImage(url, width, height);Log.i(ImageDownLoader_Log, "Re-download " + url + ":" + times);}}return bitmap;}/*** 取消正在下载的任务*/public synchronized void cancelTasks() {if (threadPool != null) {threadPool.shutdownNow();threadPool = null;}}/*** 获取任务列表* * @return*/public Hashtable<String, Integer> getTaskCollection() {return taskCollection;}/** 异步加载图片接口 */public interface AsyncImageLoaderListener {void onImageLoader(Bitmap bitmap);}/** 异步加载完成后,图片处理 */static class ImageHandler extends Handler {private AsyncImageLoaderListener listener;public ImageHandler(AsyncImageLoaderListener listener) {this.listener = listener;}@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);listener.onImageLoader((Bitmap) msg.obj);}}
}

GridView适配器类ImageGridViewAdapter

该类为GridView适配器,并实现滚动监听等的功能。

public class ImageGridViewAdapter extends BaseAdapter implements OnScrollListener {/** 数据源 */private List<String> data;/** 图片下载类 */private ImageDownLoader loader;/** 判定是否第一次加载 */private boolean isFirstEnter = true;/** 第一张可见Item下标 */private int firstVisibleItem;/** 每屏Item可见数 */private int visibleItemCount;/** GridView实例 */private GridView gridView;private Context context;public ImageGridViewAdapter(Context context, GridView gridView, List<String> data) {this.context = context;this.gridView = gridView;this.data = data;loader = new ImageDownLoader(context);this.gridView.setOnScrollListener(this);}@Overridepublic int getCount() {return data.size();}@Overridepublic Object getItem(int position) {return data.get(position);}@Overridepublic long getItemId(int position) {return position;}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {String url = data.get(position);if (convertView == null) {convertView = LayoutInflater.from(context).inflate(R.layout.photo_item, null);}ImageView imageView = (ImageView) convertView.findViewById(R.id.photo);// 设置Tag,保证异步加载图片不乱序imageView.setTag(url);setImageView(imageView, url);return convertView;}private void setImageView(ImageView imageView, String url) {Bitmap bitmap = loader.getBitmapCache(url);if (bitmap != null) {imageView.setImageBitmap(bitmap);} else {imageView.setImageResource(R.drawable.empty_photo);}}@Overridepublic void onScrollStateChanged(AbsListView view, int scrollState) {// 当停止滚动时,加载图片if (scrollState == SCROLL_STATE_IDLE) {loadImage(firstVisibleItem, visibleItemCount);}}@Overridepublic void onScroll(AbsListView view, int firstVisibleItem,int visibleItemCount, int totalItemCount) {this.firstVisibleItem = firstVisibleItem;this.visibleItemCount = visibleItemCount;if (isFirstEnter && visibleItemCount > 0) {loadImage(firstVisibleItem, visibleItemCount);isFirstEnter = false;}}/*** 加载图片,若缓存中没有,则根据url下载* * @param firstVisibleItem* @param visibleItemCount*/private void loadImage(int firstVisibleItem, int visibleItemCount) {Bitmap bitmap = null;for (int i = firstVisibleItem; i < firstVisibleItem + visibleItemCount; i++) {String url = data.get(i);final ImageView imageView = (ImageView) gridView.findViewWithTag(url);bitmap = loader.getBitmapCache(url);if (bitmap != null) {imageView.setImageBitmap(bitmap);} else {// 防止滚动时多次下载if (loader.getTaskCollection().containsKey(url)) {continue;}imageView.setImageDrawable(context.getResources().getDrawable(R.drawable.empty_photo));loader.loadImage(url, imageView.getWidth(),imageView.getHeight(),new ImageDownLoader.AsyncImageLoaderListener() {@Overridepublic void onImageLoader(Bitmap bitmap) {if (imageView != null && bitmap != null) {imageView.setImageBitmap(bitmap);}}});}}}/*** 取消下载任务*/public void cancelTasks() {loader.cancelTasks();}
}

最后的最后,别忘加上权限,并附效果图如下:

<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

转载于:https://www.cnblogs.com/qingtianhua/p/4143419.html

Android异步批量下载图片并缓存相关推荐

  1. android异步加载图片并缓存到内存和sd卡上,Android批量图片加载经典系列——采用二级缓存、异步加载网络图片...

    http://www.cnblogs.com/jerehedu/p/4560119.html 2015-06-08 09:20 by 杰瑞教育, 232 阅读, 1 评论, 收藏, 编辑一.问题描述 ...

  2. android 批量压缩图片大小,Android异步批量压缩图片

    最近稍微空闲了一点,然后开始整理一下之前项目用到的东西,方便以后项目再次使用.很多项目需要用到发布图片的功能吧,像社区朋友圈之类的,如果直接把图片不经过压缩上传,那体验肯定不好,第一个浪费流量.第二个 ...

  3. python爬虫之多线程threading、多进程multiprocessing、协程aiohttp 批量下载图片

    一.单线程常规下载 常规单线程执行脚本爬取壁纸图片,只爬取一页的图片. import datetime import re import requests from bs4 import Beauti ...

  4. 支付宝小程序批量下载图片

    支付宝小程序批量下载图片 const imgs = ['src1','src2','src3','src4','src5']; batchDownload(imgs).then(res => { ...

  5. 2021-03-10 Python多线程爬虫快速批量下载图片

    Python多线程爬虫快速批量下载图片 1.完成这个需要导入的模块 urllib,random,queue(队列),threading,time,os,json 第三方模块的安装 键盘win+R,输入 ...

  6. python 批量下载网页图片_Python实现多线程批量下载图片

    <派森>(Python)3.13 win32 英文安装版 类型:编程工具大小:21M语言:英文 评分:8.7 标签: 立即下载 爬取图片可真的是一个可遇不可求的机会. 有需求就会动力. 目 ...

  7. 爬虫小案例:基于Bing关键词批量下载图片(第二版)

    一.需求: 基于Bing网站,输入关键词,批量下载图片保存到本地 二.代码展示: import requests from lxml import etree import os from multi ...

  8. 爬虫小案例:基于Bing关键词批量下载图片

    一.需求: 基于Bing网站,输入关键词,批量下载图片保存到本地 二.演示: 三.直接上代码 import os import urllib.request import urllib.parse f ...

  9. img绝对路径图片显示_使用python爬虫去风景图片网站批量下载图片

    使用python爬虫(requests,BeautifulSoup)去风景图片网站批量下载图片 1.写代码背景: 今天闲来无事,想弄点图片放到电脑,方便以后使用,故去百度查找一些风景图片网站,发现图片 ...

  10. python multiprocessing 批量下载图片+tqdm

    紧接着我的上一篇博客:用tqdm可视化loop过程,我将继续探索multiprocessing 批量下载图片+tqdm 首先,是安装multiprocessing模块了,注意在python3下pip ...

最新文章

  1. apache mediawiki 安装_MediaWiki系统的安装、配置和修改方法(转载)
  2. 一位Java大牛的BAT面试心得与经验总结,挥泪整理面经
  3. [原] Android持续优化 - 提高流畅度
  4. ExtJs UI框架学习三
  5. python的用算法进制转换详解_学习python第五天进制转换
  6. imagesLoaded-检测图片是否正确加载的js插件
  7. Java Swing Mysql实现的Ktv点歌系统项目源码附带视频运行教
  8. 商城系统PageBean分页 Cookie存储浏览记录
  9. Web Application Projects的一个问题
  10. 学习js继承的6种方式
  11. 3.调整web框架行为 3.1配置路径匹配形式
  12. Pytorch——3.1. 热身:Pytorch基础
  13. CMD 命令行实现 Windows 下复制文件到文件夹下的所有文件夹
  14. 认知电子战 (1.3):认知电子战概述
  15. 中柏平板u盘启动_中柏A13笔记本如何进bios设置u盘启动
  16. At91sam9g35K烧录Linux系统
  17. 调节e18-d80nk的测量距离_没有倒车影像,用这个方法判断后方墙壁距离,这个距离感这样练习...
  18. 360视频简介及ERP投影
  19. 迅雷大乱,突现“两个迅雷”
  20. 将Unity场景以Wavefront Obj格式导出

热门文章

  1. NHibernate的缓存管理机制
  2. flex navigateToURL()页面跳转的例子
  3. Qt5学习笔记之QQ登录界面五:切换界面
  4. vue -- router路由跳转错误 , NavigationDuplicated
  5. python习题错误整理(一)
  6. BZOJ4530:[BJOI2014]大融合
  7. Runtime.exec使用错误导致延迟.md
  8. 埃博拉疫情蔓延在进行中
  9. CentOS7安装GNOME可视化界面 和 远程访问
  10. Linux上mysql安装详细教程