最近因为以前写应用的网络图片读取都是另外启动一个线程去下载,现在感觉下载速度太慢了,而且也不方便管理。就打算写一个异步下载的,在网上找了一些大神们的博客看了。
开始自己动手写了。写了一个异步下载图片的类和一个缓存图片到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 LruCache<String, Bitmap> mLruCache; 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<String, Bitmap>(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<String, Integer, Bitmap> { 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<HashMap<String, Object>> 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<HashMap<String, Object>> 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 @Override 20 public int getCount() { 21 return list.size(); 22 } 23 24 @Override 25 public Object getItem(int position) { 26 return list.get(position); 27 } 28 29 @Override 30 public long getItemId(int position) { 31 return position; 32 } 33 34 @Override 35 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卡内的缓存文件大于10MB,则自动删除里面的文件。 如果内部的缓存文件大于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 > 0.01) { 28 Log.v(TAG, "开始删除文件"); 29 delete(getExternalCacheDir()); 30 } 31 size = cacheSize / (1024 * 1024); 32 if (size > 5) { 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: getCacheImage 119 * @Description: 从SD卡的缓存目录中读取图片 120 * @throws 无 121 * @param key 122 * 图片的URL 123 * @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: cacheDirSize 152 * @Description: 此方法用来计算文件夹的大小 153 * @throws 无 154 * @param file 155 * 需要计算大小的文件夹 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: delete 170 * @Description: 此方法用来删除文件夹内的所有文件。 171 * @throws 无 172 * @param file 173 * 需要删除内部所有文件的文件夹 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: deleteExternalCacheFile 188 * @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: deleteCacheFile 200 * @Description: 删除程序安装目录下的缓存文件夹里所有文件。 201 * @throws 无 202 */ 203 public void deleteCacheFile() { 204 File cacheFile = context.getCacheDir(); 205 delete(cacheFile); 206 } 207 }