版权声明:本文为博主原创文章,未经博主允许不得转载。
本文纯个人学习笔记,由于水平有限,难免有所出错,有发现的可以交流一下。

一、图片压缩

Android 中图片是以 Bitmap 形式存在的,而且 Bitmap 是比较占内存的。所以,如果能对 Bitmap 进行压缩,对内存优化这块有很大的帮助。我们首先需要知道 Bitmap 占用内存的计算方式:

图片长上的像素点 * 图片宽上的像素点 * 一个像素点占用的字节数

从公式可以看出,只要我们减小 Bitmap 所占内存的三个因素之一,都可以压缩图片所占内存。

  1. 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. 图片存在形式

在安卓中,图片有三种形式存在。

1.文件形式(以二进制形式保存在SD 卡)
2.流的形式(以二进制形式存在于内存)
3.Bitmap 形式  

一般来说,手机 SD 卡上的文件形式图片,与内存中流形式的图片,大小相同。不过,当图片以 Bitmap 形式存在时,其占用的内存会变得很大。

查看大小:

文件形式: file.length()流的形式: 查看流的 byte 个数Bitmap:bitmap.getByteCount()
  1. 图片压缩

由上面可以知道,图片可能存在 SD 卡里,或者存在于内存,对于这两种存在形式都可以进行压缩,分别称为文件压缩和内存压缩

文件压缩: 为了减小图片文件的大小,比如上传图片到服务器,减少本地图片存储空间。

内存压缩: 这块主要是对 Bitmap 所占内存进行压缩,减少内存占用。

二、文件压缩

文件压缩有三种:

质量压缩
尺寸压缩
格式选择:JPEG/WEBP (4.0以上)

1.质量压缩

质量压缩是一种有损压缩,在压缩过程中,图片会丢失一些图片信息,变得不清晰。

代码:

    @Overrideprotected 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 越小,图片越模糊。

    @Overrideprotected 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 methodif (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 的具体类是 \external\skia\src\images 下的 SkImageDecoder_libpng.cpp、SkImageDecoder_libjpeg.cpp、SkImageDecoder_libwebp.cpp 等。

2.Skia引擎

Skia 官网

上面使用的其实就是 Skia 引擎,这是一款 Google 研发、开源的 C++ 二维图形库 。

在安卓中使用的是阉割的 Skia 版本,对 JPEG 的处理是基于 libjpeg,对 PNG 则是基于 libpng。在安卓早期,由于 CPU 吃紧,将 libjpeg 中的最优哈夫曼编码关闭了。

在安卓 7.0 的 \external\skia\src\images\SkImageDecoder_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() 进行获取。

    @Overrideprotected 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 复用池 来保存可被复用的Bitmapprivate 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() {@Overridepublic void run() {while (!shutDown){try {Reference<Bitmap> reference = referenceQueue.remove();Bitmap bitmap = reference.get();//对 bitmap 进行 recycleif(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);//获得程序可用最大内存,单位 Mint memoryClass = am.getMemoryClass();//需要设置缓存最大的内存,一般根据 app 可用内存进行计算memoryCache = new LruCache<String, Bitmap>(memoryClass / 8 * 1024 * 1024){/**** @param key* @param value* @return value占用的内存*/@Overrideprotected int sizeOf(String key, Bitmap value) {// 由于使用过了复用,// 使用 getAllocationByteCount 替代 getByteCount 进行内存获取return value.getAllocationByteCount();}/*** 当 Bitmap 从 Lru 中移除时 回调* @param evicted* @param key* @param oldValue* @param newValue*/@Overrideprotected 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 MdiskLruCache = 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;}//获得文件输入流 读取 bitmapInputStream 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();//迭代查找符合复用条件的Bitmapwhile (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 等。

六、附

代码链接

(五)图片压缩 —— 优化图片文件、内存相关推荐

  1. 使用canvas进行图片压缩(前端图片压缩核心处理)

    一.原理:实际上就是利用canvas进行重新绘制 1.先将图片的file文件转成baseURL 2.创建一个image标签去接收文件获取图片的宽高和比例. 3.创建canvas画布设置画布的大小. 4 ...

  2. 用JS实现:图片压缩、图片加密

    本文将用JavaScript实现两个颇有技术含量的功能:图片压缩.图片加密. 最终效果:可实现将任意图片加密.压缩,并保存到一个独立的html页面中,输入正确的密码,才能看到原图. 第一步.压缩图片 ...

  3. Android性能优化之图片压缩优化

    1 分类 Android图片压缩结合多种压缩方式,常用的有尺寸压缩.质量压缩.采样率压缩以及通过JNI调用libjpeg库来进行压缩. 参考此方法:Android-BitherCompress 备注: ...

  4. 新品出炉:在线PS_图片压缩_图片转换_图片编辑处理网站

    安利一个好用的在线图片编辑处理网站:改图鸭 改图鸭-在线图片压缩软件,jpg.png.gif图片一键压缩改图鸭是一个在线图片压缩软件,支持PNG压缩.JPG压缩.GIF压缩,还支持在线调整图片大小.图 ...

  5. java图像处理002---JAVA图片压缩_图片缩放_图片按照比例缩放_图片指定长宽缩放_Java使用google开源工具Thumbnailator实现图片压缩

    JAVA技术交流QQ群:170933152 前言 作为靠谱的java服务端程序员,图片这个事情一直是个头疼的事情. 现在很多网站上,都有上传图片这个功能,而图片对于现在的很多手机来说,拍摄出来的都是高 ...

  6. Java之~ 上传 图片压缩,阿里图片压缩,图片旋转方法工具类

    需要的jar jai_codec-1.1.3.jar,jai_core-1.1.3.jar,simpleimage-1.2.0.jar,metadata-extractor-2.3.1.jar(旋转时 ...

  7. 图片压缩,修改图片dpi值,更改图片大小

    主要修改图片dpi值,原理:电脑一般dpi为96,图片dpi超过这个值后,图片清晰度没多大变化,降低图片dpi值就能大幅度降低图片大小,如图片dpi为180,将其降到96后,图片宽高比例不变,图片大小 ...

  8. 网页图片压缩优化,提高网站打开速度

    最大程度的压缩了网页大小,提高访问者的用户体验.对于一个网站而言,页面的开启速度是至关重要的.它不仅直接影响到该网站在搜索引擎中的排名参数,更为重要的是,会减缓访客的页面开启速度. 图片优化基本原则: ...

  9. android图片压缩之图片和内存基础

    1.堆(HEAP)是VM中占用内存最多的部分,通常是动态分配的.堆的大小不是一成不变的,通常有一个分配机制来控制它的大小.比如初始的HEAP是4M大,当4M的空间被占用超过75%的时候,重新分配堆为8 ...

最新文章

  1. 妙用 Intellij IDEA 创建临时文件,Git 跟踪不到的那种
  2. 【转载】VSCode+OpenCV+C++配置
  3. SpringBoot加Jquery实现ajax传递json字符串并回显消息(已实践)
  4. 金御® GM-AFM介绍
  5. QT的系统总结(非常全面)
  6. 天池 在线编程 区间合并(字符串)
  7. 【BZOJ】【1036】树的统计
  8. (52)FPGA基础编码D触发器(一)
  9. 拦截Response.Redirect的跳转并转换为Js的跳转
  10. ADFS部署过程中设置network service对证书的读取权限
  11. 实用金属材料手册_各种金属材料单位重量计算公式(汇总版)
  12. java web课程设计目的_javaweb课程设计
  13. 佳能打印机imageRUNNER系列 2206AD驱动安装
  14. httpclient基本get用法
  15. 计算机考研代码854,哈工大计算机考研考纲854计算机基础
  16. 用于React,React Native,JavaScript和生产力的顶级VSCode扩展
  17. Linux学习日志-02
  18. 智能座舱全舱感知系统SCSS
  19. Text-CNN 文本分类
  20. 猿创征文|JAVA 实现《连连看》游戏

热门文章

  1. 风控黑名单库的使用与判断指南
  2. 新一配:iPod及其配置介绍【转载】
  3. 微信小程序中使用全局变量解决页面的传值问题
  4. 智能手机拍照及视频DXO mark排名
  5. 安全狗获聘福建省网络与信息安全信息通报中心技术支撑单位
  6. linux指令——刘雯丽
  7. CentOS安装EPEL软件源
  8. 河北外国语学院计算机宿舍,2021年河北外国语学院新生宿舍条件和宿舍环境图片...
  9. Java Class
  10. C++ string CString 详解