网络加载大图片优化实践方案

前言

相信对于部分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_8888A、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();
            }
        });

我们实际开发中的图片通常会限制大小,我们可以根据实际情况的宽高对图片进行等比例压缩,对大小做到极可能压缩。
在这里插入图片描述
在这里插入图片描述
看,是不是又小了一丢丢~

今天就到这吧,要做工了(这是我的第一篇博客哟,多多包涵哈哈)
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值