前言
相信对于部分android开发者来说bitmap是既熟悉又陌生,为什么这么说呢,平时展示图片要么直接使用ImageView#setImageResource加载本地资源图片,要么使用Glide、Picasso等优秀的开源框架去加载网络图片,大部分情况下都能基本满足我们的日常开发,那大家有没有想过bitmap是什么,drawable又是什么,两者有什么区别,我先卖个关子,后续我会写出相关的博客文章一起学习,也欢迎广大android开发者多多指教。接下来进入主题,今天主要讲的是如何优化加载大图片或者bitmap位图呢。
如何处理图片资源转换为bitmap
今天的主角就是BitmapFactory,这个工厂类为我们提供了四种解析转换为bitmap的方法:
try {
...
Bitmap one = BitmapFactory.decodeStream(inputStream);
...
byte[] inputStream2ByteArr = inputStream2ByteArr(inputStream);
Bitmap two= BitmapFactory.decodeByteArray(inputStream);
...
Bitmap three = BitmapFactory.decodeFile(file);
...
Bitmap four=BitmapFactory.decodeResource(context.getResources(),R.drawable.picture);
...
} catch (IOException e) {
e.printStackTrace();
}
前面两种主要用来解析网络加载图片,后两者用来解析本地图片
接下来举例使用Okhttp加载我们服务器的图片,随便网上找一张图。。
final ExecutorService EXECUTOR_SERVICE = Executors.newSingleThreadExecutor();
EXECUTOR_SERVICE.execute(() -> {//创建子线程
try {
OkHttpClient client = new OkHttpClient();
final String url = "https://img0.baidu.com/it/u=530426417,2082848644&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500";
//获取请求对象
Request request = new Request.Builder().url(url).build();
//获取响应体
ResponseBody body;
body = client.newCall(request).execute().body();
//获取流
InputStream in;
if (body != null) {
in = body.byteStream();
Bitmap last = BitmapFactory.decodeStream(in);
ThreadUtils.runInUIThread(() -> binding.bitmap.setImageBitmap(last));
Logger.d("获取大小:" + fileSizeByteToM((long) last.getAllocationByteCount()));
}
} catch (IOException e) {
e.printStackTrace();
}
});
这样图片就加载出来了,看了一下图片有1.7M,虽然说这图片不算很大,但是假如说你加载的图片是一张72M的图片呢,那改怎么处理呢,接下来的主角该登场了————BitmapFactory.Options
BitmapFactory.Options的主要属性:
- inJustDecodeBounds
设置为true时在解码的时候不会返回bitmap,只会返回这个bitmap的尺寸。目的在于得到view的尺寸而不需要加载到内存中,解码过程内存优化的主要步骤。 - inMutable
官方解释:如果设置,解码方法将始终返回可变位图而不是不可变位图
简单来说就是返回的bitmap对象允许被进行复用 - inSampleSize
官方解释:如果设置为大于 1 的值,则请求解码器对原始图像进行二次采样,返回较小的图像以节省内存。也就是如果大于1,那么就会按照比例(1 / inSampleSize)缩小bitmap的宽和高、降低分辨率,在缩小内存的同时,最大程度的保留图片的完整性(不变形)。
-inPreferredConfig
设置色彩模式,默认为ARGB_8888,还有比较常用的有RGB_565/ALPHA_8/ARGB_4444
在不需要透明度的情况下,建议使用RGB_565,RGB与ARGB区别在于A,A就是Alpha,能否支持透明跟半透明设置,那来比较下默认的ARGB_8888跟RGB_565有什么区别呢?
图片格式 | 描述 |
---|---|
ARGB_8888 | A、R、G、B分别对应后面的四个数字,分别占用8bit,一共是4*8bit=32bit=4byte,即一个像素占用四个字节,例如一张1000 * 1000的图片,占用图片大小为:1000 * 1000 * 4byte = 4000kb = 4M |
RGB_565 | 同理,RGB_565分别占用5bit、6bit、5bit,一共是5bit+6bit+5bit=16bit=2byte,即一个像素占用两个字节,例如一张1000 * 1000的图片,占用图片大小为:1000 * 1000 * byte = 2000kb = 2M,大小为RGB_8888的一半,而且两者展示出来的清晰度没什么差别(或者说我了解不多,有相关经验的小伙伴可以指出一起探讨)。除了没有透明度而已,所以建议根据实际情况优先使用RBG_565。 |
- outWidth&outHeight
bitmap的宽和高(主要一般和inJustDecodeBounds一起使用来获得Bitmap的宽高,但是不加载到内存,下面会说到) - inDither
是否进行图像抖动处理
其余的属性可以查看官方文档进行查阅,这里就不细说了 官方文档-BitmapFactory.Options
使用BitmapFactory.Options
final ExecutorService EXECUTOR_SERVICE = Executors.newSingleThreadExecutor();
EXECUTOR_SERVICE.execute(() -> {//创建子线程
try {
OkHttpClient client = new OkHttpClient();
final String url = "https://img0.baidu.com/it/u=530426417,2082848644&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500";
//获取请求对象
Request request = new Request.Builder().url(url).build();
//获取响应体
ResponseBody body;
body = client.newCall(request).execute().body();
//获取流
InputStream in;
if (body != null) {
in = body.byteStream();
byte[] inputStream2ByteArr = inputStream2ByteArr(in);
//转化为bitmap
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
Bitmap last = BitmapFactory.decodeByteArray(inputStream2ByteArr, 0, inputStream2ByteArr.length, options);
ThreadUtils.runInUIThread(() -> binding.bitmap.setImageBitmap(last));
Logger.d("获取大小:" + fileSizeByteToM((long) last.getAllocationByteCount()));
}
} catch (IOException e) {
e.printStackTrace();
}
});
这里使用了GRB_565的图片格式,可以看到跟原图差距不大,可以忽略不计,但是大小已经小了一半,
这里也许有人会问,为什么使用了BitmapFactory.decodeByteArray,而上面是BitmapFactory.decodeStream,原因是因为这里解析了两次图片,而InputStream在第一次解析之后将不能被再次解析,导致了第二次解析返回的bitmap将会是null,所以这里将InputStream转换为byte[]进行存储方便二次解析。
这样就结束了吗?NO!继续往下看
final ExecutorService EXECUTOR_SERVICE = Executors.newSingleThreadExecutor();
EXECUTOR_SERVICE.execute(() -> {//创建子线程
try {
OkHttpClient client = new OkHttpClient();
final String url = "https://img0.baidu.com/it/u=530426417,2082848644&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500";
//获取请求对象
Request request = new Request.Builder().url(url).build();
//获取响应体
ResponseBody body;
body = client.newCall(request).execute().body();
//获取流
InputStream in;
if (body != null) {
in = body.byteStream();
byte[] inputStream2ByteArr = inputStream2ByteArr(in);
//转化为bitmap
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(inputStream2ByteArr, 0, inputStream2ByteArr.length, options);
int sampleSize = 1;
int height = options.outHeight;
int width = options.outWidth;
int maxHeight = SizeUtils.dp2px(100);
int maxWidth = SizeUtils.dp2px(300);
while (height > maxHeight || width > maxWidth) {
//如果宽高的任意一方的缩放比例没有达到要求,都继续增大缩放比例
//sampleSize应该为2的n次幂,如果给sampleSize设置的数字不是2的n次幂,那么系统会就近取值
height >>= 1;
width >>= 1;
sampleSize <<= 1;
}
options.inPreferredConfig = Bitmap.Config.RGB_565;
options.inJustDecodeBounds = false;
//设置缩放比例
options.inSampleSize = sampleSize;
Bitmap last = BitmapFactory.decodeByteArray(inputStream2ByteArr, 0, inputStream2ByteArr.length, options);
ThreadUtils.runInUIThread(() -> binding.bitmap.setImageBitmap(last));
Logger.d("获取大小:" + fileSizeByteToM((long) last.getAllocationByteCount()));
}
} catch (IOException e) {
e.printStackTrace();
}
});
我们实际开发中的图片通常会限制大小,我们可以根据实际情况的宽高对图片进行等比例压缩,对大小做到极可能压缩。
看,是不是又小了一丢丢~
今天就到这吧,要做工了(这是我的第一篇博客哟,多多包涵哈哈)