最近因为以前写应用的网络图片读取都是另外启动一个线程去下载,现在感觉下载速度太慢了,而且也不方便管理。就打算写一个异步下载的,在网上找了一些大神们的博客看了。
开始自己动手写了。写了一个异步下载图片的类和一个缓存图片到SD卡的类。
首先是异步下载图片的类,用Android提供的LruCache来缓存图片非常方便,当使用的缓存接近最大时LruCache会自动释放掉使用次数最少的图片。需要了解LruCache更多的信息可以点。
1 /** 2 * 3 * @ClassName ImageDownload 4 * @Description 异步图片下载类,能异步多线程下载图片,最大为并发5线程。能将下载好的图片缓存在内存和SD卡中, 5 * 等下次需要的时候就不用再下载,节省流量。 6 * @author lan4627@gmail.com 7 * @date 2014-3-27 8 * @version V1.2 9 */ 10 public class ImageDownload { 11 private static final String TAG = ImageDownload.class.getSimpleName(); 12 private Context mContext; 13 private LruCachemLruCache; 14 private ImageLoader imageLoader; 15 private ImageCache imageCache; 16 17 public ImageDownload(Context context) { 18 mContext = context.getApplicationContext(); 19 // 用最大内存的八分之一来存图片 20 int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); 21 int cache = maxMemory / 8; 22 mLruCache = new LruCache (cache) { 23 24 protected int sizeOf(String key, Bitmap value) { 25 return (value.getRowBytes() * value.getHeight()) / 1024; 26 } 27 28 }; 29 // 初始化图片缓存类,用来把图片缓存到SD卡的文件夹里 30 imageCache = new ImageCache(mContext); 31 } 32 33 private void addImageCache(String key, Bitmap bitmap) { 34 if (getImage(key) == null) { 35 mLruCache.put(key, bitmap); 36 } 37 } 38 39 private Bitmap getImage(String key) { 40 return mLruCache.get(key); 41 } 42 43 /** 44 * 45 * @Method: loadBitmap 46 * @Description: 读取图片的方法,先找缓存中看是否有,没有再找SD卡的缓存目录中是否有,没有再启动一个线程去下载图片。 47 * 下载完成后返回图片,同时把下载好的图片储存在缓存和SD卡中,下次要用就不用再下载了。 48 * @throws 无 49 * @param key 50 * 需要下载的图片的URL 51 * @param imageLoader 52 * 回调接口 53 * @return 返回一个Bitmap类型的图片 54 */ 55 @TargetApi(Build.VERSION_CODES.HONEYCOMB) 56 public Bitmap loadBitmap(String key, ImageLoader imageLoader) { 57 Bitmap bitmap = getImage(key); 58 if (bitmap != null) { 59 return bitmap; 60 } 61 // 读取内置SD卡的缓存文件夹看有没有这张图片 62 bitmap = imageCache.getCacheImage(key); 63 if (bitmap != null) { 64 addImageCache(key, bitmap); 65 return getImage(key); 66 } 67 // 如果都没有,则启动线程去联网读取。 68 this.imageLoader = imageLoader; 69 if (android.os.Build.VERSION.SDK_INT > 10) { 70 new ImageDownloadTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, key); // 当SDK版本大于10时,建立一个线程池为5的AsyncTask 71 } else { 72 new ImageDownloadTask().execute(key); 73 } 74 // 返回一个默认的占位图。 75 return BitmapFactory.decodeResource(mContext.getResources(), R.drawable.bit_null); 76 } 77 78 public interface ImageLoader { 79 public void onImage(String key, Bitmap bitmap); 80 } 81 82 class ImageDownloadTask extends AsyncTask { 83 private String key; 84 85 protected Bitmap doInBackground(String... params) { 86 Bitmap bitmap = null; 87 InputStream is = null; 88 try { 89 key = params[0]; 90 URL url = new URL(key); 91 HttpURLConnection huc = (HttpURLConnection) url.openConnection(); 92 // 设置请求方式 93 huc.setRequestMethod("GET"); 94 // 设置读取的超时时间 95 huc.setReadTimeout(11000); 96 // 设置网络连接超时时间 97 huc.setConnectTimeout(4000); 98 // 拿到服务器返回的响应码 99 int hand = huc.getResponseCode();100 if (hand == 200) {101 // 读取图片102 is = huc.getInputStream();103 bitmap = BitmapFactory.decodeStream(is);104 } else {105 Log.e(TAG, "错误的服务器返回码" + hand);106 }107 } catch (ProtocolException e) {108 Log.e(TAG, "不支持的连接方式");109 e.printStackTrace();110 } catch (MalformedURLException e) {111 Log.e(TAG, "URL解析错误");112 e.printStackTrace();113 } catch (IOException e) {114 Log.e(TAG, "连接打开错误或创建输入流错误");115 e.printStackTrace();116 } finally {117 if (is != null) {118 try {119 is.close();120 } catch (IOException e) {121 Log.e(TAG, "InputStream关闭时发生错误");122 e.printStackTrace();123 }124 }125 }126 if (bitmap != null) {127 // 如果不等于空,则加入缓存中以及保存到SD卡。128 addImageCache(key, bitmap);129 imageCache.saveImage(key, bitmap);130 }131 return bitmap;132 }133 // 下载完成后通过实现好的接口设置图片134 protected void onPostExecute(Bitmap bitmap) {135 if (bitmap != null) {136 imageLoader.onImage(key, bitmap);137 }138 }139 140 }141 }
一般都是在GridView和ListView的适配器里使用这个异步下载的工具。
1 public class Test extends BaseAdapter { 2 private ArrayList> list; 3 private LayoutInflater lInflater; 4 private ListView listView; 5 private ImageDownload imageDownload; 6 7 public final class ListItemView { 8 public ImageView icon; 9 }10 11 public Test(Context context, ArrayList > list, ListView listView) {12 lInflater = LayoutInflater.from(context);13 this.list = list;14 this.listView = listView;15 // 初始化图片下载类16 imageDownload = new ImageDownload(context);17 }18 19 @Override20 public int getCount() {21 return list.size();22 }23 24 @Override25 public Object getItem(int position) {26 return list.get(position);27 }28 29 @Override30 public long getItemId(int position) {31 return position;32 }33 34 @Override35 public View getView(int position, View convertView, ViewGroup parent) {36 ListItemView listItem = null;37 if (convertView == null) {38 listItem = new ListItemView();39 // 获取listItem视图40 convertView = lInflater.inflate(R.layout.list_warinfo, null);41 listItem.icon = (ImageView) convertView.findViewById(R.id.get_icon);42 convertView.setTag(listItem);43 } else {44 listItem = (ListItemView) convertView.getTag();45 }46 // 获得要下载的图片网址47 String url = (String)list.get(position).get("url");48 // 设置标记49 listItem.icon.setTag(url);50 // 实现回调接口51 ImageLoader imageLoader = new ImageLoader() {52 53 public void onImage(String key, Bitmap bitmap) {54 ImageView image = (ImageView) listView.findViewWithTag(key);55 if (image != null) {56 image.setImageBitmap(bitmap);57 }58 }59 };60 Bitmap bitmap = imageDownload.loadBitmap(url, imageLoader);61 listItem.icon.setImageBitmap(bitmap);62 return convertView;63 }64 65 }
下面是图片缓存类,会如果缓存文件夹过大了会自动清除,而且对外也提供了两个清除缓存文件夹的方法。
1 /** 2 * 3 * @ClassName ImageCache 4 * @Description 图片缓存 5 * @author lan4627@gmail.com 6 * @date 2014-3-29 7 * @version V1.0 8 */ 9 public class ImageCache { 10 private static final String TAG = ImageCache.class.getSimpleName(); 11 private Context context; 12 13 /** 14 * 15 * @Description: ImageCache初始化的时候会启动一个线程检查两个缓存文件夹的大小。 16 * 如果SD卡内的缓存文件大于5MB,则自动删除里面的文件。 如果内部的缓存文件大于1MB,则自动删除里面的文件。 17 * @param context 18 * 上下文 19 */ 20 public ImageCache(final Context context) { 21 this.context = context.getApplicationContext(); 22 new Thread() { 23 public void run() { 24 long externalSize = cacheDirSize(getExternalCacheDir()); 25 long cacheSize = cacheDirSize(context.getCacheDir()); 26 float size = Float.valueOf(externalSize) / (1024 * 1024); 27 if (size > 5) { 28 Log.v(TAG, "开始删除文件"); 29 delete(getExternalCacheDir()); 30 } 31 size = cacheSize / (1024 * 1024); 32 if (size > 1) { 33 Log.v(TAG, "开始删除文件"); 34 delete(context.getCacheDir()); 35 } 36 } 37 38 }.start(); 39 } 40 41 /** 42 * 43 * @Method: getCacheFile 44 * @Description: 此方法用来获取缓存文件夹的路径。 45 * 如果SD卡是可用的状态,返回SD卡里的缓存文件夹。如果不可以则返回程序安装目录下的缓存文件夹。 46 * @throws 无 47 * @return 返回缓存文件夹的路径。 48 */ 49 private File getCacheFile() { 50 File file = null; 51 if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { 52 file = getExternalCacheDir(); 53 } else { 54 file = context.getCacheDir(); 55 } 56 return file; 57 } 58 /** 59 * 60 * @Method: getExternalCacheDir 61 * @Description: 此方法根据不同的SDK版本来返回SD卡内的缓存文件夹路径。 62 * Android2.1以上的版本返回/Android/data/包名/cache。 63 * Android2.1及以下版本在SD卡根目录创建一个ImageCache文件夹。 64 * @throws 无 65 * @return 返回SD卡里的缓存文件夹路径。 66 */ 67 @SuppressLint("NewApi") 68 private File getExternalCacheDir() { 69 File file = null; 70 if (android.os.Build.VERSION.SDK_INT > 7) { 71 file = context.getExternalCacheDir(); 72 } else { 73 file = new File(Environment.getExternalStorageDirectory() + "/ImageCache"); 74 // 如果目录不存在就创建一个 75 if (!file.exists()) { 76 file.mkdir(); 77 } 78 } 79 return file; 80 } 81 82 /** 83 * 84 * @Method: saveImage 85 * @Description: 把下载的图片保存在内置SD卡的缓存文件夹中,下次要用的时候可以先检查缓存的文件夹里有没有,有的话就不用下载 86 * @throws 无 87 * @param key 88 * 图片的URL 89 * @param bitmap 90 * 图片 91 */ 92 public boolean saveImage(String path, Bitmap bitmap) { 93 // 把图片的下载地址作为保存的名字,需要把/和:替换掉,不然程序会认为这是一个目录。 94 String fileName = path.replace("/", "_").replace(":", "&"); 95 File saveFile = new File(getCacheFile(), fileName); 96 FileOutputStream fos = null; 97 try { 98 fos = new FileOutputStream(saveFile); 99 return bitmap.compress(CompressFormat.JPEG, 70, fos);100 } catch (FileNotFoundException e) {101 Log.e(TAG, "缓存图片的文件夹路径不存在");102 return false;103 } finally {104 if (fos != null) {105 try {106 fos.flush();107 fos.close();108 } catch (IOException e) {109 Log.e(TAG, "写入缓存图片的文件输出流关闭错误");110 e.printStackTrace();111 }112 }113 }114 }115 116 /**117 * 118 * @Method: getCacheImage119 * @Description: 从SD卡的缓存目录中读取图片120 * @throws 无121 * @param key122 * 图片的URL123 * @return 返回一个Bitmap类型的图片, 如果缓存文件夹里无此图片则返回null。124 */125 @SuppressLint("NewApi")126 public Bitmap getCacheImage(String path) {127 String fileName = path.replace("/", "_").replace(":", "&");128 Bitmap bitmap = null;129 File cacheFile = new File(getCacheFile(), fileName);130 FileInputStream fis = null;131 try {132 fis = new FileInputStream(cacheFile);133 bitmap = BitmapFactory.decodeStream(fis);134 } catch (FileNotFoundException e) {135 Log.i(TAG, "缓存图片不存在");136 } finally {137 if (fis != null) {138 try {139 fis.close();140 } catch (IOException e) {141 Log.e(TAG, "读取缓存图片的文件输入流关闭错误");142 e.printStackTrace();143 }144 }145 }146 return bitmap;147 }148 149 /**150 * 151 * @Method: cacheDirSize152 * @Description: 此方法用来计算文件夹的大小153 * @throws 无154 * @param file155 * 需要计算大小的文件夹156 * @return 返回文件夹大小。一个long类型的数,单位为字节。157 */158 private long cacheDirSize(File file) {159 long size = 0;160 String[] files = file.list();161 for (int i = 0; i < files.length; i++) {162 size += new File(file, files[i]).length();163 }164 return size;165 }166 167 /**168 * 169 * @Method: delete170 * @Description: 此方法用来删除文件夹内的所有文件。171 * @throws 无172 * @param file173 * 需要删除内部所有文件的文件夹174 */175 private static synchronized void delete(File file) {176 if (file != null && file.exists() && file.isDirectory()) {177 String[] files = file.list();178 for (int i = 0; i < files.length; i++) {179 new File(file, files[i]).delete();180 }181 Log.v(TAG, "删除完成");182 }183 }184 185 /**186 * 187 * @Method: deleteExternalCacheFile188 * @Description: 删除SD卡缓存文件夹里的所有文件。189 * @throws 无190 */191 public void deleteExternalCacheFile() {192 if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {193 delete(getExternalCacheDir());194 }195 }196 197 /**198 * 199 * @Method: deleteCacheFile200 * @Description: 删除程序安装目录下的缓存文件夹里所有文件。201 * @throws 无202 */203 public void deleteCacheFile() {204 File cacheFile = context.getCacheDir();205 delete(cacheFile);206 }207 }