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 绘制好用的多。)
描述的文字太多,看着就费劲,以一张图来展示这几种加载方式的区别。 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,系统给出了八种缩放类型,对应含义和作用如下图所示: ImageView ScaleType.png网上大多以很形象的图展示各种缩放类型下ImageVIew加载图片的场景,这样看起来更容易理解,链接:ImageView的ScaleType原理及效果分析 。
对缩放类型有了清晰的认识后,再来看 updateDrawable() 方法具体做了哪些操作,源码流程如下: 图片缩放流程.png这里就有一个疑问了,既然图片加载时会按缩放类型放大或缩小图片,那么这会影响图片占用的内存大小吗?
测试场景:让 ImageView 加载同一张图片,然后以不同的 scaleType 进行加载,查看该图片占用的内存是否会改变?
了解如何计算图片占用内存,就比较清楚,将图片转化为 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_XY,也就是占满整个 ImageView;
- ImageView 缩放内存对加载图片本身占用的内存大小并没有关系,仅仅是缩放图片内容展示的边界而已;
- 图片占用内存大小,若为资源文件,则同drawable文件夹代表的密度和设备屏幕密度有关;若为网络或文件,则为图片本身大小;
- 图片的压缩方式分两种:质量压缩和分辨率压缩,前者能减少图片质量,但对图片分辨率并没有影响,图片占用的内存大小没有改变,常运用于 上传网络图片时上传字节大小有限制;后者压缩分辨率,会改变图片的清晰度,占用内存也会减少。更多了解详见 Android图片压缩(质量压缩和尺寸压缩)