Bitmap,表示位图,由像素点构成。Bitmap的承载容器是jpg、png等格式的文件,是对bitmap的压缩。当jpg、png等文件需要展示在手机上的控件时,就会解析成Bitmap并绘制到view上。通常处理图片时要避免过多的内存使用,毕竟移动设备的内存有限。
那么加载一张图片需要占用多大内存呢?考虑到效率加载图片时缓存策略是怎样的呢?
原始计算公式如下:
Bitmap的内存 = 分辨率 * 像素点的大小
详细理解参考Android中一张图片占据的内存大小是如何计算
Bitmap的加载,可过系统提供的BitmapFactory四个方法:decodeFile、decodeResource、decodeStream、decodeByteArray,对应处理从 文件、资源、流、字节数组 来加载Bitmap。
如何优化加载呢?由公式可见想要减少图片加载成Bitmap时占用的内存,两个方法:
针对第二点,降低分辨率,BitmapFactory.Options也提供了对应的方法,步骤如下:
代码如下:
private void initView() {
//R.mipmap.blue放在Res的xxh(480dpi)中,测试手机dpi也是480
//1、inJustDecodeBounds设为true,并加载图片
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.mipmap.blue, options);
//2、获取原始宽高信息
int outWidth = options.outWidth;
int outHeight = options.outHeight;
Log.i(TAG, "initView: outWidth="+outWidth+",outHeight="+outHeight);
//3、原始宽高信息 和 view的大小 计算并设置采样率
ViewGroup.LayoutParams layoutParams = ivBitamp.getLayoutParams();
int inSampleSize = getInSampleSize(layoutParams.width, layoutParams.height, outWidth, outHeight);
options.inSampleSize = inSampleSize;
Log.i(TAG, "initView: inSampleSize="+options.inSampleSize);
//4、inJustDecodeBounds设为false,并加载图片
options.inJustDecodeBounds = false;
Bitmap bitmap= BitmapFactory.decodeResource(getResources(), R.mipmap.blue, options);
Log.i(TAG, "initView: size="+bitmap.getByteCount());
int density = bitmap.getDensity();
Log.i(TAG, "initView: density="+density);
Log.i(TAG, "initView: original size="+337*222*4);
Log.i(TAG, "initView: calculated size="+ (337/inSampleSize) *(222/inSampleSize)* density/480 *4);
//绘制到view
ivBitamp.setImageBitmap(bitmap);
}
/**
* 计算采样率
* @param width view的宽
* @param height view的高
* @param outWidth 图片原始的宽
* @param outHeight 图片原始的高
* @return
*/
private int getInSampleSize(int width, int height, int outWidth, int outHeight) {
int inSampleSize = 1;
if (outWidth>width || outHeight>height){
int halfWidth = outWidth / 2;
int halfHeight = outHeight / 2;
//保证采样后的宽高都不小于目标快高,否则会拉伸而模糊
while (halfWidth/inSampleSize >=width
&& halfHeight/inSampleSize>=height){
//采样率一般取2的指数
inSampleSize *=2;
}
}
return inSampleSize;
}
}
缓存策略在Android中应用广泛。使用缓存可以节省流量、提高效率。
加载图片时,一般会从网络加载,然后缓存在存储设备上,这样下次就不用请求网络了。并且通常也会缓存一份到内存中,这样下次可以直接取内存中的缓存,要比从存储设备中取快很多。所以一般是先从内存中取,内存没有就取存储设备,也没有才会请求网络,这就是所谓的“三级缓存”。此策略同样适用其他文件类型。
缓存策略中的操作有 添加缓存、获取缓存、删除缓存。添加和获取比较好理解,删除缓存是啥意思?因为缓存大小是有限制的,像移动设备的 内存 和 设备存储都是有限的,不能无限制的添加,只能限定一个最大缓存,到达最大时就会删除一部分缓存。但是删除哪一部分缓存呢?删除 缓存创建时间最老的吗,如果它经常用到呢,好像不太完美,当然这也是一种缓存算法。
目前经典的缓存算法是LRU(Least Recently Used),最近最少使用。具体就是 当缓存满时,会先删除那些 近期 最少使用 的缓存。使用LRU算法的缓存有两种,LruCache和DiskLruCache,LruCache是使用内存缓存,DiskLruCache是实现磁盘缓存。
LruCache是泛型类,使用方法如下:
提供最大缓存容量,创建LruCache实例,并重写其sizeOf方法来计算缓存对象的大小。最大容量和缓存对象大小单位要一致。
private void testLruCache() {
//当前进程的最大内存,单位M
long maxMemory = Runtime.getRuntime().maxMemory() / 1024 / 1024;
//取进程内存的1/8
int cacheMaxSize = (int) (maxMemory/8);
//创建Bitmap实例
mBitmapLruCache = new LruCache<String, Bitmap>(cacheMaxSize){
@Override
protected int sizeOf(String key, Bitmap value) {
//缓存对象bitmap的大小,单位M
return value.getByteCount()/1024/1024;
}
@Override
protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
//移除旧缓存时会调用,可以在这里进行像资源回收的工作。
//evicted为true,表示此处移除是因为快满了要腾出空间
}
};
//添加缓存
mBitmapLruCache.put("1",mBitmap);
//获取缓存
Bitmap bitmap = mBitmapLruCache.get("1");
ivBitamp.setImageBitmap(bitmap);
//删除缓存,一般不会用,因为快满时会自动删近期最少使用的缓存,就是它的核心功能
mBitmapLruCache.remove("1");
}
可见使用很简单,那么LruCache是怎么完成 删除“近期最少使用” 的呢?看下LruCache的代码:
public class LruCache<K, V> {
//此map以强引用的方式存储缓存对象
private final LinkedHashMap<K, V> map;
//当前缓存的大小(带单位的)
private int size;
//缓存最大容量(带单位的)
private int maxSize;
...
public LruCache(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
this.maxSize = maxSize;
//LinkedHashMap是按照 访问顺序 排序的,所以get、put操作都会把要存的k-v放在队尾
this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
}
/**
* 获取缓存,同时会把此k-v放在链表的尾部
*/
public final V get(K key) {
if (key == null) {
throw new NullPointerException("key == null");
}
V mapValue;
//get是线程安全的操作
synchronized (this) {
//LinkedHashMap的get方法中调afterNodeAccess,会移到链表尾部
mapValue = map.get(key);
if (mapValue != null) {
hitCount++;
return mapValue;
}
missCount++;
}
...
}
/**
* 缓存key-value,value会存在 队尾
* @return 之前也是这个key存的value
*/
public final V put(K key, V value) {
if (key == null || value == null) {
//不允许 null key、null value
throw new NullPointerException("key == null || value == null");
}
V previous;
//可见put操作是线程安全的
synchronized (this) {
putCount++;
size += safeSizeOf(key, value);
//强引用存入map(不会被动地被系统回收),其因为是LinkedHashMap,会放在队尾
previous = map.put(key, value);
if (previous != null) {
//如果前面已这个key,那么替换后调整下当前缓存大小
size -= safeSizeOf(key, previous);
}
}
if (previous != null) {
entryRemoved(false, key, previous, value);
}
//重新调整大小
trimToSize(maxSize);
return previous;
}
/**
* 比较 当前已缓存的大小 和最大容量,决定 是否删除
*/
private void trimToSize(int maxSize) {
while (true) {
K key;
V value;
synchronized (this) {
if (size < 0 || (map.isEmpty() && size != 0)) {
throw new IllegalStateException(getClass().getName()
+ ".sizeOf() is reporting inconsistent results!");
}
if (size <= maxSize) {
//大小还没超过最大值
break;
}
//已经达到最大容量
//因为是访问顺序,所以遍历的最后一个就是最近没有访问的,那么就可以删掉它了!
Map.Entry<K, V> toEvict = null;
for (Map.Entry<K, V> entry : map.entrySet()) {
toEvict = entry;
}
// END LAYOUTLIB CHANGE
if (toEvict == null) {
break;
}
key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);
size -= safeSizeOf(key, value);
evictionCount++;
}
//因为是为了腾出空间,所以这个回调第一个参数是true
entryRemoved(true, key, value, null);
}
}
protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}
...
}
由以上代码及注释,可见LruCache的算法实现是依靠 设置了访问顺序的LinkedHashMap。因为是访问顺序模式,get、put操作都会调整k-v到链表尾部。在缓存将满时,遍历LinkedHashMap,因为是访问顺序模式,所以遍历的最后一个就是最近没有使用的,然后删除即可。
DiskLruCache是实现磁盘缓存,所以需要设备存储的读写权限;一般是从网络请求图片后缓存到磁盘中,所以还需要网络权限。
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
DiskLruCache,不是官方提供,所以需要引入依赖:
implementation 'com.jakewharton:disklrucache:2.0.2'
通过查看源码,发现LinkedHashMap内部也是维护了访问顺序的LinkedHashMap,原理上和LruCache是一致的。只是使用上有点点复杂,毕竟涉及文件的读写。
具体使用及注意点如下代码:
private void testDiskLruCache(String urlString) {
long maxSize = 50*1024*1024;
try {
//一、创建DiskLruCache
//第一个参数是要存放的目录,这里选择外部缓存目录(若app卸载此目录也会删除);
//第二个是版本一般设1;第三个是缓存节点的value数量一般也是1;
//第四个是最大缓存容量这里取50M
mDiskLruCache = DiskLruCache.open(getExternalCacheDir(), 1, 1, maxSize);
//二、缓存的添加:1、通过Editor,把图片的url转成key,通过edit方法得到editor,然后获取输出流,就可以写到文件系统了。
DiskLruCache.Editor editor = mDiskLruCache.edit(hashKeyFormUrl(urlString));
if (editor != null) {
//参数index取0(因为上面的valueCount取的1)
OutputStream outputStream = editor.newOutputStream(0);
boolean downSuccess = downloadPictureToStream(urlString, outputStream);
if (downSuccess) {
//2、编辑提交,释放编辑器
editor.commit();
}else {
editor.abort();
}
//3、写到文件系统,会检查当前缓存大小,然后写到文件
mDiskLruCache.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
//三、缓存的获取
try {
String key = hashKeyFormUrl(urlString);
DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
FileInputStream inputStream = (FileInputStream)snapshot.getInputStream(0);
// Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
// mIvBitamp.setImageBitmap(bitmap);
//注意,一般需要采样加载,但文件输入流是有序的文件流,采样时两次decodeStream影响文件流的文职属性,导致第二次decode是获取是null
//为解决此问题,可用文件描述符
FileDescriptor fd = inputStream.getFD();
//采样加载(就是前面讲的bitmap的高效加载)
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds=true;
BitmapFactory.decodeFileDescriptor(fd,null,options);
ViewGroup.LayoutParams layoutParams = mIvBitamp.getLayoutParams();
options.inSampleSize = getInSampleSize(layoutParams.width, layoutParams.height, options.outWidth, options.outHeight);
options.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeFileDescriptor(fd, null, options);
//存入内容缓存,绘制到view。(下次先从内存缓存获取,没有就从磁盘缓存获取,在没有就请求网络--"三级缓存")
mBitmapLruCache.put(key,bitmap);
runOnUiThread(new Runnable() {
@Override
public void run() {
mIvBitamp.setImageBitmap(mBitmapLruCache.get(key));
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 下载图片到文件输入流
*/
private boolean downloadPictureToStream(String urlString, OutputStream outputStream) {
URL url = null;
HttpURLConnection urlConnection = null;
BufferedInputStream in = null;
BufferedOutputStream out = null;
try {
url = new URL(urlString);
urlConnection = (HttpURLConnection) url.openConnection();
in = new BufferedInputStream(urlConnection.getInputStream());
out = new BufferedOutputStream(outputStream);
int b;
while ((b=in.read()) != -1) {
//写入文件输入流
out.write(b);
}
return true;
} catch (IOException e) {
e.printStackTrace();
}finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
try {
if (in != null) {in.close();}
if (out != null) {out.close();}
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}
/**
* 图片的url转成key,使用MD5
*/
private String hashKeyFormUrl(String url) {
try {
MessageDigest digest = MessageDigest.getInstance("MD5");
return byteToHexString(digest.digest(url.getBytes()));
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
private String byteToHexString(byte[] bytes) {
StringBuffer stringBuffer = new StringBuffer();
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(0XFF & bytes[i]);
if (hex.length()==1) {
stringBuffer.append(0);
}
stringBuffer.append(hex);
}
return stringBuffer.toString();
}
前面说的 Bitmap的高效加载、LruCache、DiskLruCache,是一个图片加载框架必备的功能点。下面就来封装一个ImageLoader。首先罗列 实现的要点:
说明,
详细如下
public class ImageLoader {
private static final String TAG = "ImageLoader";
private static final long KEEP_ALIVE_TIME = 10L;
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_THREAD_SIZE = CPU_COUNT + 1;
private static final int THREAD_SIZE = CPU_COUNT * 2 + 1;
private static final int VIEW_TAG_URL = R.id.view_tag_url;
private static final Object object = new Object();
private ThreadPoolExecutor mExecutor;
private Handler mMainHandler;
private Context mApplicationContext;
private static volatile ImageLoader mImageLoader;
private LruCache<String, Bitmap> mLruCache;
private DiskLruCache mDiskLruCache;
/**
* 磁盘缓存最大容量,50M
*/
private static final long DISK_LRU_CACHE_MAX_SIZE = 50 * 1024 * 1024;
/**
* 当前进程的最大内存,取进程内存的1/8
*/
private static final long MEMORY_CACHE_MAX_SIZE = Runtime.getRuntime().maxMemory() / 8;
public ImageLoader(Context context) {
if (context == null) {
throw new RuntimeException("context can not be null !");
}
mApplicationContext = context.getApplicationContext();
initLruCache();
initDiskLruCache();
initAsyncLoad();
}
public static ImageLoader with(Context context){
if (mImageLoader == null) {
synchronized (object) {
if (mImageLoader == null) {
mImageLoader = new ImageLoader(context);
}
}
}
return mImageLoader;
}
private void initAsyncLoad() {
mExecutor = new ThreadPoolExecutor(CORE_THREAD_SIZE, THREAD_SIZE,
KEEP_ALIVE_TIME, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(), new ThreadFactory() {
private final AtomicInteger count = new AtomicInteger(1);
@Override
public Thread newThread(Runnable runnable) {
return new Thread(runnable, "load bitmap thread "+ count.getAndIncrement());
}
});
mMainHandler = new Handler(Looper.getMainLooper()){
@Override
public void handleMessage(@NonNull Message msg) {
LoadResult result = (LoadResult) msg.obj;
ImageView imageView = result.imageView;
Bitmap bitmap = result.bitmap;
String url = result.url;
if (imageView == null || bitmap == null) {
return;
}
//此判断是 避免 ImageView在列表中复用导致图片错位的问题
if (url.equals(imageView.getTag(VIEW_TAG_URL))) {
imageView.setImageBitmap(bitmap);
}else {
Log.w(TAG, "handleMessage: set image bitmap,but url has changed,ignore!");
}
}
};
}
private void initLruCache() {
mLruCache = new LruCache<String, Bitmap>((int) MEMORY_CACHE_MAX_SIZE){
@Override
protected int sizeOf(String key, Bitmap value) {
//缓存对象bitmap的大小,单位要和MEMORY_CACHE_MAX_SIZE一致
return value.getByteCount();
}
@Override
protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
//移除旧缓存时会调用,可以在这里进行像资源回收的工作。
}
};
}
private void initDiskLruCache() {
File externalCacheDir = mApplicationContext.getExternalCacheDir();
if (externalCacheDir != null) {
long usableSpace = externalCacheDir.getUsableSpace();
if (usableSpace < DISK_LRU_CACHE_MAX_SIZE){
//剩余空间不够了
Log.e(TAG, "initDiskLruCache: "+"UsableSpace="+usableSpace+" , not enough(target 50M),cannot creat diskLruCache!");
return;
}
}
//一、创建DiskLruCache
//第一个参数是要存放的目录,这里选择外部缓存目录(若app卸载此目录也会删除);
//第二个是版本一般设1;第三个是缓存节点的value数量一般也是1;
//第四个是最大缓存容量这里取50M
try {
this.mDiskLruCache = DiskLruCache.open(mApplicationContext.getExternalCacheDir(), 1, 1, DISK_LRU_CACHE_MAX_SIZE);
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, "initDiskLruCache: "+e.getMessage());
}
}
/**
* 缓存bitmap到内存
* @param url url
* @param bitmap bitmap
*/
private void addBitmapMemoryCache(String url, Bitmap bitmap) {
String key = UrlKeyTransformer.transform(url);
if (mLruCache.get(key) == null && bitmap != null) {
mLruCache.put(key,bitmap);
}
}
/**
* 从内存缓存加载bitmap
* @param url url
* @return
*/
private Bitmap loadFromMemoryCache(String url) {
return mLruCache.get(UrlKeyTransformer.transform(url));
}
/**
* 从磁盘缓存加载bitmap(并添加到内存缓存)
* @param url url
* @param requestWidth 要求的宽
* @param requestHeight 要求的高
* @return bitmap
*/
private Bitmap loadFromDiskCache(String url, int requestWidth, int requestHeight) throws IOException {
if (Looper.myLooper()==Looper.getMainLooper()) {
Log.w(TAG, "loadFromDiskCache from Main Thread may cause block !");
}
if (mDiskLruCache == null) {
return null;
}
DiskLruCache.Snapshot snapshot = null;
String key = UrlKeyTransformer.transform(url);
snapshot = mDiskLruCache.get(key);
if (snapshot != null) {
FileInputStream inputStream = (FileInputStream)snapshot.getInputStream(0);
//Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
//一般需要采样加载,但文件输入流是有序的文件流,采样时两次decodeStream影响文件流的位置属性,
//导致第二次decode是获取是null,为解决此问题,可用文件描述符。
FileDescriptor fd = inputStream.getFD();
Bitmap bitmap = BitmapSampleDecodeUtil.decodeFileDescriptor(fd, requestWidth, requestHeight);
addBitmapMemoryCache(url,bitmap);
return bitmap;
}
return null;
}
/**
* 从网路加载图片 到磁盘缓存(然后再从磁盘中采样加载)
* @param urlString urlString
* @param requestWidth 要求的宽
* @param requestHeight 要求的高
* @return Bitmap
*/
private Bitmap loadFromHttp(String urlString, int requestWidth, int requestHeight) throws IOException {
//线程检查,不能是主线程
if (Looper.myLooper()==Looper.getMainLooper()) {
throw new RuntimeException("Do not loadFromHttp from Main Thread!");
}
if (mDiskLruCache == null) {
return null;
}
DiskLruCache.Editor editor = null;
editor = mDiskLruCache.edit(UrlKeyTransformer.transform(urlString));
if (editor != null) {
OutputStream outputStream = editor.newOutputStream(0);
if (downloadBitmapToStreamFromHttp(urlString, outputStream)) {
editor.commit();
}else {
editor.abort();
}
mDiskLruCache.flush();
}
return loadFromDiskCache(urlString, requestWidth, requestHeight);
}
/**
* 从网络下载图片到文件输入流
* @param urlString
* @param outputStream
* @return
*/
private boolean downloadBitmapToStreamFromHttp(String urlString, OutputStream outputStream) {
URL url = null;
HttpURLConnection urlConnection = null;
BufferedInputStream in = null;
BufferedOutputStream out = null;
try {
url = new URL(urlString);
urlConnection = (HttpURLConnection) url.openConnection();
in = new BufferedInputStream(urlConnection.getInputStream());
out = new BufferedOutputStream(outputStream);
int b;
while ((b=in.read()) != -1) {
//写入文件输入流
out.write(b);
}
return true;
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, "downloadBitmapToStreamFromHttp,failed : "+e.getMessage());
}finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
IoUtil.close(in);
IoUtil.close(out);
}
return false;
}
/**
* 从网络直接下载bitmap(无缓存、无采样)
* @param urlString
* @return
*/
private Bitmap downloadBitmapFromUrlDirectly(String urlString) {
URL url;
HttpURLConnection urlConnection = null;
BufferedInputStream bufferedInputStream = null;
try {
url = new URL(urlString);
urlConnection = (HttpURLConnection) url.openConnection();
bufferedInputStream = new BufferedInputStream(urlConnection.getInputStream());
return BitmapFactory.decodeStream(bufferedInputStream);
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, "downloadBitmapFromUrlDirectly,failed : "+e.getMessage());
}finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
IoUtil.close(bufferedInputStream);
}
return null;
}
public Bitmap loadBitmap(String url){
return loadBitmap(url,0,0);
}
/**
* 同步 加载bitmap
*
* 不能在主线程执行。加载时 先从内存缓存获取,有就返回bitmap,若没有就从磁盘缓存获取;
* 磁盘缓存有就返回bitmap并缓存到内存缓存,没有就请求网络;
* 网络请求回来,就缓存到磁盘缓存,然后从磁盘缓存获取返回。
*
* @return Bitmap
*/
public Bitmap loadBitmap(String url, int requestWidth, int requestHeight){
Bitmap bitmap = loadFromMemoryCache(url);
if (bitmap != null) {
Log.d(TAG, "loadBitmap: loadFromMemoryCache, url:"+url);
return bitmap;
}
try {
bitmap = loadFromDiskCache(url, requestWidth, requestHeight);
} catch (IOException e) {
e.printStackTrace();
}
if (bitmap != null) {
Log.d(TAG, "loadBitmap: loadFromDiskCache, url:"+url);
return bitmap;
}
try {
bitmap = loadFromHttp(url, requestWidth, requestHeight);
} catch (IOException e) {
e.printStackTrace();
}
if (bitmap != null){
Log.d(TAG, "loadBitmap: loadFromHttp, url:"+url);
return bitmap;
}
if (mDiskLruCache == null) {
Log.d(TAG, "loadBitmap: diskLruCache is null,load bitmap from url directly!");
bitmap = downloadBitmapFromUrlDirectly(url);
}
return bitmap;
}
public void loadBitmapAsync(final String url, final ImageView imageView){
loadBitmapAsync(url,imageView,0,0);
}
/**
* 异步 加载bitmap
* 外部可在任意线程执行,因为内部实现是在子线程(线程池)加载,
* 并且内部会通过Handler切到主线程,只需要传入view,内部就可直接绘制Bitmap到view。
* @param url
* @param imageView
* @param requestWidth
* @param requestHeight
*/
public void loadBitmapAsync(final String url, final ImageView imageView, final int requestWidth, final int requestHeight){
if (url == null || url.isEmpty() || imageView == null) {
return;
}
// 标记当前imageView要绘制图片的url
imageView.setTag(VIEW_TAG_URL, url);
mExecutor.execute(new Runnable() {
@Override
public void run() {
Bitmap loadBitmap = loadBitmap(url, requestWidth, requestHeight);
Message message = Message.obtain();
message.obj = new LoadResult(loadBitmap, url, imageView);
mMainHandler.sendMessage(message);
}
});
}
}
/**
* Bitmap采样压缩加载工具
* @author hufeiyang
*/
public class BitmapSampleDecodeUtil {
private static final String TAG = "BitmapSampleDecodeUtil";
/**
* 对资源图片的采样
* @param resources resources
* @param resourcesId 资源id
* @param requestWidth view的宽
* @param requestHeight view的高
* @return 采样后的bitmap
*/
public static Bitmap decodeSampleResources(Resources resources, int resourcesId, int requestWidth, int requestHeight){
if (resources == null || resourcesId<=0) {
return null;
}
//1、inJustDecodeBounds设为true,并加载图片
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(resources, resourcesId, options);
//2、获取原始宽高信息
int outWidth = options.outWidth;
int outHeight = options.outHeight;
//3、原始宽高信息 和 view的大小 计算并设置采样率
options.inSampleSize = getInSampleSize(requestWidth, requestHeight, outWidth, outHeight);
//4、inJustDecodeBounds设为false,并加载图片
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(resources, resourcesId, options);
}
/**
* 对文件描述符的采样加载
* @param fileDescriptor fileDescriptor
* @param requestWidth view的宽
* @param requestHeight view的高
* 注意,文件输入流是有序的文件流,采样时两次decodeStream影响文件流的文职属性,导致第二次decode是获取是null。
* 为解决此问题,可用本方法对文件流的文件描述符 加载。
*/
public static Bitmap decodeFileDescriptor(FileDescriptor fileDescriptor, int requestWidth, int requestHeight){
if (fileDescriptor == null) {
return null;
}
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds=true;
BitmapFactory.decodeFileDescriptor(fileDescriptor,null,options);
options.inSampleSize = getInSampleSize(requestWidth, requestHeight, options.outWidth, options.outHeight);
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
}
/**
* 计算采样率
* @param width view的宽
* @param height view的高
* @param outWidth 图片原始的宽
* @param outHeight 图片原始的高
* @return
*/
private static int getInSampleSize(int width, int height, int outWidth, int outHeight) {
int inSampleSize = 1;
if (width==0 || height ==0){
return inSampleSize;
}
if (outWidth>width || outHeight>height){
int halfWidth = outWidth / 2;
int halfHeight = outHeight / 2;
//保证采样后的宽高都不小于目标快高,否则会拉伸而模糊
while (halfWidth/inSampleSize >=width
&& halfHeight/inSampleSize>=height){
inSampleSize *=2;
}
}
Log.d(TAG, "getInSampleSize: inSampleSize="+inSampleSize);
return inSampleSize;
}
}
/**
* 图片的url转成key
* @author hufeiyang
*/
public class UrlKeyTransformer {
/**
* 图片的url转成key
* @param url
* @return MD5转换后的key
*/
public static String transform(String url) {
if (url == null || url.isEmpty()) {
return null;
}
try {
MessageDigest digest = MessageDigest.getInstance("MD5");
return byteToHexString(digest.digest(url.getBytes()));
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
private static String byteToHexString(byte[] bytes) {
StringBuffer stringBuffer = new StringBuffer();
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(0XFF & bytes[i]);
if (hex.length()==1) {
stringBuffer.append(0);
}
stringBuffer.append(hex);
}
return stringBuffer.toString();
}
}
public class LoadResult {
public Bitmap bitmap;
/**
* bitmap对应的url
*/
public String url;
public ImageView imageView;
public LoadResult(Bitmap bitmap, String url, ImageView imageView) {
this.bitmap = bitmap;
this.url = url;
this.imageView = imageView;
}
}
具体代码gitHub地址:SimpleImageLoader