概述
版权声明:本文为博主原创文章,未经博主允许不得转载。
本文纯个人学习笔记,由于水平有限,难免有所出错,有发现的可以交流一下。
一、图片压缩
Android 中图片是以 Bitmap 形式存在的,而且 Bitmap 是比较占内存的。所以,如果能对 Bitmap 进行压缩,对内存优化这块有很大的帮助。我们首先需要知道 Bitmap 占用内存的计算方式:
图片长上的像素点 * 图片宽上的像素点 * 一个像素点占用的字节数
从公式可以看出,只要我们减小 Bitmap 所占内存的三个因素之一,都可以压缩图片所占内存。
- Android 中 RGB 编码格式
RGB888(int):R、G、B分量各占8位
RGB565(short):R、G、B分量分别占5、6、5位
RGB555(short):RGB 分量都用5位表示(剩下的1位不用)
ARGB8888(int):A、R、G、B分量各占8位
ARGB4444(short):A、R、G、B分量各占4位
- 图片存在形式
在安卓中,图片有三种形式存在。
1.文件形式(以二进制形式保存在SD 卡)
2.流的形式(以二进制形式存在于内存)
3.Bitmap 形式
一般来说,手机 SD 卡上的文件形式图片,与内存中流形式的图片,大小相同。不过,当图片以 Bitmap 形式存在时,其占用的内存会变得很大。
查看大小:
文件形式: file.length()
流的形式: 查看流的 byte 个数
Bitmap:bitmap.getByteCount()
- 图片压缩
由上面可以知道,图片可能存在 SD 卡里,或者存在于内存,对于这两种存在形式都可以进行压缩,分别称为文件压缩和内存压缩。
文件压缩: 为了减小图片文件的大小,比如上传图片到服务器,减少本地图片存储空间。
内存压缩: 这块主要是对 Bitmap 所占内存进行压缩,减少内存占用。
二、文件压缩
文件压缩有三种:
质量压缩
尺寸压缩
格式选择:JPEG/WEBP (4.0以上)
1.质量压缩
质量压缩是一种有损压缩,在压缩过程中,图片会丢失一些图片信息,变得不清晰。
代码:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.timg);
compress(bitmap, Bitmap.CompressFormat.JPEG, 100);
compress(bitmap, Bitmap.CompressFormat.JPEG, 70);
compress(bitmap, Bitmap.CompressFormat.JPEG, 50);
compress(bitmap, Bitmap.CompressFormat.JPEG, 30);
compress(bitmap, Bitmap.CompressFormat.JPEG, 0);
}
/**
* 压缩图片到指定文件
* @param bitmap 待压缩图片
* @param format 压缩的格式
* @param quality 质量
*/
private boolean compress(Bitmap bitmap, Bitmap.CompressFormat format, int quality){
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
bitmap.compress(format, quality, outputStream);
byte[] bytes = outputStream.toByteArray();
bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
Log.i("MainActivity", " 111 zx 压缩后图片大小:" + bitmap.getByteCount()
+ " 宽度:" + bitmap.getWidth() + " 高度:" + bitmap.getHeight()
+ " bytes.length= " + (bytes.length / 1024) + "KB"
+ " quality=" + quality);
if(outputStream != null){
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}
结果:
可以发现,进行压缩后,图片的大小,宽高是没有改变的。这是因为进行质量压缩的时候,并不会减少图片的像素,只是改变图片的位深和透明度。图片的宽、高、每个像素所占字节都没有改变,所以生成的 Bitmap 所占内存是不会改变的。
这边主要用到了 Bitmap 的 compress 方法。
public boolean compress(CompressFormat format, int quality, OutputStream stream)
format: Bitmap 内部类的枚举值,JPEG、PNG、WEBP。
quality: 图片质量,quality 越小,压缩后的图片二进制数据越短,也越容易丢失图片信息。
stream: 压缩后数据流。
注: PNG 图片是无损的,不能进行压缩。如果保存图片格式选择 PNG,那么 quality 失效。
也可以使用下面这段代码,把图片保存在 SD 卡中,然后进行查看,可以发现 quality 越小,图片越模糊。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.timg);
compress(bitmap, Bitmap.CompressFormat.JPEG, 100,Environment.getExternalStorageDirectory()+"/test_scaled100.jpeg");
compress(bitmap, Bitmap.CompressFormat.JPEG, 70,Environment.getExternalStorageDirectory()+"/test_scaled70.jpeg");
compress(bitmap, Bitmap.CompressFormat.JPEG, 50,Environment.getExternalStorageDirectory()+"/test_scaled50.jpeg");
compress(bitmap, Bitmap.CompressFormat.JPEG, 30,Environment.getExternalStorageDirectory()+"/test_scaled30.jpeg");
compress(bitmap, Bitmap.CompressFormat.JPEG, 0,Environment.getExternalStorageDirectory()+"/test_scaled0.jpeg");
}
/**
* 压缩图片到指定文件
* @param bitmap 待压缩图片
* @param format 压缩的格式
* @param quality 质量
* @param path 文件地址
*/
private void compress(Bitmap bitmap, Bitmap.CompressFormat format, int quality, String path){
FileOutputStream outputStream = null;
try {
outputStream = new FileOutputStream(path);
bitmap.compress(format, quality, outputStream);
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if(outputStream != null){
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
小结:
进行质量压缩不会改变图片的大小以及图片编码格式,所以图片生成的 Bitmap 所占内存不会改变,无法达到内存压缩。
quality 越小,图片文件内存越小, 图片信息丢失越严重,失真越明显。
2.尺寸压缩
很明显就是减小图片的分辨率,从而达到压缩的目的。
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.timg);
/**
* 尺寸压缩
*/
Matrix matrix = new Matrix();
float scale = 0.5f;
//scale = 缩放大小 / 原大小
matrix.setScale(scale,scale);
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
这是对图片进行缩放,压缩图片的宽、高,可以把这个 Bitmap 保存成图片文件。对于图片文件来说,宽、高变小了,所占 SD 卡空间自然也变小了。而且宽、高是 Bitmap 所占内存的两大因素,所以压缩后图片生成的 Bitmap 所占内存也变小了。
3.格式选择 JPEG/WEBP
在调用 Bitmap 的 compress 方法时,可以进行格式设置。
JPEG 格式的图片文件比 PNG 格式的图片文件小,而 WEBP 格式的图片文件比 JPEG 格式的图片文件更小。但是,一般上传到服务器的图片采用 JPEG,WEBP 格式目前应用还没有特别广。
对比相同 quality 下,JPEG 和 WEBP。
compress(bitmap, Bitmap.CompressFormat.JPEG, 100);
compress(bitmap, Bitmap.CompressFormat.JPEG, 70);
compress(bitmap, Bitmap.CompressFormat.JPEG, 50);
compress(bitmap, Bitmap.CompressFormat.JPEG, 30);
compress(bitmap, Bitmap.CompressFormat.JPEG, 0);
compress(bitmap, Bitmap.CompressFormat.WEBP, 100);
compress(bitmap, Bitmap.CompressFormat.WEBP, 70);
compress(bitmap, Bitmap.CompressFormat.WEBP, 50);
compress(bitmap, Bitmap.CompressFormat.WEBP, 30);
compress(bitmap, Bitmap.CompressFormat.WEBP, 0);
结果:
可以发现,不同格式的图片跟不同 quality 一样,图片文件大小不变,但是图片所生成的 Bitmap 占用的内存减小。
三、压缩原理
1.Bitmap 的 compress
compress:
public boolean compress(CompressFormat format, int quality, OutputStream stream) {
checkRecycled("Can't compress a recycled bitmap");
// do explicit check before calling the native method
if (stream == null) {
throw new NullPointerException();
}
if (quality < 0 || quality > 100) {
throw new IllegalArgumentException("quality must be 0..100");
}
StrictMode.noteSlowCall("Compression of a bitmap is slow");
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "Bitmap.compress");
boolean result = nativeCompress(mNativePtr, format.nativeInt,
quality, stream, new byte[WORKING_COMPRESS_STORAGE]);
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
return result;
}
private static native boolean nativeCompress(long nativeBitmap, int format,
int quality, OutputStream stream, byte[] tempStorage);
compress 方法调用到原生的 nativeCompress 方法,这个方法的具体实现是在安卓源码中:
/frameworks/base/core/jni/android/graphics/Bitmap.cpp
nativeCompress:
//nativeCompress 采用动态注册
{"nativeCompress", "(IIILjava/io/OutputStream;[B)Z", (void*)Bitmap_compress },
static bool Bitmap_compress(JNIEnv* env, jobject clazz, SkBitmap* bitmap,
int format, int quality,
jobject jstream, jbyteArray jstorage) {
SkImageEncoder::Type fm;
//判断图片格式
switch (format) {
case kJPEG_JavaEncodeFormat:
fm = SkImageEncoder::kJPEG_Type;
break;
case kPNG_JavaEncodeFormat:
fm = SkImageEncoder::kPNG_Type;
break;
case kWEBP_JavaEncodeFormat:
fm = SkImageEncoder::kWEBP_Type;
break;
default:
return false;
}
bool success = false;
if (NULL != bitmap) {
SkAutoLockPixels alp(*bitmap);
if (NULL == bitmap->getPixels()) {
return false;
}
SkWStream* strm = CreateJavaOutputStreamAdaptor(env, jstream, jstorage);
if (NULL == strm) {
return false;
}
//开始压缩
SkImageEncoder* encoder = SkImageEncoder::Create(fm);
if (NULL != encoder) {
success = encoder->encodeStream(strm, *bitmap, quality);
delete encoder;
}
delete strm;
}
return success;
}
SkImageEncoder 的具体类是 externalskiasrcimages 下的 SkImageDecoder_libpng.cpp、SkImageDecoder_libjpeg.cpp、SkImageDecoder_libwebp.cpp 等。
2.Skia引擎
Skia 官网
上面使用的其实就是 Skia 引擎,这是一款 Google 研发、开源的 C++ 二维图形库 。
在安卓中使用的是阉割的 Skia 版本,对 JPEG 的处理是基于 libjpeg,对 PNG 则是基于 libpng。在安卓早期,由于 CPU 吃紧,将 libjpeg 中的最优哈夫曼编码关闭了。
在安卓 7.0 的 externalskiasrcimagesSkImageDecoder_libjpeg.cpp 中有进行设置,7.0 之前的源码是没有这个设置。
cinfo.optimize_coding = TRUE;
这是 Android Studio 在创建项目时候,提供的版本选择帮助,可以看见目前市面上各版本的支持。很明显,只有少部分手机达到 Android 7.0 以上,也就是说,只有少部分手机可以使用 libjpeg 中的最优哈夫曼编码。
3.哈夫曼编码
主要思想: 采取可变长编码方式,对文件中出现次数多的字符采取比较短的编码,对于出现次数少的字符采取比较长的编码,可以有效地减小总的编码长度。
想了解跟多,具体可自行百度学习。
4.总结
libjpeg-turbo github地址
在 Android 7.0 之前,如果对图片文件大小有要求,自行下载 libjpeg-turbo 进行编译生成 so 库,然后编写 C 代码,使用 JNI 进行调用。这块属于 NDK 相关,内容较多,不在这里进行记录。
四、内存压缩
1.内存大小
在图片文件压缩中已经用到了图片生成的 Bitmap 所占内存使用方法 bitmap.getByteCount() 进行获取。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.timg);
compress(bitmap, Bitmap.CompressFormat.JPEG, 100);
}
/**
* 压缩图片到制定文件
* @param bitmap 待压缩图片
* @param format 压缩的格式
* @param quality 质量
*/
private boolean compress(Bitmap bitmap, Bitmap.CompressFormat format, int quality){
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
bitmap.compress(format, quality, outputStream);
byte[] bytes = outputStream.toByteArray();
bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
Log.i("MainActivity", " 111 zx 压缩后图片大小:" + bitmap.getByteCount()
+ " 宽度:" + bitmap.getWidth() + " 高度:" + bitmap.getHeight()
+ " bytes.length= " + (bytes.length / 1024) + "KB"
+ " quality=" + quality);
if(outputStream != null){
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}
打印结果:
Bitmap 占用内存的计算方式:
图片长上的像素点 * 图片宽上的像素点 * 一个像素点占用的字节数
Android 中 RGB 编码格式
RGB888(int):R、G、B分量各占8位
RGB565(short):R、G、B分量分别占5、6、5位
RGB555(short):RGB分量都用5位表示(剩下的1位不用)
ARGB8888(int):A、R、G、B分量各占8位
ARGB4444(short):A、R、G、B分量各占4位
在截图中,图片内存大小为 1093928,宽为 682, 高为 401。 1093928 = 682 * 401 * 4。这是因为采用的是 ARGB8888 编码格式,每个像素占用 4 个字节。
2.不同 drawer 文件夹
同样的代码,同一张图片,原先处于 drawable-xhdpi 文件夹下,现在我们移到 drawable 文件夹下。
可以发现,图片的宽、高、所占内存全部改变了。
我们获取 Bitmap 的方法是采用了 BitmapFactory 的 decodeResource,而这个会调用到 BitmapFactory 的 decodeResourceStream 方法。
BitmapFactory 的 decodeResourceStream:
public static Bitmap decodeResourceStream(Resources res, TypedValue value,
InputStream is, Rect pad, Options opts) {
validate(opts);
if (opts == null) {
opts = new Options();
}
if (opts.inDensity == 0 && value != null) {
final int density = value.density;
if (density == TypedValue.DENSITY_DEFAULT) {
opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;
} else if (density != TypedValue.DENSITY_NONE) {
opts.inDensity = density;
}
}
if (opts.inTargetDensity == 0 && res != null) {
opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
}
return decodeStream(is, pad, opts);
}
在这里会有个 Options 参数,如果为空,进行赋默认值。
BitmapFactory.Options 是控制解码图片参数,有两个参数。
inDensity: 表示这个 Bitmap 的像素密度,根据 drawable 目录
inTargetDensity: 表示要被画出来时的目标(屏幕)像素密度, getResources().getDisplayMetrics().densityDpi。
Bitmap 在创建的时候,会根据这两个参数进行图片的缩放。
注: 简单对比上面两次打印结果,可以发现,第二次内存是第一次的 4 倍多,这是非常可怕的事情。也就是说,如果我们图片放对了 drawer 文件夹,可以节省大量的内存。
3.inBitmap 复用
我们知道,在安卓中, Bitmap 是内存大户,在不使用的时候,经常需要对 Bitmap 进行手动的回收释放。由于每一个 Bitmap 内存通常都很大,如果说对每一个新的 Bitmap 都进行一次内存申请,使用后释放,容易引起较大的内存抖动。严重的话,释放不及时,频繁的触发 GC 进行会后,甚至内存溢出。
在 Android3.0 开始,系统在 BitmapFactory.Options 里引入了 inBitmap 机制来配合缓存机制(在后面)。如果在载入图片时传入了inBitmap 那么载入的图片就会使用 inBitmap 的内存,而不会去新申请一块内存。
看一个简单的例子:
BitmapFactory.Options options = new BitmapFactory.Options();
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.timg, options);
for (int i = 0; i < 100; i ++){
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.timg, options);
}
这是直接连续创建 100 个临时的 Bitmap,app 不停的申请内存。
占用内存:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inMutable = true;
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.timg, options);
for (int i = 0; i < 100; i ++){
options.inBitmap = bitmap;
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.timg, options);
}
在这里,使用了 inBitmap 复用,新建的 Bitmap 使用旧的 Bitmap 内存。
占用内存:
合理的利用 inBitmap ,可以减少 app 申请的 Bitmap 内存,以及减少多次申请内存,是否内存造成的抖动。
注意点:
1.可被复用的 Bitmap 必须设置 inMutable 为 true。
2.Android4.4(API 19) 之前只有格式为 jpg、png,同等宽高(要求苛刻),inSampleSize 为 1 的 Bitmap 才可以复用;
Android4.4(API 19) 之前被复用的 Bitmap 的 inPreferredConfig 会覆盖待分配内存的 Bitmap 设置的 inPreferredConfig;
Android4.4(API 19) 之后被复用的 Bitmap 的内存必须大于等于需要申请内存的 Bitmap的 内存;
3.通过bitmap复用,减少频繁申请内存带来的性能问题。
4.API-19 的 getAllocationByteCount
一般情况下 getByteCount() 与 getAllocationByteCount() 是相等的;
通过复用 Bitmap 来解码图片,如果被复用的 Bitmap 的内存比待分配内存的 Bitmap 大,那么 getByteCount() 表示新解码图片占用内存的大小(并非实际内存大小,实际大小是复用的那个 Bitmap 的大小),getAllocationByteCount() 表示被复用 Bitmap 占用的内存大小。所以可能 allocation 比 bytecount 大。
4.LruCache
LruCache 是 Android 提供的一个缓存工具类,使用 LRU 缓存淘汰算法。根据数据的历史访问记录来进行淘汰数据, “如果数据最近被访问过,那么将来被访问的几率也更高”。
1.新数据插入到链表头部;
2.每当缓存命中(即缓存数据被访问),则将数据移到链表头部;
3.当链表满的时候,将链表尾部的数据丢弃。
五、手写图片缓存
1.核心思路
1.图片有二级缓存,内存缓存和磁盘缓存。
2.使用 inBitmap 复用已经不用的 Bitmap 内存,避免多次申请释放内存。
3.使用类似 LruCache 的 DiskLruCache 进行磁盘缓存的实现(二者基本一样)。
流程:
1.加载图片时,先从内存缓存读取图片。存在的话直接读取。
2.内存缓存不存在的时候,从磁盘缓存进行读取。存在的话直接读取,并添加到内存缓存。
3.磁盘缓存也不存在的时候,开始加载图片,同时保存到内存缓存和磁盘缓存中。
4.内存缓存中图片释放时候,添加到复用队列中,复用队列使用软引用。
5.添加到内存缓存的时候,先判断复用队列中是否有可复用的内存,有则进行复用。
2.核心代码
ImageCache:
public class ImageCache {
private static ImageCache instance;
private LruCache<String, Bitmap> memoryCache;
// private DiskLruCache diskLruCache;
BitmapFactory.Options options = new BitmapFactory.Options();
//使用一个 Bitmap 复用池 来保存可被复用的Bitmap
private Set<WeakReference<Bitmap>> reusablePool;
/**
* 使用一个引用队列,来接收 Bitmap 复用池 reusablePool 中弱引用回收事件
* 主要是 Bitmap 在安卓 8.0 之后内存在 native,需要释放
*/
private Thread clearReferenceQueue;
private boolean shutDown;
//磁盘缓存
private DiskLruCache diskLruCache;
//单例模式
public static ImageCache getInstance() {
if (null == instance) {
synchronized (ImageCache.class) {
if (null == instance) {
instance = new ImageCache();
}
}
}
return instance;
}
private ReferenceQueue referenceQueue;
public ReferenceQueue<Bitmap> getReferenceQueue(){
if(referenceQueue == null){
referenceQueue = new ReferenceQueue<Bitmap>();
clearReferenceQueue = new Thread(new Runnable() {
@Override
public void run() {
while (!shutDown){
try {
Reference<Bitmap> reference = referenceQueue.remove();
Bitmap bitmap = reference.get();
//对 bitmap 进行 recycle
if(bitmap != null && !bitmap.isRecycled()){
bitmap.recycle();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
clearReferenceQueue.start();
}
return referenceQueue;
}
public void init(String path){
reusablePool = Collections.synchronizedSet(new HashSet<WeakReference<Bitmap>>());
ActivityManager am = (ActivityManager) App.getInstance().getSystemService(Context.ACTIVITY_SERVICE);
//获得程序可用最大内存,单位 M
int memoryClass = am.getMemoryClass();
//需要设置缓存最大的内存,一般根据 app 可用内存进行计算
memoryCache = new LruCache<String, Bitmap>(memoryClass / 8 * 1024 * 1024){
/**
*
* @param key
* @param value
* @return value占用的内存
*/
@Override
protected int sizeOf(String key, Bitmap value) {
// 由于使用过了复用,
// 使用 getAllocationByteCount 替代 getByteCount 进行内存获取
return value.getAllocationByteCount();
}
/**
* 当 Bitmap 从 Lru 中移除时 回调
* @param evicted
* @param key
* @param oldValue
* @param newValue
*/
@Override
protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
//在回收的时候,判断其是否可复用
if(oldValue.isMutable()){
/**
* Bitmap 在安卓中内存位置:
* 3.0 以下 native
* 3.0 Java
* 8.0 native
* 3.0 以下可以不管,
* 3.0 之后,内存由虚拟机管理
* 8.0 之后,在 oldValue 回收的时候,需要通知释放 native 内存。
*/
Log.i("Adapter","加入复用池");
reusablePool.add(new WeakReference<Bitmap>(oldValue, getReferenceQueue()));
} else {
oldValue.recycle();
}
}
};
try {
// 第一个参数 directory:缓存文件
// 第二个参数 appVersion:app 版本
// 第三个参数 valueCount:表示一个 key 对应 valueCount 个文件
// 第四个参数 maxSize:初始化磁盘缓存,默认设置 10 M
diskLruCache = DiskLruCache.open(new File(path), BuildConfig.VERSION_CODE,
1, 10 * 1024 * 1024);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 保存 Bitmap 到内存
* @param key
* @param bitmap
*/
public void putBitmap2Memory(String key, Bitmap bitmap) {
memoryCache.put(key, bitmap);
}
/**
* 从内存获取 Bitmap
* @param key
* @return
*/
public Bitmap getBitmapFromMemory(String key) {
return memoryCache.get(key);
}
/**
* 清除内存 Bitmap 缓存
* @return
*/
public void clearMemory() {
memoryCache.evictAll();
}
/**
* 加入磁盘缓存
* @param key
* @param bitmap
*/
public void putBitMap2Disk(String key,Bitmap bitmap){
DiskLruCache.Snapshot snapshot = null;
OutputStream os = null;
try {
snapshot = diskLruCache.get(key);
// 如果缓存有对应 key 的文件,那么不管(也可以替换)
if (null == snapshot){
DiskLruCache.Editor edit = diskLruCache.edit(key);
if (null != edit){
os = edit.newOutputStream(0);
bitmap.compress(Bitmap.CompressFormat.JPEG,50,os);
edit.commit();
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != snapshot){
snapshot.close();
}
if (null != os){
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 从磁盘缓存获取 对应key缓存的图片
* @param key
* @return
*/
public Bitmap getBitmapFromDisk(String key,Bitmap reusable){
DiskLruCache.Snapshot snapshot = null;
Bitmap bitmap = null;
try {
snapshot = diskLruCache.get(key);
if (null == snapshot){
return null;
}
//获得文件输入流 读取 bitmap
InputStream is = snapshot.getInputStream(0);
//为了能够被复用内存
options.inMutable = true;
options.inBitmap = reusable;
Log.i("Adapter","使用复用内存:"+reusable);
bitmap = BitmapFactory.decodeStream(is,null,options);
if (null != bitmap){
memoryCache.put(key,bitmap);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != snapshot){
snapshot.close();
}
}
return bitmap;
}
/**
* 从复用池获取可复用的内存
*
* 可被复用的 Bitmap 必须设置 inMutable 为 true;
* Android4.4(API 19) 之前只有格式为 jpg、png,同等宽高(要求苛刻),
* inSampleSize 为 1 的 Bitmap 才可以复用;
* Android4.4(API 19) 之前被复用的 Bitmap 的 inPreferredConfig
* 会覆盖待分配内存的 Bitmap 设置的 inPreferredConfig;
* Android4.4(API 19) 之后被复用的 Bitmap 的内存
* 必须大于等于需要申请内存的 Bitmap 的内存;
*
* @param w 新 Bitmap 的宽度
* @param h 新 Bitmap 的高度
* @param inSampleSize 缩放系数
* @return 可复用的内存
*/
public Bitmap getReusable(int w,int h,int inSampleSize){
//忽略 15 之前的,现在比较少手机使用这个系统
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB){
return null;
}
Bitmap reusable = null;
Iterator<WeakReference<Bitmap>> iterator = reusablePool.iterator();
//迭代查找符合复用条件的Bitmap
while (iterator.hasNext()){
Bitmap bitmap = iterator.next().get();
if (null != bitmap){
//可以被复用
if (checkInBitmap(bitmap, w, h, inSampleSize)){
reusable = bitmap;
//移出复用池
iterator.remove();
break;
}
}else{
iterator.remove();
}
}
return reusable;
}
/**
* 判断 bitmap 所在内存是否满足新 Bitmap 复用
* @param bitmap
* @param w
* @param h
* @param inSampleSize
* @return
*/
boolean checkInBitmap(Bitmap bitmap,int w,int h,int inSampleSize){
//API 19 之前的判断
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT){
return bitmap.getWidth() == w && bitmap.getHeight() == h
&& inSampleSize == 1;
}
//如果缩放系数大于1 获得缩放后的宽与高
if (inSampleSize > 1){
w /= inSampleSize;
h /= inSampleSize;
}
int byteCout = w * h * getPixelsCout(bitmap.getConfig());
return byteCout <= bitmap.getAllocationByteCount();
}
/**
* 根据 Bitmap.Config 获取每个像素占几个字节
* @param config Bitmap 的格式
* @return 占用字节数
*/
private int getPixelsCout(Bitmap.Config config){
if (config == Bitmap.Config.ARGB_8888){
return 4;
}
//除了 ARGB_8888,其他基本都是 2 字节,有特殊的自行添加
return 2;
}
}
3.效果
未使用缓存:
使用缓存:
明显使用缓存的效果比没有使用缓存的效果流畅。
4.总结
这边的代码主要是学习是按思路进行编写的,大体上没有问题。实际项目中可以直接使用现在网络上的图片加载框架:Picasso、Glide 等。
六、附
代码链接
最后
以上就是体贴飞鸟为你收集整理的(五)图片压缩 —— 优化图片文件、内存一、图片压缩二、文件压缩三、压缩原理四、内存压缩五、手写图片缓存六、附的全部内容,希望文章能够帮你解决(五)图片压缩 —— 优化图片文件、内存一、图片压缩二、文件压缩三、压缩原理四、内存压缩五、手写图片缓存六、附所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复