最近一直在学习男神+大神任玉刚老师的Android开发艺术探索,我我的很是推荐这本书,写的真tm好,很差意思,有点激动。你们有空能够去看看。看完总以为要写点什么吧,就将其中Bitmap加载和Cache的内容分享给尚未来得及看过的朋友们。git
缓存策略在Android中有着普遍的。。。算了,先轻松一下,来张美女镇楼github
老司机要发车了,来不及解释了,你们快上车啊。 算法
缓存策略在Android中有着普遍的使用场景,尤为在图片加载这个场景下,缓存策略变得更为重要。今天就带你们本身动手设计图片缓存工具。缓存
提到缓存,不得不提目前经常使用的一种缓存算法-LRU(Least Recently Used),LRU是近期最少使用算法。采用这种算法思想的缓存有两种:LruCache和DiskLruCache,LruCache用于实现内存缓存,而DiskLruCache则充当了存储设备缓存,经过二者完美结合,就能够轻松实现咱们的ImageLoader。bash
下面咱们就正式开始了。。。网络
1.1LruCacheapp
LruCache的实现比较简单,你们能够参考它的源码。LruCache主要用来实现内存缓存,咱们拿图片缓存来举例子,反手就是一段代码:异步
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight() / 1024;
}
};
复制代码
能够看出,咱们只须要提供缓存的总容量大小并重写sizeOf方法便可。ide
除了LruCache的建立之外,还有缓存的获取和添加,这也很简单,从LruCache中获取一个缓存对象,以下所示。工具
mMemoryCache.get(key)
复制代码
向LruCache中添加一个缓存对象,以下所示。
mMemoryCache.put(key,bitmap)
复制代码
从Android3.1开始,LruCache就已是Android源码的一部分了。
1.2DiskLruCache
DiskCahe用于实现存储设备缓存,已经获得了Android官方的推荐,但不属于SDK的一部分,咱们要导入引用一下:
compile 'com.jakewharton:disklrucache:2.0.2'
1.2.1先来看下DiskLruCache的建立
DiskLruCache提供open方法用于建立自身,以下所示。
pulic static DiskLruCache open(File directory,int appVersion,int valueCount,long maxSize)
复制代码
这里有四个参数,稍微解释下,有兴趣的朋友,下去能够深刻了解下:
directory:磁盘缓存的存储路径。
appVersion:版本号,通常设为1便可。
valueCount:单个节点所对应的数据的个数,通常也设为1便可。
maxSize:缓存的总大小。
整个建立过程,反手又是一段代码:
private static final int DISK_CACHE_SIZE = 50*1024*1024;
File diskCacheDir = getDiskCacheDir(mContext, "bitmap");
if (!diskCacheDir.exists()) {
diskCacheDir.mkdirs();
}
if (getUsableSpace(diskCacheDir) > DISK_CACHE_SIZE) {
try {
mDiskLruCache = DiskLruCache.open(diskCacheDir, 1, 1, DISK_CACHE_SIZE);
mIsDiskLruCacheCreated = true;
} catch (IOException e) {
e.printStackTrace();
}
}
复制代码
1.2.2DiskLruCache的缓存添加
String key = hashKeyFormUrl(url);
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if(editor!=null){
OutputStream output Stream = editor.newOutputStream(DISK_CACHE_INDEX);
}
复制代码
这里的hashKeyFormUrl()方法主要是将url转成key,之因此这么作,是由于url中极可能有特殊字符,会影响url在Android中的直接使用。
有了文件输出流,接下来就是讲网络下载的图片时的文件流写入到文件系统上了:
private boolean downloadUrlToStream(String urlString, OutputStream outputStream) {
HttpURLConnection urlConnection=null;
BufferedOutputStream out = null;
BufferedInputStream in = null;
try {
URL url = new URL(urlString);
urlConnection = (HttpURLConnection) url.openConnection();
in = new BufferedInputStream(urlConnection.getInputStream(),IO_BUFFER_SIZE);
out = new BufferedOutputStream(outputStream,IO_BUFFER_SIZE);
int b;
while ((b=in.read())!=-1){
out.write(b);
}
return true;
} catch (IOException e) {
e.printStackTrace();
}finally {
if(urlConnection!=null){
urlConnection.disconnect();
}
MyUtils.close(out);
MyUtils.close(in);
}
return false;
}
复制代码
还有不要忘记了,editor要提交写入操做。
editor.commit();
复制代码
1.2.3DiskLruCache缓存的查找
private Bitmap loadBitmapFromDiskCache(String url, int reqWidth, int reqHeight)
throws IOException{
if (Looper.myLooper() == Looper.getMainLooper()) {
throw new RuntimeException("load bitmap from UI Thread,it's not recommended");
}
if(mDiskLruCache ==null){
return null;
}
Bitmap bitmap = null;
String key = hashKeyFormUrl(url);
DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
if(snapshot!=null){
FileInputStream fileInputStream = (FileInputStream) snapshot.getInputStream(DISK_CACHE_INDEX);
FileDescriptor fileDescriptor = fileInputStream.getFD();
bitmap = mImageResizer.decodeSampleFromFileDescriptor(fileDescriptor,reqWidth,reqHeight);
if(bitmap !=null){
addBitmapToMemoryCache(key,bitmap);
}
}
return bitmap;
}
复制代码
代码的意思应该好理解,将url转成key,经过get方法获得一个Snapshot对象,经过这个对象便可获得缓存的输入流,有了输入流,还怕拿不到bitmap对象吗。
好了,介绍完两个核心的知识点,喝口雪碧压压惊。
接下来就是重头戏了,就是ImageLoader的实现。
要实现整个完整的ImageLoader功能,要怎么作呢?先捋下思想,应该具有如下几个功能:
-图片的同步加载
-图片的异步加载
-图片压缩
-内存缓存
-磁盘缓存
-网络拉取
接下来,咱们一步一步的实现。
2.1图片压缩功能的实现
将压缩图片功能抽出来单独造成一个类,就叫ImageResizer吧,看下实现:
public class ImageResizer {
private static final String TAG = "ImageResizer";
public Bitmap decodeSampleFromResource(Resources res, int resId, int reqWidth, int reqHeight) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res,resId,options);
}
public Bitmap decodeSampleFromFileDescriptor(FileDescriptor fd, int reqWidth, int reqHeight) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFileDescriptor(fd, null, options);
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFileDescriptor(fd, null, options);
}
private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
if (reqWidth == 0 || reqHeight == 0) {
return 1;
}
int width = options.outWidth;
int height = options.outHeight;
int inSampleSize = 1;
if (width < reqWidth || height < reqHeight) {
return inSampleSize;
}
inSampleSize *= 2;
while (width /inSampleSize>=reqWidth&&height/inSampleSize>=reqHeight){
inSampleSize *=2;
}
Log.e(TAG, "inSampleSize: "+inSampleSize);
return inSampleSize;
}
}
复制代码
2.2内存缓存和磁盘缓存的实现
以前介绍过了,这里放下具体实现:
初始化:
public ImageLoader(Context context) {
mImageResizer = new ImageResizer();
mContext = context;
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight() / 1024;
}
};
File diskCacheDir = getDiskCacheDir(mContext, "bitmap");
if (!diskCacheDir.exists()) {
diskCacheDir.mkdirs();
}
if (getUsableSpace(diskCacheDir) > DISK_CACHE_SIZE) {
try {
mDiskLruCache = DiskLruCache.open(diskCacheDir, 1, 1, DISK_CACHE_SIZE);
mIsDiskLruCacheCreated = true;
} catch (IOException e) {
e.printStackTrace();
}
}
}
复制代码
内存缓存添加和获取
private void addBitmapToMemoryCache(String key, Bitmap bitmap) {
Log.e(TAG, "addBitmapToMemoryCache: ");
if (getBitmapFromMemoryCache(key) == null) {
mMemoryCache.put(key, bitmap);
}
}
private Bitmap getBitmapFromMemoryCache(String key) {
return mMemoryCache.get(key);
}
复制代码
磁盘缓存添加和获取
private Bitmap loadBitmapFromHttp(String url, int reqWidth, int reqHeight)
throws IOException {
if (Looper.myLooper() == Looper.getMainLooper()) {
throw new RuntimeException("can not visit netWork from UI Thread");
}
if (mDiskLruCache == null) {
return null;
}
String key = hashKeyFormUrl(url);
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if(editor!=null){
OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX);
if(downloadUrlToStream(url,outputStream)){
editor.commit();
}else {
editor.abort();
}
mDiskLruCache.flush();
}
return loadBitmapFromDiskCache(url,reqWidth,reqHeight);
}
private Bitmap loadBitmapFromDiskCache(String url, int reqWidth, int reqHeight)
throws IOException{
if (Looper.myLooper() == Looper.getMainLooper()) {
throw new RuntimeException("load bitmap from UI Thread,it's not recommended");
}
if(mDiskLruCache ==null){
return null;
}
Bitmap bitmap = null;
String key = hashKeyFormUrl(url);
DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
if(snapshot!=null){
FileInputStream fileInputStream = (FileInputStream) snapshot.getInputStream(DISK_CACHE_INDEX);
FileDescriptor fileDescriptor = fileInputStream.getFD();
bitmap = mImageResizer.decodeSampleFromFileDescriptor(fileDescriptor,reqWidth,reqHeight);
if(bitmap !=null){
addBitmapToMemoryCache(key,bitmap);
}
}
return bitmap;
}
复制代码
2.3同步加载和异步加载接口的设计
看下同步加载:
public Bitmap loadBitmap(String uri,int reqWidth,int reqHeight){
Bitmap bitmap = loadBitmapFromMemCache(uri);
if(bitmap!=null){
Log.e(TAG, "loadBitmapFromMemCache,url "+uri);
return bitmap;
}
try {
bitmap = loadBitmapFromDiskCache(uri,reqWidth,reqHeight);
if(bitmap!=null){
Log.e(TAG, "loadBitmapFromDiskCache,url "+uri );
return bitmap;
}
bitmap = loadBitmapFromHttp(uri,reqWidth,reqHeight);
Log.e(TAG, "loadBitmapFromHttp,url "+uri);
} catch (IOException e) {
e.printStackTrace();
}
if(bitmap==null&&!mIsDiskLruCacheCreated){
Log.e(TAG, "encounter error,DiskLruCache is not created.");
bitmap = downLoadBitmapFromUrl(uri);
}
return bitmap;
}
复制代码
再看下异步加载
public void bindBitmap(final String uri, final ImageView imageView, final int reqWidth, final int reqHeight){
imageView.setTag(TAG_KEY_URI,uri);
Bitmap bitmap = loadBitmapFromMemCache(uri);
if(bitmap!=null){
imageView.setImageBitmap(bitmap);
return;
}
Runnable loadBitmapTask = new Runnable() {
@Override
public void run() {
Bitmap bitmap = loadBitmap(uri,reqWidth,reqHeight);
if(bitmap!=null){
LoaderResult result = new LoaderResult(imageView,uri,bitmap);
Message message = mMainHandler.obtainMessage(MESSAGE_POST_RESULT,result);
message.sendToTarget();
// sendToTarget();
}
}
};
THREAD_POOL_EXECUTOR.execute(loadBitmapTask);
}
复制代码
仔细的同窗发现了,这里面好像用到了线程池和Handler,关于线程池的内容这里就不扯了,你们本身去度娘。
这里直接把撸好的代码放上来:
private Handler mMainHandler = new Handler(Looper.getMainLooper()){
@Override
public void handleMessage(Message msg) {
LoaderResult result = (LoaderResult) msg.obj;
ImageView imageView = result.imageView;
imageView.setImageBitmap(result.bitmap);
String uri = (String) imageView.getTag(TAG_KEY_URI);
if(uri.equals(result.uri)){
imageView.setImageBitmap(result.bitmap);
}else {
Log.d(TAG, "set image bitmap,but url has changed,ignored!");
}
}
};
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
@Override
public Thread newThread(@NonNull Runnable r) {
return new Thread(r,"ImageLoader#"+mCount.getAndIncrement());
}
};
public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(
CORE_POOL_SIZE,MAXIMUM_POOL_SIZE,
KEEP_ALIVE, TimeUnit.SECONDS
,new LinkedBlockingDeque<Runnable>(),sThreadFactory);
复制代码
哇哈哈哈哈哈哈哈哈。。。。。
下面仍是给下ImageLoader的完整代码。
public class ImageLoader {
private static final String TAG = "ImageLoader";
private Context mContext;
private LruCache<String, Bitmap> mMemoryCache;
private DiskLruCache mDiskLruCache;
private Bitmap bitmap;
private static final int DISK_CACHE_SIZE = 50*1024*1024;
private boolean mIsDiskLruCacheCreated;
private static final int DISK_CACHE_INDEX = 0;
private ImageResizer mImageResizer;
private String cachePath;
private static final int IO_BUFFER_SIZE = 8*1024;
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int TAG_KEY_URI = R.id.imageloader_uri;
private static final int CORE_POOL_SIZE = CPU_COUNT+1;
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT*2+1;
private static final long KEEP_ALIVE = 10L;
private Handler mMainHandler = new Handler(Looper.getMainLooper()){
@Override
public void handleMessage(Message msg) {
LoaderResult result = (LoaderResult) msg.obj;
ImageView imageView = result.imageView;
imageView.setImageBitmap(result.bitmap);
String uri = (String) imageView.getTag(TAG_KEY_URI);
if(uri.equals(result.uri)){
imageView.setImageBitmap(result.bitmap);
}else {
Log.d(TAG, "set image bitmap,but url has changed,ignored!");
}
}
};
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
@Override
public Thread newThread(@NonNull Runnable r) {
return new Thread(r,"ImageLoader#"+mCount.getAndIncrement());
}
};
public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(
CORE_POOL_SIZE,MAXIMUM_POOL_SIZE,
KEEP_ALIVE, TimeUnit.SECONDS
,new LinkedBlockingDeque<Runnable>(),sThreadFactory);
private static final int MESSAGE_POST_RESULT = 1;
public ImageLoader(Context context) {
mImageResizer = new ImageResizer();
mContext = context;
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight() / 1024;
}
};
File diskCacheDir = getDiskCacheDir(mContext, "bitmap");
if (!diskCacheDir.exists()) {
diskCacheDir.mkdirs();
}
if (getUsableSpace(diskCacheDir) > DISK_CACHE_SIZE) {
try {
mDiskLruCache = DiskLruCache.open(diskCacheDir, 1, 1, DISK_CACHE_SIZE);
mIsDiskLruCacheCreated = true;
} catch (IOException e) {
e.printStackTrace();
}
}
}
@TargetApi(Build.VERSION_CODES.GINGERBREAD)
private long getUsableSpace(File path) {
if(Build.VERSION.SDK_INT> Build.VERSION_CODES.GINGERBREAD){
return path.getUsableSpace();
}
StatFs statFs = new StatFs(path.getPath());
return statFs.getBlockSize()*statFs.getAvailableBlocks();
}
private File getDiskCacheDir(Context mContext, String uniqueName) {
boolean externalStorageAvailable = Environment
.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
if(externalStorageAvailable){
cachePath = mContext.getExternalCacheDir().getPath();
}else {
cachePath = mContext.getCacheDir().getPath();
}
return new File(cachePath+File.separator+uniqueName);
}
/**
* load bitmap from memory cache or disk cache or network.
* @param uri http url
* @param reqWidth the width ImageView desired
* @param reqHeight the height ImageView desired
* @return bitmap,maybe null.
*/
public Bitmap loadBitmap(String uri,int reqWidth,int reqHeight){
Bitmap bitmap = loadBitmapFromMemCache(uri);
if(bitmap!=null){
Log.e(TAG, "loadBitmapFromMemCache,url "+uri);
return bitmap;
}
try {
bitmap = loadBitmapFromDiskCache(uri,reqWidth,reqHeight);
if(bitmap!=null){
Log.e(TAG, "loadBitmapFromDiskCache,url "+uri );
return bitmap;
}
bitmap = loadBitmapFromHttp(uri,reqWidth,reqHeight);
Log.e(TAG, "loadBitmapFromHttp,url "+uri);
} catch (IOException e) {
e.printStackTrace();
}
if(bitmap==null&&!mIsDiskLruCacheCreated){
Log.e(TAG, "encounter error,DiskLruCache is not created.");
bitmap = downLoadBitmapFromUrl(uri);
}
return bitmap;
}
public void bindBitmap(final String uri, final ImageView imageView, final int reqWidth, final int reqHeight){
imageView.setTag(TAG_KEY_URI,uri);
Bitmap bitmap = loadBitmapFromMemCache(uri);
if(bitmap!=null){
imageView.setImageBitmap(bitmap);
return;
}
Runnable loadBitmapTask = new Runnable() {
@Override
public void run() {
Bitmap bitmap = loadBitmap(uri,reqWidth,reqHeight);
if(bitmap!=null){
LoaderResult result = new LoaderResult(imageView,uri,bitmap);
Message message = mMainHandler.obtainMessage(MESSAGE_POST_RESULT,result);
message.sendToTarget();
// sendToTarget();
}
}
};
THREAD_POOL_EXECUTOR.execute(loadBitmapTask);
}
private Bitmap downLoadBitmapFromUrl(String uri) {
Bitmap bitmap = null;
HttpURLConnection urlConnection = null;
BufferedInputStream in = null;
try {
URL url = new URL(uri);
urlConnection = (HttpURLConnection) url.openConnection();
in = new BufferedInputStream(urlConnection.getInputStream(),IO_BUFFER_SIZE);
bitmap = BitmapFactory.decodeStream(in);
} catch (IOException e) {
Log.e(TAG, "Error in downloadBitmap:"+e);
}finally {
if(urlConnection!=null){
urlConnection.disconnect();
}
MyUtils.close(in);
}
return bitmap;
}
private Bitmap loadBitmapFromMemCache(String url) {
String key = hashKeyFormUrl(url);
Bitmap bitmap = getBitmapFromMemoryCache(key);
return bitmap;
}
private void addBitmapToMemoryCache(String key, Bitmap bitmap) {
Log.e(TAG, "addBitmapToMemoryCache: ");
if (getBitmapFromMemoryCache(key) == null) {
mMemoryCache.put(key, bitmap);
}
}
private Bitmap getBitmapFromMemoryCache(String key) {
return mMemoryCache.get(key);
}
private Bitmap loadBitmapFromHttp(String url, int reqWidth, int reqHeight)
throws IOException {
if (Looper.myLooper() == Looper.getMainLooper()) {
throw new RuntimeException("can not visit netWork from UI Thread");
}
if (mDiskLruCache == null) {
return null;
}
String key = hashKeyFormUrl(url);
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if(editor!=null){
OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX);
if(downloadUrlToStream(url,outputStream)){
editor.commit();
}else {
editor.abort();
}
mDiskLruCache.flush();
}
return loadBitmapFromDiskCache(url,reqWidth,reqHeight);
}
private Bitmap loadBitmapFromDiskCache(String url, int reqWidth, int reqHeight)
throws IOException{
if (Looper.myLooper() == Looper.getMainLooper()) {
throw new RuntimeException("load bitmap from UI Thread,it's not recommended");
}
if(mDiskLruCache ==null){
return null;
}
Bitmap bitmap = null;
String key = hashKeyFormUrl(url);
DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
if(snapshot!=null){
FileInputStream fileInputStream = (FileInputStream) snapshot.getInputStream(DISK_CACHE_INDEX);
FileDescriptor fileDescriptor = fileInputStream.getFD();
bitmap = mImageResizer.decodeSampleFromFileDescriptor(fileDescriptor,reqWidth,reqHeight);
if(bitmap !=null){
addBitmapToMemoryCache(key,bitmap);
}
}
return bitmap;
}
private boolean downloadUrlToStream(String urlString, OutputStream outputStream) {
HttpURLConnection urlConnection=null;
BufferedOutputStream out = null;
BufferedInputStream in = null;
try {
URL url = new URL(urlString);
urlConnection = (HttpURLConnection) url.openConnection();
in = new BufferedInputStream(urlConnection.getInputStream(),IO_BUFFER_SIZE);
out = new BufferedOutputStream(outputStream,IO_BUFFER_SIZE);
int b;
while ((b=in.read())!=-1){
out.write(b);
}
return true;
} catch (IOException e) {
e.printStackTrace();
}finally {
if(urlConnection!=null){
urlConnection.disconnect();
}
MyUtils.close(out);
MyUtils.close(in);
}
return false;
}
private String hashKeyFormUrl(String url) {
String cacheKey;
MessageDigest mDigest;
try {
mDigest = MessageDigest.getInstance("MD5");
mDigest.update(url.getBytes());
cacheKey = bytesToHexString(mDigest.digest());
} catch (NoSuchAlgorithmException e) {
cacheKey = String.valueOf(url.hashCode());
}
return cacheKey;
}
private String bytesToHexString(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for(int i = 0;i<bytes.length;i++){
String hex = Integer.toHexString(0xFF&bytes[i]);
if(hex.length() == 1){
sb.append("0");
}
sb.append(hex);
}
return sb.toString();
}
}
复制代码
本身模仿着写了个项目分享到github上了。
Github地址