在咱们编写
Android
程序的时候,几乎永远逃避不了图片压缩的难题。除了应用图标以外,咱们所要显示的图片基本上只有两个来源:java
- 来自网络下载
- 本地相册中加载
不论是网上下载下来的也好,仍是从系统图片库中读取的图片,都有一个相同的特色:像素一帮较高。同时咱们都知道,Android
系统分配给咱们每一个应用的内存是有限的,因为解析、加载一张图片,须要占用的内存大小,是远大于图片自身大小的。因此,这时程序就可能由于占用了过多的内存,从而出现OOM
现象。那么什么是 OOM
呢?android
Exception java.lang.OutOfMemoryError: Failed to allocate a 916 byte allocation with 8388608 free bytes and 369MB until OOM; failed due to fragmentation (required continguous free 65536 bytes for a new buffer where largest contiguous free 32768 bytes) java.nio.CharBuffer.allocate (CharBuffer.java:54) java.nio.charset.CharsetDecoder.allocateMore (CharsetDecoder.java:226) java.nio.charset.CharsetDecoder.decode (CharsetDecoder.java:188) org.java_websocket.util.Charsetfunctions.stringUtf8 (Charsetfunctions.java:77) org.java_websocket.WebSocketImpl.decodeFrames (WebSocketImpl.java:375) org.java_websocket.WebSocketImpl.decode (WebSocketImpl.java:158) org.java_websocket.client.WebSocketClient.run (WebSocketClient.java:185) java.lang.Thread.run (Thread.java:818) 复制代码
OOM
即 OutOfMemory
异常,也就是咱们所说的 内存溢出 ,其通常表现为应用闪退等现象。那么咱们该如何下手去解决呢?git
首先咱们发现,咱们所加载的这些图片的分辨率,要比咱们手机屏幕高得多,更有甚者,咱们在一个拇指大的控件上,去加载一个 4k 大图是彻底没有必要的,也就是说,若是咱们能让每一个控件上都去显示相应大小的图片,那么这个问题也就迎刃而解了github
那么,要怎样才能达到图片与控件的对号入座?这时咱们就引进了图片压缩的方案:web
那么就让咱们开始吧:编程
咱们都知道,Android 向咱们提供了 BitmapFactory
这个类,在这个类中有着诸如:decodeResource()
decodeFile()
decodeStream()
等:websocket
public static Bitmap decodeResource(Resources res, int id) public static Bitmap decodeFile(String pathName) public static Bitmap decodeStream(InputStream is) 复制代码
其余的方法这里就很少说了,由于在源码中咱们可有i看到,几乎全部的方法,最后都会将图片解析为流的形式,最后调用 decodeStream()
方法,实例化出咱们的 Bitmap
对象。网络
虽然这些方法对咱们是再熟悉不过的了,但对于某些初学者而言,却常常忽略了一个重要的内部类 :BitmapFactory.Options
,然而他确实咱们图片压缩必不可少的,为何须要这个参数呢?Options
的对象用于肯定须要生成的 Bitmap 即目标图片的参数。 他的用法很简单,咱们先 new 一个 BitmapFactory.Options
对象。再去调用含有 Options
参数的方法,如app
public static Bitmap decodeResource(Resources res, int id, Options opts)
public static Bitmap decodeResourceStream(@Nullable Resources res,@Nullable TypedValue value,@Nullable InputStream is, @Nullable Rect pad, @Nullable Options opts)
调用完以后咱们发现,除了方法放回给咱们一个实例化出来的 Bitmap
图片以外,这个 Options
对象中长度、宽度、类型等等属性,也都被设置成了了咱们图片的相应属性。因此,咱们很容易想到:经过将 Options
对象传入,来得到图片的原始尺寸,为后期的压缩作准备,说干就干,咱们将 Options
对象,和 Resources
中一张 4k 图片的id
一块传入上诉方法中,来尝试得到它的尺寸,结果咱们发现:程序 OOM
崩溃了!框架
为何会发生这种状况?首先咱们想一想咱们为何要得到这个Options
对象?时为了得到图片的尺寸大小;那咱们为何要得到原图尺寸大小?是为了按照原图尺寸和控件尺寸的比例,将其压缩为适合显示的大小?那咱们又为何要去压缩它为合适的大小呢?是由于若是按照原大小去调用相应的 decode...()
方法解析图片,会致使内存占有率太高触发OOM
异常,进而致使程序崩溃啊!没想到的是:结果咱们为了得到 Options
而调用了相应的 decode...()
方法,的确 Options
是复制了,但因为该方法适用于生成图片,也就是 Bitmap
对象的。因此程序也在解析这张超大图的过程当中OOM
崩溃了
那么难道就没方法了吗?
有的,我以前说过:Option
内部有着众多参数,其中有一个叫作: inJustDecodeBounds
。这个参数默认值为false
。但若是咱们先把这个参数设置为 true 时,该方法便不在会去生成相应的 Bitmap
,而仅仅是去测量图片的各类属性,如长度、宽度、类型等等,而后放回一个 null
。因此,咱们很容易想到:能够先经过将 inJustDecodeBounds
的值设为 true
,再去调用相应的相应的 decode...()
方法,最后再将inJustDecodeBounds
的值改回 false
。这种作法有两个好处:
OOM
而崩溃。但这偏偏是被不少人所忽略的一点。
public static void calculateOptionsById(@NonNull Resources res,@NonNull BitmapFactory.Options options, int imgId) {
BitmapFactory.decodeResource(res, imgId, options);
}
复制代码
你们可能发现,这里只将
inJustDecodeBounds
设为true
却没有改回false
,这是由于得到Options
只是图片压缩的第一步,咱们在后续方法中将会进行修改
咱们继续看 Options
的构成。咱们发现,其中有个名为 inSampleSize
的数据成员,他就是关键所在,那么他有着什么意义呢?
这里我给你们举个例子,好比我这有张 4000*1000 像素的图片:
inSampleSize
的值设为 4
时,最后生成出来的图片大小将会是:1000 x 250 像素inSampleSize
的值设为5
时,最后生成出来的图片大小将会是:800 x 200 像素。这是个什么概念?这不只仅是长宽都变为原来四分之一或者五分之一这么简单,而是其图片大小,直接变为原图的 1/(n^2)
!也就是说:
2MB
,那么当 inSampleSize
赋值为4
加载时就只须要 0.125MB
inSampleSize
赋值为 5
呢?只须要 0.08 MB
!连100k
都不到的小图啊!public static int calculateInSamplesizeByOptions(@NonNull BitmapFactory.Options options, int reqWidth, int reqHeight) {
int inSamplesize = 1;
int originalWidth = options.outWidth;
int originalHeight = options.outHeight;
if (originalHeight > reqHeight || originalWidth > reqWidth) {
int heightRatio = originalHeight / reqHeight;
int widthRatio = originalWidth / reqWidth;
inSamplesize = heightRatio > widthRatio ? heightRatio : widthRatio;
}
return inSamplesize;
}
复制代码
咱们发现,这里我先计算出了,原图尺寸与目标大小大比例,在三目运算符中,将inSamplesize
赋值为较大的一个。为何不用小的那一个呢?这里我就卖个关子,你们能够在评论区中发表本身的想法
通过前面的两个步骤,想必你们已经能勾勒处这最后一步的作法了,思路很是简单:
Options
对象Options 的 inJustDecodeBounds
设置为true
calculateOptionsById
得到原图尺寸到Options
中calculateInSamplesizeByOptions
得到相应的inSampleSize
对象Options
的inJustDecodeBounds
改回 false
decode...()
方法(这里是 decodeResource
)得到压缩后的 Bitmap
对象public static Bitmap decodeBitmapById (@NonNull Resources res, int resId, int reqWidth, int reqHeight) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
calculateOptionsById(res, options, resId);
options.inSampleSize = calculateInSamplesizeByOptions(options, reqWidth, reqHeight);
options.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeResource(res, resId, options);
return bitmap;
}
复制代码
很是棒,咱们赶忙看看效果:
太棒了,几乎和原图效果一摸同样,但软件运行的流畅性确大大提升了!可是,这真的就完美了吗?
最求完美的咱们可能会有个想法:若是调用咱们方法的人,或者说特殊时候的咱们。不想用这个已经写好的 decodeBitmapById
方法,而是像本身经过前两个方法:calculateOptionsById
calculateInSamplesizeByOptions
来实现图片压缩功能,这是问题就出现了:
calculateOptionsById
前可能忘记,设置 inJustDecodeBound
为 true
,进而致使计算超大图时,直接发生 OOM
calculateInSamplesizeByOptions
后可能忘记,设置inJustDecodeBounds
为 false
,进而致使没法得到Bitmap
对象,一脸懵逼calculateInSamplesizeByOptions
没把没回的值赋给 options.inSampleSize
,白忙活一场因此,咱们须要在优化一下:
首先,在calculateOptionsById
中,默认将 options.inJustDecodeBounds
设置为true
:
public static void calculateOptionsById(@NonNull Resources res,@NonNull BitmapFactory.Options options, int imgId) {
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, imgId, options);
}
复制代码
其次,在 calculateInSamplesizeByOptions
最后,默认将 options.inJustDecodeBounds
设置为false
:
public static int calculateInSamplesizeByOptions(@NonNull BitmapFactory.Options options, int reqWidth, int reqHeight) {
int inSamplesize = 1;
int originalWidth = options.outWidth;
int originalHeight = options.outHeight;
if (originalHeight > reqHeight || originalWidth > reqWidth) {
int heightRatio = originalHeight / reqHeight;
int widthRatio = originalWidth / reqWidth;
inSamplesize = heightRatio > widthRatio ? heightRatio : widthRatio;
}
options.inJustDecodeBounds = false;
return inSamplesize;
}
复制代码
为何不在该方法后面,对 options.inSampleSize
进行赋值呢?这主要是防止,有时咱们可能只想获得计算相应比例来作其余操做,而不想改变原有属性,因此是否赋值,就交给用户去选择吧
好了,到这里为止,历时有关图片压缩的全部坑坑洼洼都已经总结好了,咱们从头理以边思路:
options.inJustDecodeBounds
参数赋值true
时,不生成图片的特性,将原图尺寸保存在 Options
中options
中原图尺寸与目标(控件)尺寸的比例,对 options.inSampleSize
进行设置Android 让你的 Room 搭上 RxJava 的顺风车 从重复的代码中解脱出来
ViewModel 和 ViewModelProvider.Factory:ViewModel 的建立者
单例模式-全局可用的 context 对象,这一篇就够了
缩放手势 ScaleGestureDetector 源码解析,这一篇就够了
Android 属性动画框架 ObjectAnimator、ValueAnimator ,这一篇就够了
看完这篇再不会 View 的动画框架,我跪搓衣板
Android 自定义时钟控件 时针、分针、秒针的绘制这一篇就够了
android 自定义控件之-绘制钟表盘
Android 进阶自定义 ViewGroup 自定义布局
Android 逐帧动画( Drawable 动画),这一篇就够了
按期分享Android开发
湿货,追求文章幽默与深度
的完美统一。
源码 Demo 连接:Drop 我第一次写的 Android 项目,但愿你们点歌 star~ 谢谢!