添加DiskLruCache的依赖:
compile 'com.jakewharton:disklrucache:2.0.2'
图片压缩
package com.example.bitmap_demo;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;
/**
* 文件描述:
*
创建时间:2019/3/10
*/
public class ImageResizer {
private static final String TAG = "vera ImageResizer";
public Bitmap decodeSampledBitmapFromResource(Resources res,int resId,int reqWidth,int reqHeight){
// 可以通过BitmapFactory.Options来按照一定的采样率加载缩小后的图片
final BitmapFactory.Options options=new BitmapFactory.Options();
// 当inJustDecodeBounds设置为true时,BitmapFactory只会解析图片的原始大小,并不会真正加载图片
options.inJustDecodeBounds=true;
BitmapFactory.decodeResource(res,resId,options);
// 设置采样率
options.inSampleSize=calculateInSampleSize(options,reqWidth,reqHeight);
options.inJustDecodeBounds=false;
return BitmapFactory.decodeResource(res,resId,options);
}
private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
if(reqWidth==0||reqHeight==0)return 1;
final int rawHeight=options.outHeight;
final int rawWidth=options.outWidth;
Log.e(TAG, "calculateInSampleSize: origin width:"+rawWidth+"origin height:"+rawHeight );
int inSampleSize=1;
if(rawHeight>reqHeight||rawWidth>reqWidth){
final int halfWidth=rawWidth/2;
final int halfHeight=rawHeight/2;
// 如果改为||的话会导致所缩放后的图片小于ImageView所期望的大小,这样图片会被拉伸导致图片模糊。
while((halfHeight/inSampleSize)>=reqHeight&&
(halfWidth/inSampleSize)>reqWidth){
inSampleSize*=2;
}
}
// 如果图片原始大小本来就小于ImageView期望的大小,返回1.
Log.e(TAG, "calculateInSampleSize: "+inSampleSize );
return inSampleSize;
}
public Bitmap decodeSampledBitmapFromFileDescriptor(FileDescriptor fd,int reqWidth,int reqHeight){
final 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);
}
}
在ImageLoader的构造函数中初始化LruCache和DiskLruCache:
package com.example.bitmap_demo;
import android.content.Context;
import android.graphics.Bitmap;
import android.os.Build;
import android.os.Environment;
import android.os.StatFs;
import android.util.LruCache;
import com.jakewharton.disklrucache.DiskLruCache;
import java.io.File;
import java.io.IOException;
/**
* 文件描述:
*
创建时间:2019/3/10
*/
public class ImageLoader {
private boolean mIsDiskLruCacheCreated=false;
private Context mContext;
private LruCache<String , Bitmap> mLruCache;//内存缓存
private DiskLruCache mDiskLruCache;//磁盘缓存
private static final int DISK_CACHE_SIZE = 50*1024*1024;//50M
public ImageLoader(Context context) {
mContext = context;
int maxMemory= (int) (Runtime.getRuntime().maxMemory()/1024);//单位是kb,当前进程可用内存
int cacheSize=maxMemory/8;
mLruCache=new LruCache<String,Bitmap>(cacheSize){
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes()*value.getHeight()/1024;//单位是kb,返回Bitmap对象的大小
}
};
File diskCacheDir=getDiskCacheDir(mContext,"bitmap"); //在sd card中的位置
if(!diskCacheDir.exists()){
diskCacheDir.mkdirs();
}
// 如果磁盘剩余空间小于DISK_CACHE_SIZE无法创建DiskLruCache
if(getUsableSpace(diskCacheDir)>DISK_CACHE_SIZE){
try {
mDiskLruCache=DiskLruCache.open(diskCacheDir,1,1,DISK_CACHE_SIZE);
} catch (IOException e) {
e.printStackTrace();
}
mIsDiskLruCacheCreated=true;
}
}
private long getUsableSpace(File diskCacheDir) {
if(Build.VERSION.SDK_INT>= Build.VERSION_CODES.GINGERBREAD){
return diskCacheDir.getUsableSpace();
}
final StatFs statFs=new StatFs(diskCacheDir.getPath());
return statFs.getBlockSize()*statFs.getAvailableBlocks();
}
private File getDiskCacheDir(Context context, String Name) {
boolean externalStorageAvailable= Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
final String cachePath;
if(externalStorageAvailable){
cachePath=context.getExternalCacheDir().getPath();
}else{
cachePath=context.getCacheDir().getPath();
}
return new File(cachePath+File.separator+Name);
}
}
内存缓存的添加和获取:
private void addBitmapToMemoryCache(String key,Bitmap bitmap){
if(getBitmapFromMemoryCache(key)==null){
mLruCache.put(key,bitmap);
}
}
private Bitmap getBitmapFromMemoryCache(String key){
return mLruCache.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())
Log.e(TAG, "loadBitmapFromDiskCache: 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 fileDescripto=fileInputStream.getFD();
bitmap=mImageResizer.decodeSampledBitmapFromFileDescriptor(fileDescripto,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 {
final 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();
try {
out.close();
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}
//图片的url可能会有特殊字符
private String hashKeyFormUrl(String url) {
String cacheKey;
try {
final MessageDigest messageDigest=MessageDigest.getInstance("MD5");
messageDigest.update(url.getBytes());
cacheKey=bytesToHexString(messageDigest.digest());
} catch (NoSuchAlgorithmException e) {
cacheKey=String.valueOf(url.hashCode());
}
return cacheKey;
}
private String bytesToHexString(byte[] bytes) {
StringBuilder stringBuilder=new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
String hex=Integer.toHexString(0xFF&bytes[i]);
if(hex.length()==1)stringBuilder.append('0');
stringBuilder.append(hex);
}
return stringBuilder.toString();
}
同步加载和异步加载接口的设计:
/**
* 同步加载接口需要外部在线程中调用,因为同步加载可能很耗时
*/
public Bitmap loadBitmap(String uri,int reqWidth,int reqHeight){
Bitmap bitmap=loadBitmapFromMemCache(uri);//首先尝试从内存获取图片
if (bitmap!=null){
Log.e(TAG, "loadBitmap:loadBitmapFromMemCache,url: "+uri );
return bitmap;
}
try {
bitmap=loadBitmapFromDiskCache(uri,reqWidth,reqHeight);//再尝试从磁盘获取
if (bitmap!=null){
Log.e(TAG, "loadBitmap:loadBitmapFromDiskCache,url: "+uri );
return bitmap;
}
bitmap=loadBitmapFromHttp(uri,reqWidth,reqHeight);//再尝试从网络拉取
Log.e(TAG, "loadBitmap: loadBitmapFromHttp"+uri );
} catch (IOException e) {
e.printStackTrace();
}
if (bitmap==null&&!mIsDiskLruCacheCreated){
Log.e(TAG, "loadBitmap: 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;
}
// 如果没用从内存中获取到图片,就去线程池中调用loadBitmap()方法。
Runnable loadBitmapTask=new Runnable() {
@Override
public void run() {
Bitmap bitmap1=loadBitmap(uri,reqWidth,reqHeight);
if (bitmap1!=null){
// 图片加载成功之后,封装成一个LoadResult对象
LoaderResult result=new LoaderResult(imageView,uri,bitmap1);
// 通过Handler向主线程发送消息,在主线程中给imageVIew设置照片
mMainHandler.obtainMessage(MESSAGE_POST_RESULT,result).sendToTarget();
}
}
};
THREAD_POOL_EXECUTOR.execute(loadBitmapTask);
}
完整的ImageLoader:
package com.example.bitmap_demo;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Build;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.StatFs;
import android.support.annotation.NonNull;
import android.util.Log;
import android.util.LruCache;
import android.widget.ImageView;
import com.jakewharton.disklrucache.DiskLruCache;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiFunction;
/**
* 文件描述:
*
创建时间:2019/3/10
*/
public class ImageLoader {
private static final String TAG = "vera";
private static final int IO_BUFFER_SIZE = 8*1024;//8kb
private static final int TAG_KEY_URI =R.id.imageloader_uri;
private static final int MESSAGE_POST_RESULT = 1;
private boolean mIsDiskLruCacheCreated=false;
private Context mContext;
private LruCache<String , Bitmap> mLruCache;//内存缓存
private DiskLruCache mDiskLruCache;//磁盘缓存
private ImageResizer mImageResizer=new ImageResizer();
private static final int DISK_CACHE_SIZE = 50*1024*1024;//50M
private int DISK_CACHE_INDEX;
private static final int CPU_COUNT=Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE=CPU_COUNT+1;
private static final int MAX_POOL_SIZE=CPU_COUNT*2+1;
private static final long KEEP_ALIVE=10L;
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.getAndDecrement());
}
};
public static final Executor THREAD_POOL_EXECUTOR=new ThreadPoolExecutor(
CORE_POOL_SIZE,MAX_POOL_SIZE,
KEEP_ALIVE, TimeUnit.SECONDS,
new LinkedBlockingDeque<Runnable>(),sThreadFactory
);
private Handler mMainHandler=new Handler(Looper.getMainLooper()){
@Override
public void handleMessage(Message msg) {
LoaderResult result= (LoaderResult) msg.obj;
ImageView imageView=result.imageView;
String uri= (String) imageView.getTag(TAG_KEY_URI);
if (uri.equals(result.uri)){
imageView.setImageBitmap(result.bitmap);
}else {
Log.e(TAG, "handleMessage: set image bitmap,but url has changed,ignores!" );
}
}
};
public ImageLoader(Context context) {
mContext = context;
int maxMemory= (int) (Runtime.getRuntime().maxMemory()/1024);//单位是kb,当前进程可用内存
int cacheSize=maxMemory/8;
mLruCache=new LruCache<String,Bitmap>(cacheSize){
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes()*value.getHeight()/1024;//单位是kb,返回Bitmap对象的大小
}
};
File diskCacheDir=getDiskCacheDir(mContext,"bitmap"); //在sd card中的位置
if(!diskCacheDir.exists()){
diskCacheDir.mkdirs();
}
// 如果磁盘剩余空间小于DISK_CACHE_SIZE无法创建DiskLruCache
if(getUsableSpace(diskCacheDir)>DISK_CACHE_SIZE){
try {
mDiskLruCache=DiskLruCache.open(diskCacheDir,1,1,DISK_CACHE_SIZE);
} catch (IOException e) {
e.printStackTrace();
}
mIsDiskLruCacheCreated=true;
}
}
public static ImageLoader build(Context context){
return new ImageLoader(context);
}
private long getUsableSpace(File diskCacheDir) {
if(Build.VERSION.SDK_INT>= Build.VERSION_CODES.GINGERBREAD){
return diskCacheDir.getUsableSpace();
}
final StatFs statFs=new StatFs(diskCacheDir.getPath());
return statFs.getBlockSize()*statFs.getAvailableBlocks();
}
private File getDiskCacheDir(Context context, String Name) {
boolean externalStorageAvailable= Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
final String cachePath;
if(externalStorageAvailable){
cachePath=context.getExternalCacheDir().getPath();
}else{
cachePath=context.getCacheDir().getPath();
}
return new File(cachePath+File.separator+Name);
}
private void addBitmapToMemoryCache(String key,Bitmap bitmap){
if(getBitmapFromMemoryCache(key)==null){
mLruCache.put(key,bitmap);
}
}
private Bitmap getBitmapFromMemoryCache(String key){
return mLruCache.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())
Log.e(TAG, "loadBitmapFromDiskCache: 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 fileDescripto=fileInputStream.getFD();
bitmap=mImageResizer.decodeSampledBitmapFromFileDescriptor(fileDescripto,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 {
final 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();
try {
out.close();
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}
//图片的url可能会有特殊字符
private String hashKeyFormUrl(String url) {
String cacheKey;
try {
final MessageDigest messageDigest=MessageDigest.getInstance("MD5");
messageDigest.update(url.getBytes());
cacheKey=bytesToHexString(messageDigest.digest());
} catch (NoSuchAlgorithmException e) {
cacheKey=String.valueOf(url.hashCode());
}
return cacheKey;
}
private String bytesToHexString(byte[] bytes) {
StringBuilder stringBuilder=new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
String hex=Integer.toHexString(0xFF&bytes[i]);
if(hex.length()==1)stringBuilder.append('0');
stringBuilder.append(hex);
}
return stringBuilder.toString();
}
/**
* 同步加载接口需要外部在线程中调用,因为同步加载可能很耗时
*/
public Bitmap loadBitmap(String uri,int reqWidth,int reqHeight){
Bitmap bitmap=loadBitmapFromMemCache(uri);//首先尝试从内存获取图片
if (bitmap!=null){
Log.e(TAG, "loadBitmap:loadBitmapFromMemCache,url: "+uri );
return bitmap;
}
try {
bitmap=loadBitmapFromDiskCache(uri,reqWidth,reqHeight);//再尝试从磁盘获取
if (bitmap!=null){
Log.e(TAG, "loadBitmap:loadBitmapFromDiskCache,url: "+uri );
return bitmap;
}
bitmap=loadBitmapFromHttp(uri,reqWidth,reqHeight);//再尝试从网络拉取
Log.e(TAG, "loadBitmap: loadBitmapFromHttp"+uri );
} catch (IOException e) {
e.printStackTrace();
}
if (bitmap==null&&!mIsDiskLruCacheCreated){
Log.e(TAG, "loadBitmap: 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;
}
// 如果没用从内存中获取到图片,就去线程池中调用loadBitmap()方法。
Runnable loadBitmapTask=new Runnable() {
@Override
public void run() {
Bitmap bitmap1=loadBitmap(uri,reqWidth,reqHeight);
if (bitmap1!=null){
// 图片加载成功之后,封装成一个LoadResult对象
LoaderResult result=new LoaderResult(imageView,uri,bitmap1);
// 通过Handler向主线程发送消息,在主线程中给imageVIew设置照片
mMainHandler.obtainMessage(MESSAGE_POST_RESULT,result).sendToTarget();
}
}
};
THREAD_POOL_EXECUTOR.execute(loadBitmapTask);
}
private Bitmap downloadBitmapFromUrl(String urlString) {
Bitmap bitmap=null;
HttpURLConnection urlConnection=null;
BufferedInputStream in=null;
try {
final URL url=new URL(urlString);
urlConnection= (HttpURLConnection) url.openConnection();
in=new BufferedInputStream(urlConnection.getInputStream(),IO_BUFFER_SIZE);
bitmap= BitmapFactory.decodeStream(in);
} catch (IOException e) {
Log.e(TAG, "downloadBitmapFromUrl: error " +e);
}finally {
if(urlConnection!=null)urlConnection.disconnect();
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return bitmap;
}
private Bitmap loadBitmapFromMemCache(String url) {
final String key=hashKeyFormUrl(url);
Bitmap bitmap=getBitmapFromMemoryCache(key);
return bitmap;
}
private static class LoaderResult {
public ImageView imageView;
public String uri;
public Bitmap bitmap;
public LoaderResult(ImageView imageView, String uri, Bitmap bitmap) {
this.imageView = imageView;
this.uri = uri;
this.bitmap = bitmap;
}
}
}