1.意义:加快读取速度,减少流量的消耗,减少崩溃的次数
2.Android应用中的UI现成5秒没有相应的话就会强制抛出异常,俗称ANR(Appliction Not Responce),对于获取远程的资源,这里特指的是从服务器获取的数据譬如图片等等,这种异常将会更加容易被抛出来,所以在Android 4.0 里面将限制了网络的访问,不允许将网络的访问放在主线程,低于4.0的版本就不会收到限制,这个是在测试的时候发现的,Android中提供两个方法来做这件事情:
启动一个心的现成来获取资源,完成后通过handler机制发送消息,同时在handler的接收端更新主线程,从而达到异步线程获取图片,接收端定义handler变量,同事复写handlMessage(Message msg)方法
本地缓存
对图片来说,你不可能让应用每次获取的时候都重新到远程服务器去下载,特别是显示ListView中的图片的时候,滑动的速度变得很快,这样将会造成ANR,即使图片比较小,但是图片还没来得及释放的话,累计的图片将会占用比较大的内存,但是又不能将所有的图片资源在获取之后放在内存中,使用弱引用保存对象的方法保存,因为图片的资源往往很占内存也比较容易造成ANR,那么如果下载下来的图片保存的SdCard中,下次直接从SDcard上去获取的话,是比较靠谱的缓存方法,采用LRU等一些算法可以保证sdcard被占用的空间的一小部分,这样即保证了图片的加载,节省了从远程获取的图片流量,又使Sdcard的空间只占用了一笑部分,另外一中方法是设置LRU规则跟过期的时间
代码的流程如下:
下载图片--->判断Sdcard上的空间--->判断开辟的10Mde空间--->保存图片--->过期策略
2.1在Sdcard上开辟一定的空间,需要先判断Sdcard上剩余的空间是否足够,如果足够的话,就可以开辟空间,例如开辟10M的内存空间用来保存图片
2.2当需要获取图片的时候,就先从Sdcard上的目录中去找,如果找的到的话,使用该图片,并且更新图片最后被使用的时间,如果找不到,通过URL去DownLoad
2.3去服务器下载图片,如果图片下载成功了,放入SDcard,并使用,如果失败了,应该有重试的机制重新下载,譬如三次
2.4下载成功后保存到Sdcard上需要判断10M的空间是否已经用完,如果没用完就保存,如果已经用完空间,就根据LRU规则删除一些最近没有被用户用到的资源
保存图片到SD的代码:
private void saveBmpToSd(Bitmap bm, String url) { if (bm == null) { Log.w(TAG, " trying to savenull bitmap"); return; } // 判断sdcard上的空间 if (FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) { Log.w(TAG, "Low free space onsd, do not cache"); return; } String filename = convertUrlToFileName(url); String dir = getDirectory(filename); File file = new File(dir + "/" + filename); try { file.createNewFile(); OutputStream outStream = new FileOutputStream(file); bm.compress(Bitmap.CompressFormat.JPEG, 100, outStream); outStream.flush(); outStream.close(); Log.i(TAG, "Image saved tosd"); } catch (FileNotFoundException e) { Log.w(TAG, "FileNotFoundException"); } catch (IOException e) { Log.w(TAG, "IOException"); } }
计算Sdcard上的空间:
/** * 计算sdcard上的剩余空间 * * @return */ private int freeSpaceOnSd() { // TODO Auto-generated method stub StatFs stat = new StatFs(Environment.getExternalStorageDirectory() .getPath()); double sdFreeMB = ((double) stat.getAvailableBlocks() * (double) stat .getBlockSize()); // MB return (int) sdFreeMB; }
修改文件的最后修改时间:
/** * 修改文件的最后修改时间 * * @param dir * @param fileName */ private void updateFileTime(String dir, String fileName) { File file = new File(dir, fileName); long newModifiedTime = System.currentTimeMillis(); file.setLastModified(newModifiedTime); }
本地缓存优化:
/** * 计算储存目录下的文件大小,当文件的总大小超过规定的CACHE_SIZE, * 或者是Sdcard剩余空间小于FREE_SD_SPACE_NEEDED_TO_CACHE 的规定 ,那么删除40%最近没有使用的图片 * * @param dirPath */ private void removeCache(String dirPath) { File file = new File(dirPath); File[] files = file.listFiles(); if (files == null) { return; } int dirSize = 0; for (int i = 0; i < files.length; i++) { if (files[i].getName().contains(WHOLESALE_CONV)) { dirSize += files[i].length(); } } if (dirSize > CACHE_SIZE * MB || FREE_SD_SPACE_NEEED_TO_CACHE > freeSpaceOnSd()) { int removeFactor = (int) ((0.4 * files.length) + 10); Arrays.sort(files, new FileLastModifSort()); Log.i(TAG, "Clear some expiredcache files"); for (int i = 0; i < removeFactor; i++) { if (files[i].getName().contains(WHOLESALE_CONV)) { files[i].delete(); } } } }
/** * 删除过期文件 * @param dirPath * @param fileName */ private void removeExpiredCache(String dirPath, String fileName) { File file = new File(dirPath, fileName); if (System.currentTimeMillis() - file.lastModified() > M_TIME_DIFF) { Log.i(TAG, "Clear some expiredcache files"); file.delete(); } }
文件使用时间排序:
/** * 根据文件的最后修改时间进行排序 * @author huanglong * */ class FileLastModifSort implements Comparator<File> { public int compare(File arg0, File arg1) { if (arg0.lastModified() > arg1.lastModified()) { return 1; } else if (arg0.lastModified() == arg1.lastModified()) { return 0; } else { return -1; } } }
内存保存:
在内存中保存的话,只能保存一定的量,而不能一直往里面放,需要设置数据的过期时间,LRU等算法,这里有一个方法是把常用的数据放到一个缓存中(A),不常用的放在另外一个缓存中(B),当要获取数据时候先从A中去获取,如果A中存在在去B中获取,B中的数据主要是A中LUR出来的数据,这里的内存回收主要是针对B,从而保持A中的数据可以有效的被命中。
定义A缓存:
// 定义A缓存 private final HashMap<String, Bitmap> mHardBItmapCache = new LinkedHashMap<String, Bitmap>( HARD_CACHE_CAPACITY / 2, 0.75f, true) { @Override protected boolean removeEldestEntry( java.util.Map.Entry<String, Bitmap> eldest) { // TODO Auto-generated method stub if (size() > HARD_CACHE_CAPACITY) { // 保证map的size大于30时候,把最不常用的key放到mSoftBitmapCache中,从而保证mHardBitmapCache的效率 mSoftBitmapCache.put(eldest.getKey(), new SoftReference<Bitmap>(eldest.getValue())); return true; } else { return false; } } };
定义B缓存:
// 定义B缓存 // 当mHardBitmapCache的key大于30的时候,会根据LRU算法把最近没有使用的Key放入到缓存中 // Bitmap 使用了SoftReference 当内存不足的时候,此时cache中的bitmap会被垃圾回收掉 private final static ConcurrentHashMap<String, SoftReference<Bitmap>> mSoftBitmapCache = new ConcurrentHashMap<String, SoftReference<Bitmap>>( HARD_CACHE_CAPACITY / 2);
从缓存中获取数据:
/** * 从缓存中获得数据 * * @param url * @return */ private Bitmap getBitmapFromeCache(String url) { // 先从mHardBitmapCache缓存中获取 synchronized (mHardBItmapCache) { final Bitmap bitmap = mHardBItmapCache.get(url); if (bitmap != null) { // 如果找到的话,把元素移动到linkedHashMap的最前面,从而保证LRU算法中是最后被删除的 mHardBItmapCache.remove(url); mHardBItmapCache.put(url, bitmap); return bitmap; } } // 如果mHardBitmapCache中找不到,到mSoftBitmapCache中找 SoftReference<Bitmap> bitmapReference = mSoftBitmapCache.get(url); if (bitmapReference != null) { final Bitmap bitmap = bitmapReference.get(); if (bitmap != null) { return bitmap; } else { mSoftBitmapCache.remove(url); } } return null; }
如果缓存不存在,那么就只能去服务器下载:
// 如果缓存中不存在那么就只能去服务器下载 class ImageDownloaderTask extends AsyncTask<String, Void, Bitmap> { private static final int IO_BUFFER_SIZE = 4 * 1024; private String url; private final WeakReference<ImageView> imageViewReferrReference; private ImageDownloaderTask() { imageViewReferrReference = new WeakReference<ImageView>(imageView); } @Override protected Bitmap doInBackground(String... params) { // TODO Auto-generated method stub final AndroidHttpClient client = AndroidHttpClient .newInstance("Android"); url = params[0]; final HttpGet getRequest = new HttpGet(url); try { HttpResponse response = client.execute(getRequest); final int statusCode = response.getStatusLine().getStatusCode(); if (statusCode != HttpStatus.SC_OK) { Log.v(TAG, "从" + url + "中下载图片是出错! 错误码:" + statusCode); return null; } final HttpEntity entity = response.getEntity(); if (entity != null) { InputStream inputStream = null; OutputStream outputStream = null; try { inputStream = entity.getContent(); final ByteArrayOutputStream dataStream = new ByteArrayOutputStream(); outputStream = new BufferedOutputStream(dataStream, IO_BUFFER_SIZE); copy(inputStream, outputStream); outputStream.flush(); final byte[] data = dataStream.toByteArray(); final Bitmap bitma = BitmapFactory.decodeByteArray( data, 0, data.length); } finally { if (inputStream != null) { inputStream.close(); } if (outputStream != null) { outputStream.close(); } entity.consumeContent(); } } } catch (IOException e) { // TODO Auto-generated catch block getRequest.abort(); Log.w(TAG, "Incorrect URL :" + url); e.printStackTrace(); } finally { if (client != null) { client.close(); } } return null; } }
这是两种做法,还有一些应用在下载的时候使用了线程池和消息队列MQ,对于图片的下载效果会更好一些
http://mobile.51cto.com/android-288600.htm