Android 图片加载系列:ImageView详解

对于 ImageView ,你知道的有多少呢?我知道的有以下这么一些。本篇主要总结和分析 ImageView 加载图片的几种方式、加载图片时的缩放类型以及使用 ImageView 时的一些误解、建议和优化。

图片加载方法

在项目中,加载图片时,都会用到 ImageView,对应的几种设置图片的方式有如下几种:

  • 在布局文件中设置属性 android:src=“@drawable/resId” 加载本地图片
  • setImageResource(int resId); 加载drawable文件夹中的资源文件
  • setImageURI(Uri); 加载手机内存卡中的图片格式文件
  • setImageBitmap(Bitmap); 加载 Bitmap
  • setImageDrawable(Drawable); 加载 Drawable

很清楚,这几种方式,涵盖了所有可能格式的图片。对于资源图片,直接调用 setImageResource() 或属性定义;对于图片文件,我们知道内部存储中的文件,都可以将文件转化为 Uri 格式,Uri 的 schema 类型两种:file 和 content ,这时将图片文件转化为 Uri,然后通过 setImageURI() 设置。

另外两种方式 setImageBitmap() 和 setImageDrawable() 方式,可以看做是一个通用的方法,比如说将 文件格式或网络图片先转化为 Bitmap 再使用 setImageBitmap() 加载,将资源文件转化为 Drawable 然后调用 setImageDrawable() 加载。

这几种方式传递的参数虽然不一样,表面上我们可以通过各种方式来设置,但最终所有格式的图片在绘制到屏幕前,都会以 Drawable 的形式进行绘制。(提前安利一个技巧,自定义 View 时,绘制图片,通过 Drawable 在 canvas 上绘制比使用 Bitmap 绘制好用的多。)

描述的文字太多,看着就费劲,以一张图来展示这几种加载方式的区别。
Android 图片加载系列:ImageView详解_第1张图片
ImageView设置图方法流程.png

对于在布局中定义 android:src 属性加载图片的这种方式,没有画在上图中,这里以文字说明,可别认为设置图片最后调用的是 setImageRecource() 方法。在 ImageView 中获取属性然后设置数据,追溯源码,可知,这种情形会先将 resId 转化为 Drawable,然后直接通过 setImageDrawable(Drawable) 方法设置。

通过上述流程图,可以得知:

  • 所有的图片格式,不管是资源,还是 Uri 还是 Bitmap ,都会转化为 Drawable;
  • setImageURI(Uri) 方法,涉及到将文件转化为文件流,然后将文件流解析为 Bitmap 操作,需要注意的是在主线程中做这些操作,可能会造成延时;
  • 针对性能方面,给上述加载图片方法排序,从劣到优,可以这样来:setImageURI() < setImageBitmap() < setImageRecource() < 属性设置 < setImageDrawable() ,能肯定的是,setImageDrawable() 是最优设置方法。

对于ImageView设置图片的几种方式,先说到这里。接下来看将所有图片转化为 Drawable 后的操作,也即是 updateDrawable(Drawable) 这个方法做了些什么,总结来说主要是根据 ImageView 设置的图片缩放类型确定其内容(即图片)绘制的边界,所以在这之前,需要知道关于 scaleType 缩放类型的一些事。

图片缩放类型

ImageView 对应的图片缩放类型,属性名为 scaleType,系统给出了八种缩放类型,对应含义和作用如下图所示:
Android 图片加载系列:ImageView详解_第2张图片
ImageView ScaleType.png

网上大多以很形象的图展示各种缩放类型下ImageVIew加载图片的场景,这样看起来更容易理解,链接:ImageView的ScaleType原理及效果分析 。

对缩放类型有了清晰的认识后,再来看 updateDrawable() 方法具体做了哪些操作,源码流程如下:
Android 图片加载系列:ImageView详解_第3张图片
图片缩放流程.png

这里就有一个疑问了,既然图片加载时会按缩放类型放大或缩小图片,那么这会影响图片占用的内存大小吗?

测试场景:让 ImageView 加载同一张图片,然后以不同的 scaleType 进行加载,查看该图片占用的内存是否会改变?设置 vWidth = 200dp, vHeight = 160dp,加载图片原始大小:144x144 ,设备屏幕尺寸:1080x1920。
测试代码:

    Log.e("ImageView", "scaleType = " + iv_img.getScaleType().toString());  // 默认为 FIT_CENTER

        final Drawable drawable = iv_img.getDrawable();
        int iWidth = drawable.getIntrinsicWidth();   // 216
        int iHeigth = drawable.getIntrinsicHeight(); // 216

        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_logo);
        Log.e("ImageView", "bmap size = " + (bitmap.getWidth() + ", " + bitmap.getHeight()) + ", " + bitmap.getAllocationByteCount());

        // 216 216 432
        Log.e("ImageView", "drawable size = " + (iWidth + ", " + iHeigth));

        iv_img.post(new Runnable() {
            @Override
            public void run() {
                Rect bounds = drawable.getBounds();
                int boundsW = bounds.right - bounds.left;
                int boundsH = bounds.bottom - bounds.top;
                // 216 216
                Log.e("ImageView", "bounds size = " + (boundsW + ", " + boundsH));
            }
        });

测试结果:

  • 默认缩放类型FIT_CENTER:bmpW_H = drawableW_H = 216x216, bounds = 216x216,分配内存大小 = 186624
  • 设置缩放类型FIT_XY:bmpW_H = drawableW_H = 216x216, bounds = 600x480,分配内存大小 = 186624

只设置两组区别比较到的缩放内存,从得到的结果可知缩放类型不会影响图片占用内存大小,只会影响 Drawable 在绘制图片到屏幕时的区域大小,可以看一下 Drawable ,其实也具备绘制功能。其实了解如何计算图片占用内存,就比较清楚,将图片转化为 Bitmap ,然后分场景计算:

  • 加载资源文件时,计算的图片占用内存大小同图片所在drawable 文件夹和展示图片设备的分辨率有关;
  • 加载内部存储中的图片文件时,则同设备屏幕密度无关,为图片本身大小。

可以明确的是,图片最终是以 Bitmap 形式存在于内存中的,ImageView 的缩放类型只是将图片展示的区域按缩放规则进行划定,并没有对图片本身产生作用,所以 scaleType 缩放对图片占用的内存大小并没有什么关系。

计算图片占用内存大小,链接:Android性能优化:Bitmap详解&你的Bitmap占多大内存?

总结

了解本质之后,对图片的加载也有了大半的认识,在此做一下对 ImageView 的使用和认知做一些总结:

  • setImageURI() 方法存在对 Uri 代表的图片文件转化为文件流而后解析为Bitmap 的操作,可能会存在延时,需要注意一下;
  • 设置图片的最优方式是 setImageDrawable() 方法;
  • 在布局中通过 src 属性展示图片 实际调用的并非 setImageResource() 方法,而是先将 resId 转化为 Drawable,然后通过 setImageDrawable() 方法加载;
  • Drawable 是Android 系统中图片绘制前的最终形式,图片如何绘制到 canvas 上,也是通过 Drawable 来的;区别于在内存中存在的最终形式为 Bitmap;
  • 自定义 View 时,绘制图片,使用 Drawable.setBounds() 确定边界后,再调用 drawable.draw(canvas) 方法绘制 会比 canvas.drawBitmap() 更简单强大,前者还能绘制 shape xml 文件转化的图片;
  • ImageView 默认缩放类型为 FIX_CENTER;
  • ImageView 缩放内存对加载图片本身占用的内存大小并没有关系,仅仅是缩放图片内容展示的边界而已;
  • 图片占用内存大小,若为资源文件,则同drawable文件夹代表的密度和设备屏幕密度有关;若为网络或文件,则为图片本身大小;
  • 图片的压缩方式分两种:质量压缩和分辨率压缩,前者能减少图片质量,但对图片分辨率并没有影响,图片占用的内存大小没有改变,常运用于 上传网络图片时上传字节大小有限制;后者压缩分辨率,会改变图片的清晰度,占用内存也会减少。更多了解详见 Android图片压缩(质量压缩和尺寸压缩)

你可能感兴趣的:(Android 图片加载系列:ImageView详解)