Android缓存网络图片的实现

如果每次加载同一张图片都要从网络获取,那代价实在太大了。所以同一张图片只要从网络获取一次就够了,然后在本地缓存起来,之后加载同一张图片时就 从缓存中加载就可以了。从内存缓存读取图片是最快的,但是因为内存容量有限,所以最好再加上文件缓存。文件缓存空间也不是无限大的,容量越大读取效率越 低,因此可以设置一个限定大小比如10M,或者限定保存时间比如一天。
因此,加载图片的流程应该是:
1、先从内存缓存中获取,取到则返回,取不到则进行下一步;
2、从文件缓存中获取,取到则返回并更新到内存缓存,取不到则进行下一步;

3、从网络下载图片,并更新到内存缓存和文件缓存。

接下来看内存缓存类:ImageMemoryCache
public class ImageMemoryCache {
/**
* 从内存读取数据速度是最快的,为了更大限度使用内存,这里使用了两层缓存。
* 硬引用缓存不会轻易被回收,用来保存常用数据,不常用的转入软引用缓存。
*/
private static final int SOFT_CACHE_SIZE = 15; //软引用缓存容量
private static LruCache mLruCache; //硬引用缓存
private static LinkedHashMap> mSoftCache; //软引用缓存

public ImageMemoryCache(Context context) {
    int memClass = ((ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();
    int cacheSize = 1024 * 1024 * memClass / 4;  //硬引用缓存容量,为系统可用内存的1/4
    mLruCache = new LruCache(cacheSize) {
        @Override
        protected int sizeOf(String key, Bitmap value) {
            if (value != null)
                return value.getRowBytes() * value.getHeight();
            else
                return 0;
        }
                                                                                    
        @Override
        protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
            if (oldValue != null)
                // 硬引用缓存容量满的时候,会根据LRU算法把最近没有被使用的图片转入此软引用缓存
                mSoftCache.put(key, new SoftReference(oldValue));
        }
    };
    mSoftCache = new LinkedHashMap>(SOFT_CACHE_SIZE, 0.75f, true) {
        private static final long serialVersionUID = 6040103833179403725L;
        @Override
        protected boolean removeEldestEntry(Entry> eldest) {
            if (size() > SOFT_CACHE_SIZE){    
                return true;  
            }  
            return false; 
        }
    };
}
                                                                            
/**
 * 从缓存中获取图片
 */
public Bitmap getBitmapFromCache(String url) {
    Bitmap bitmap;
    //先从硬引用缓存中获取
    synchronized (mLruCache) {
        bitmap = mLruCache.get(url);
        if (bitmap != null) {
            //如果找到的话,把元素移到LinkedHashMap的最前面,从而保证在LRU算法中是最后被删除
            mLruCache.remove(url);
            mLruCache.put(url, bitmap);
            return bitmap;
        }
    }
    //如果硬引用缓存中找不到,到软引用缓存中找
    synchronized (mSoftCache) { 
        SoftReference bitmapReference = mSoftCache.get(url);
        if (bitmapReference != null) {
            bitmap = bitmapReference.get();
            if (bitmap != null) {
                //将图片移回硬缓存
                mLruCache.put(url, bitmap);
                mSoftCache.remove(url);
                return bitmap;
            } else {
                mSoftCache.remove(url);
            }
        }
    }
    return null;
} 
                                                                            
/**
 * 添加图片到缓存
 */
public void addBitmapToCache(String url, Bitmap bitmap) {
    if (bitmap != null) {
        synchronized (mLruCache) {
            mLruCache.put(url, bitmap);
        }
    }
}
                                                                            
public void clearCache() {
    mSoftCache.clear();
}

}

文件缓存类:ImageFileCache
public class ImageFileCache {
private static final String CACHDIR = "ImgCach";
private static final String WHOLESALE_CONV = ".cach";

private static final int MB = 1024*1024;
private static final int CACHE_SIZE = 10;
private static final int FREE_SD_SPACE_NEEDED_TO_CACHE = 10;
                                                          
public ImageFileCache() {
    //清理文件缓存
    removeCache(getDirectory());
}
                                                          
/** 从缓存中获取图片 **/
public Bitmap getImage(final String url) {    
    final String path = getDirectory() + "/" + convertUrlToFileName(url);
    File file = new File(path);
    if (file.exists()) {
        Bitmap bmp = BitmapFactory.decodeFile(path);
        if (bmp == null) {
            file.delete();
        } else {
            updateFileTime(path);
            return bmp;
        }
    }
    return null;
}
                                                          
/** 将图片存入文件缓存 **/
public void saveBitmap(Bitmap bm, String url) {
    if (bm == null) {
        return;
    }
    //判断sdcard上的空间
    if (FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {
        //SD空间不足
        return;
    }
    String filename = convertUrlToFileName(url);
    String dir = getDirectory();
    File dirFile = new File(dir);
    if (!dirFile.exists())
        dirFile.mkdirs();
    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();
    } catch (FileNotFoundException e) {
        Log.w("ImageFileCache", "FileNotFoundException");
    } catch (IOException e) {
        Log.w("ImageFileCache", "IOException");
    }
} 
                                                          
/**
 * 计算存储目录下的文件大小,
 * 当文件总大小大于规定的CACHE_SIZE或者sdcard剩余空间小于FREE_SD_SPACE_NEEDED_TO_CACHE的规定
 * 那么删除40%最近没有被使用的文件
 */
private boolean removeCache(String dirPath) {
    File dir = new File(dirPath);
    File[] files = dir.listFiles();
    if (files == null) {
        return true;
    }
    if (!android.os.Environment.getExternalStorageState().equals(
            android.os.Environment.MEDIA_MOUNTED)) {
        return false;
    }
                                                      
    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_NEEDED_TO_CACHE > freeSpaceOnSd()) {
        int removeFactor = (int) ((0.4 * files.length) + 1);
        Arrays.sort(files, new FileLastModifSort());
        for (int i = 0; i < removeFactor; i++) {
            if (files[i].getName().contains(WHOLESALE_CONV)) {
                files[i].delete();
            }
        }
    }
                                                      
    if (freeSpaceOnSd() <= CACHE_SIZE) {
        return false;
    }
                                                              
    return true;
}
                                                          
/** 修改文件的最后修改时间 **/
public void updateFileTime(String path) {
    File file = new File(path);
    long newModifiedTime = System.currentTimeMillis();
    file.setLastModified(newModifiedTime);
}
                                                          
/** 计算sdcard上的剩余空间 **/
private int freeSpaceOnSd() {
    StatFs stat = new StatFs(Environment.getExternalStorageDirectory().getPath());
    double sdFreeMB = ((double)stat.getAvailableBlocks() * (double) stat.getBlockSize()) / MB;
    return (int) sdFreeMB;
} 
                                                          
/** 将url转成文件名 **/
private String convertUrlToFileName(String url) {
    String[] strs = url.split("/");
    return strs[strs.length - 1] + WHOLESALE_CONV;
}
                                                          
/** 获得缓存目录 **/
private String getDirectory() {
    String dir = getSDPath() + "/" + CACHDIR;
    return dir;
}
                                                          
/** 取SD卡路径 **/
private String getSDPath() {
    File sdDir = null;
    boolean sdCardExist = Environment.getExternalStorageState().equals(
            android.os.Environment.MEDIA_MOUNTED);  //判断sd卡是否存在
    if (sdCardExist) {
        sdDir = Environment.getExternalStorageDirectory();  //获取根目录
    }
    if (sdDir != null) {
        return sdDir.toString();
    } else {
        return "";
    }
} 
                                                      
/**
 * 根据文件的最后修改时间进行排序
 */
private class FileLastModifSort implements Comparator {
    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;
        }
    }
}

}

从网络获取图片:
public class ImageGetFormHttp {
private static final String LOG_TAG = "ImageGetForHttp";

public static Bitmap downloadBitmap(String url) {
    final HttpClient client = new DefaultHttpClient();
    final HttpGet getRequest = new HttpGet(url);
                                                         
    try {
        HttpResponse response = client.execute(getRequest);
        final int statusCode = response.getStatusLine().getStatusCode();
        if (statusCode != HttpStatus.SC_OK) {
            Log.w("ImageDownloader", "Error " + statusCode + " while retrieving bitmap from " + url);
            return null;
        }
                                                             
        final HttpEntity entity = response.getEntity();
        if (entity != null) {
            InputStream inputStream = null;
            try {
                inputStream = entity.getContent();
                FilterInputStream fit = new FlushedInputStream(inputStream);
                return BitmapFactory.decodeStream(fit);
            } finally {
                if (inputStream != null) {
                    inputStream.close();
                    inputStream = null;
                }
                entity.consumeContent();
            }
        }
    } catch (IOException e) {
        getRequest.abort();
        Log.w(LOG_TAG, "I/O error while retrieving bitmap from " + url, e);
    } catch (IllegalStateException e) {
        getRequest.abort();
        Log.w(LOG_TAG, "Incorrect URL: " + url);
    } catch (Exception e) {
        getRequest.abort();
        Log.w(LOG_TAG, "Error while retrieving bitmap from " + url, e);
    } finally {
        client.getConnectionManager().shutdown();
    }
    return null;
}
                                                 
/*
 * An InputStream that skips the exact number of bytes provided, unless it reaches EOF.
 */
static class FlushedInputStream extends FilterInputStream {
    public FlushedInputStream(InputStream inputStream) {
        super(inputStream);
    }
                                                 
    @Override
    public long skip(long n) throws IOException {
        long totalBytesSkipped = 0L;
        while (totalBytesSkipped < n) {
            long bytesSkipped = in.skip(n - totalBytesSkipped);
            if (bytesSkipped == 0L) {
                int b = read();
                if (b < 0) {
                    break;  // we reached EOF
                } else {
                    bytesSkipped = 1; // we read one byte
                }
            }
            totalBytesSkipped += bytesSkipped;
        }
        return totalBytesSkipped;
    }
}

}

最后,获取一张图片的流程就如下代码所示:
/*** 获得一张图片,从三个地方获取,首先是内存缓存,然后是文件缓存,最后从网络获取 ***/
public Bitmap getBitmap(String url) {
// 从内存缓存中获取图片
Bitmap result;
result = memoryCache.getBitmapFromCache(url);
if (result == null) {
// 文件缓存中获取
result = fileCache.getImage(url);
if (result == null) {
// 从网络获取
result = ImageGetFormHttp.downloadBitmap(url);
if (result != null) {
fileCache.saveBitmap(result, url);
memoryCache.addBitmapToCache(url, result);
}
} else {
// 添加到内存缓存
memoryCache.addBitmapToCache(url, result);
}
}
return result;
}

你可能感兴趣的:(Android缓存网络图片的实现)