高性能图片优化方案
高性能图片优化方案
目录介绍
01.图片基础概念介绍
1.1 图片占用内存介绍
1.2 网络图片加载流程
1.3 三方库加载图片逻辑
1.4 BitmapFactory
1.5 图片大小 VS 内存
1.6 Bitmap 能直接存储吗
1.7 Bitmap 创建流程
1.8 图片框架如何设计
02.图片内存计算方式
2.1 如何计算占用内存
2.2 上面计算内存对吗
2.3 一个像素占用内存
2.4 使用 API 获取内存
2.5 影响 Bitmap 内存因素
2.6 加载 xhdpi 和 xxhdpi 图片
2.7 图片一些注意事项
03.大图的内存优化
3.0 图片压缩核心思想
3.1 常见图片压缩
3.2 图片尺寸压缩
3.3 图片质量压缩
3.4 双线性采样压缩
3.5 高清图分片加载
3.6 鲁班图片综合压缩
04.色彩格式及内存优化
4.1 RGB 颜色种类
4.2 ARGB 色彩模式
4.3 改变色彩格式优化
05.图片内存缓存设计
5.1 图片内存缓存思想
5.2 Lru 内存缓存
5.3 Lru 缓存注意事项
5.4 使用 Lru 磁盘缓存
06.不同版本对 Bitmap 管理
6.1 演变进程
6.2 管理 Bitmap 内存
6.3 提高 Bitmap 复用
07.图片其他方面优化
7.1 减少 PNG 图片使用
7.2 控件切割圆角优化
7.3 如何给图片置灰色
7.4 如何处理图片旋转呢
7.5 保存图片且刷相册
7.6 统一图片域名优化
7.7 优化 H5 图片加载
7.8 优化图片阴影效果
7.9 图片资源的压缩
01.图片基础概念介绍
1.1 图片占用内存介绍
移动设备的系统资源有限,所以应用应该尽可能的降低内存的使用。
在应用运行过程中,Bitmap (图片)往往是内存占用最大的一个部分,Bitmap 图片的加载和处理,通常会占用大量的内存空间,所以在操作 Bitmap 时,应该尽可能的小心。
Bitmap 会消耗很多的内存,特别是诸如照片等内容丰富的大图。例如,一个手机拍摄的 2700 * 1900 像素的照片,需要 5.1M 的存储空间,但是在图像解码配置 ARGB_8888 时,它加载到内存需要 19.6M 内存空间(2592 * 1936 * 4 bytes),从而迅速消耗掉该应用的剩余内存空间。
OOM 的问题也是我们常见的严重问题,OOM 的产生的一个主要场景就是在大图片分配内存的时候产生的,如果 APP 可用内存紧张,这时加载了一张大图,内存空间不足以分配该图片所需要的内存,就会产生 OOM,所以控制图片的高效使用是必备技能。
1.2 网络图片加载流程
这一部分压缩和缓存图片,在 glide 源码分析的文章里已经做出了比较详细的说明。在这里简单说一下图片请求加载过程……
在使用 App 的时候,会经常需要加载一些网络图片,一般的操作步骤大概是这样的:
第一步从网络加载图片:一般都是通过网络拉取的方式去服务器端获取到图片的文件流后,再通过 BitmapFactory.decodeStream(InputStream)来加载图片 Bitmap。
第二步这种压缩图片:网络加载图片方式加载一两张图片倒不会出现问题,但是如果短时间内加载十几张或者几十张图片的时候,就很有可能会造成 OOM(内存溢出),因为现在的图片资源大小都是非常大的,所以我们在加载图片之前还需要进行相应的图片压缩处理。
第三步变换图片:比如需要裁剪,切割圆角,旋转,添加高斯模糊等属性。
第四步缓存图片:但又有个问题来了,在使用移动数据的情况下,如果用户每次进入 App 的时候都会去进行网络拉取图片,这样就会非常的浪费数据流量,这时又需要对图片资源进行一些相应的内存缓存以及磁盘缓存处理,这样不仅节省用户的数据流量,还能加快图片的加载速度。
第五步异步加载:虽然利用缓存的方式可以加快图片的加载速度,但当需要加载很多张图片的时候(例如图片墙瀑布流效果),就还需用到多线程来加载图片,使用多线程就会涉及到线程同步加载与异步加载问题。
1.3 三方库加载图片逻辑
先说出结论,目前市面较为常用的大概是 Glide,Picasso,Fresco 等。大概的处理图片涉及主要逻辑有:
从网络或者本地等路径拉取图片;然后解码图片;然后进行压缩;接着会有图片常用圆角,模糊或者裁剪等处理;然后三级缓存加载的图片;当然加载图片过程涉及同步加载和异步加载;最后设置到具体 view 控件上。
1.4 BitmapFactory
直接通过网络请求将网络图片转化成 bitmap,在这将采用最原生的网络请求方式 HttpURLConnection 方式进行图片获取。
经过测试,请求 8 张图片,耗时毫秒值 174。一般是通过 get 请求拉取图片的。这种方法应该是最基础的网络请求,大家也可以回顾一下,一般开发中很少用这种方式加载图片。具体可以看:ImageToolLib
如何加载一个图片呢?可以看看 BitmapFactory 类为我们提供了四类方法来加载 Bitmap:decodeFile、decodeResource、decodeStream、decodeByteArray;也就是说 Bitmap,Drawable,InputStream,Byte[] 之间是可以进行转换。
1.5 图片大小 VS 内存
搞清楚一个图片概念,在电脑上看到的 png 格式或者 jpg 格式的图片,png(jpg) 只是这张图片的容器。是经过相对应的压缩算法将原图每个像素点信息转换用另一种数据格式表示。
加载图片显示到手机,通过代码,将这张图片加载进内存时,会先解析(也就是解码操作)图片文件本身的数据格式,然后还原为位图,也就是 Bitmap 对象。
图片大小 vs 图片内存大小,一张 png 或者 jpg 格式的图片大小,跟这张图片加载进内存所占用的大小完全是两回事。
1.6 Bitmap 能直接存储吗
Bitmap 基础概念,Bitmap 对象本质是一张图片的内容在手机内存中的表达形式。它将图片的内容看做是由存储数据的有限个像素点组成;每个像素点存储该像素点位置的 ARGB 值。每个像素点的 ARGB 值确定下来,这张图片的内容就相应地确定下来了。
Bitmap 本质上不能直接存储 为什么?bitmap 是一个对象,如果要存储成本地可以查看的图片文件,则必须对 bitmap 进行编码,然后通过 io 流写到本地 file 文件上。
1.7 Bitmap 创建流程
对于图片 OOM,可以发现一个现象。heapsize(虚拟机的内存配置)越大越不容易 OOM,Android8.0 及之后的版本更不容易 OOM,这个该如何理解呢?Bitmap 对象内存的变化:
在 Android 8.0 之前,Bitmap 像素占用的内存是在 Java heap 中分配的;8.0 及之后,Bitmap 像素占用的内存分配到了 Native Heap。
由于 Native heap 的内存分配上限很大,32 位应用的可用内存在 3~4G,64 位上更大,虚拟内存几乎很难耗尽,所以推测 OOM 时 Java heap 中占用内存较多的对象是 Bitmap” 成立的情况下,应用更不容易 OOM。
搞清楚 Bitmap 对象内存分配,Bitmap 的构造方法是不公开的,在使用 Bitmap 的时候,一般都是通过 Bitmap、BitmapFactory 提供的静态方法来创建 Bitmap 实例。以 Bitmap.createBitmap 说明了 Bitmap 对象的主要创建过程分析,可以看到 Java Bitmap 对象是在 Native 层通过 NewObject 创建的。
allocateJavaPixelRef,是 8.0 之前版本为 Bitmap 像素从 Java heap 申请内存。其核心原理是 Bitmap 的像素是保存在 Java 堆上。
allocateHeapBitmap,是 8.0 版本为 Bitmap 像素从 Native heap 申请内存。其核心原理主要是通过 calloc 为 Bitmap 的像素分配内存,这个分配就在 Native 堆上。
1.8 图片框架如何设计
大多数图片框架加载流程,概括来说,图片加载包含封装,解析,下载,解码,变换,缓存,显示等操作。
图片框架是如何设计的
封装参数:从指定来源,到输出结果,中间可能经历很多流程,所以第一件事就是封装参数,这些参数会贯穿整个过程;
解析路径:图片的来源有多种,格式也不尽相同,需要规范化;比如 glide 可以加载 file,io,id,网络等各种图片资源
读取缓存:为了减少计算,通常都会做缓存;同样的请求,从缓存中取图片(Bitmap)即可;
查找文件/下载文件:如果是本地的文件,直接解码即可;如果是网络图片,需要先下载;比如 glide 这块是发起一个请求
解码:这一步是整个过程中最复杂的步骤之一,有不少细节;比如 glide 中解析图片数据源,旋转方向,图片头等信息
变换和压缩:解码出 Bitmap 之后,可能还需要做一些变换处理(圆角,滤镜等),还要做图片压缩;
缓存:得到最终 bitmap 之后,可以缓存起来,以便下次请求时直接取结果;比如 glide 用到三级缓存
显示:显示结果,可能需要做些动画(淡入动画,crossFade 等);比如 glide 设置显示的时候可以添加动画效果
02.图片内存计算方式
2.1 如何计算占用内存
如果图片要显示下 Android 设备上,ImageView 最终是要加载 Bitmap 对象的,就要考虑单个 Bitmap 对象的内存占用了,如何计算一张图片的加载到内存的占用呢?其实就是所有像素的内存占用总和:
bitmap 内存大小 = 图片长度 x 图片宽度 x 单位像素占用的字节数
起决定因素就是最后那个参数了,Bitmap 常见有 2 种编码方式:ARGB_8888 和 RGB_565,ARGB_8888 每个像素点 4 个 byte,RGB_565 是 2 个 byte,一般都采用 ARGB_8888 这种。那么常见的 1080*1920 的图片内存占用就是:1920 x 1080 x 4 = 7.9M
2.2 上面计算内存对吗
我看到好多博客都是这样计算的,但是这样算对吗?有没有哥们试验过这种方法正确性?我觉得看博客要对博主表示怀疑,论证别人写的是否正确。
说出我的结论:上面 2.1 这种说法也对,但是不全对,没有说明场景,同时也忽略了一个影响项:Density。接下来看看源代码。
inDensity 默认为图片所在文件夹对应的密度;inTargetDensity 为当前系统密度。
加载一张本地资源图片,那么它占用的内存 = width * height * nTargetDensity/inDensity 一个像素所占的内存。
正确说法,这个注意呢?计算公式如下所示
对资源文件:width * height * nTargetDensity/inDensity * nTargetDensity/inDensity * 一个像素所占的内存;
别的:width * height * 一个像素所占的内存;
2.3 一个像素占用内存
一个像素占用多大内存?Bitmap.Config 用来描述图片的像素是怎么被存储的?
ARGB_8888: 每个像素 4 字节. 共 32 位,默认设置。
Alpha_8: 只保存透明度,共 8 位,1 字节。
ARGB_4444: 共 16 位,2 字节。
RGB_565:共 16 位,2 字节,只存储 RGB 值。
2.4 使用 API 获取内存
Bitmap 使用 API 获取内存
getByteCount(),方法是在 API12 加入的,代表存储 Bitmap 的色素需要的最少内存。API19 开始 getAllocationByteCount()方法代替了 getByteCount()。
getAllocationByteCount(),API19 之后,Bitmap 加了一个 Api:getAllocationByteCount();代表在内存中为 Bitmap 分配的内存大小。
思考:getByteCount()与 getAllocationByteCount()的区别?
一般情况下两者是相等的;通过复用 Bitmap 来解码图片,如果被复用的 Bitmap 的内存比待分配内存的 Bitmap 大,那么 getByteCount()表示新解码图片占用内存的大小(并非实际内存大小,实际大小是复用的那个 Bitmap 的大小),getAllocationByteCount()表示被复用 Bitmap 真实占用的内存大小(即 mBuffer 的长度)。
在复用 Bitmap 的情况下,getAllocationByteCount()可能会比 getByteCount()大。
2.5 影响 Bitmap 内存因素
影响 Bitmap 占用内存的因素:
图片最终加载的分辨率;
图片的格式(PNG/JPEG/BMP/WebP);
图片所存放的 drawable 目录;
图片属性设置的色彩模式;
设备的屏幕密度;
2.6 加载 xhdpi 和 xxhdpi 图片
提个问题,加载 xhdpi 和 xxhdpi 中相同的图片,显示在控件上会一样吗?内存大小一样吗?为什么?
肯定是不一样的。xhdpi:240dpi--320dpi,xxhdpi:320dpi--480dpi,
app 中设置的图片是如何从 hdpi 中查找的?
首先计算 dpi,比如手机分辨率是 1920x1080,5.1 寸的手机。那么得到的 dpi 公式是(√ ̄1920² + 1080²)/5.1 =2202/5.1= 431dpi。这样优先查找 xxhdpi
如果 xxhdpi 里没有查找图片,如果没有会往上找,遵循“先高再低”原则。如果 xhdpi 里有这个图片会使用 xhdpi 里的图片,这时候发现会比在 xhdpi 里的图片放大了。
为何要引入不同 hdpi 的文件管理?比如:xxhdpi 放 94x94,xhdpi 放 74x74,hdpi 放 45x45,这样不管是什么样的手机图片都能在指定的比例显示。引入多种 hdpi 是为了让这个图片在任何手机上都是手机的这个比例。
2.7 图片一些注意事项
同样图片显示在大小不相同的 ImageView 上,内存是一样吗?图片占据内存空间大小与图片在界面上显示的大小没有关系。
图片放在 res 不同目录,加载的内存是一样的吗?最终图片加载进内存所占据的大小会不一样,因为系统在加载 res 目录下的资源图片时,会根据图片存放的不同目录做一次分辨率的转换,而转换的规则是:新图的高度 = 原图高度 * (设备的 dpi / 目录对应的 dpi )
03.大图的内存优化
3.0 图片压缩核心思想
图片尺寸压缩的核心思想是通过减少图片的像素数量(分辨率)或调整图片的质量(压缩率)来降低图片文件的大小,从而节省存储空间、减少内存占用以及加快图片加载和传输速度。
减少分辨率:原理是通过降低图片的宽度和高度(即减少像素数量),从而减少图片的存储大小。
降低质量:原理是通过调整图片的压缩率(如 JPEG 的质量参数),减少图片文件的大小。
转化图片格式:不同的图片格式(如 JPEG、PNG、WebP)具有不同的压缩算法和特性,选择合适的格式可以在保证质量的同时减少文件大小。
3.1 常见图片压缩
常见压缩方法 Api
降低质量:Bitmap.compress(),质量压缩,不会对内存产生影响;
减少分辨率:BitmapFactory.Options.inSampleSize,内存压缩;
Bitmap.compress()质量压缩
质量压缩,不会对内存产生影响。它是在保持像素的前提下改变图片的位深及透明度等,来达到压缩图片的目的,不会减少图片的像素。进过它压缩的图片文件大小会变小,但是解码成 bitmap 后占得内存是不变的。
BitmapFactory.Options.inSampleSize 内存压缩
解码图片时,设置 BitmapFactory.Options 类的 inJustDecodeBounds 属性为 true,可以在 Bitmap 不被加载到内存的前提下,获取 Bitmap 的原始宽高。而设置 BitmapFactory.Options 的 inSampleSize 属性可以真实的压缩 Bitmap 占用的内存,加载更小内存的 Bitmap。
设置 inSampleSize 之后,Bitmap 的宽、高都会缩小 inSampleSize 倍。例如:一张宽高为 2048x1536 的图片,设置 inSampleSize 为 4 之后,实际加载到内存中的图片宽高是 512x384。占有的内存就是 0.75M 而不是 12M,足足节省了 15 倍。
备注:inSampleSize 值的大小不是随便设、或者越大越好,需要根据实际情况来设置。inSampleSize 比 1 小的话会被当做 1,任何 inSampleSize 的值会被取接近 2 的幂值。
3.2 图片尺寸压缩
3.2.1 如何理解尺寸压缩
通常在大多数情况下,图片的实际大小都比需要呈现的尺寸大很多。例如,我们的原图是一张 2700 * 1900 像素的照片,加载到内存就需要 19.6M 内存空间,但是,我们需要把它展示在一个列表页中,组件可展示尺寸为 270 * 190,这时,我们实际上只需要一张原图的低分辨率的缩略图即可(与图片显示所对应的 UI 控件匹配),那么实际上 270 * 190 像素的图片,只需要 0.2M 的内存即可。可以看到,优化前后相差了 98 倍,原来显示 1 张,现在可以显示 98 张图片,效果非常显著。
既然在对原图缩放可以显著减少内存大小,那么我们应该如何操作呢?先加载到内存,再进行操作吗,可以如果先加载到内存,好像也不太对,这样只接占用了 19.6M + 0.2M 2 份内存了,而我们想要的是,在原图不加载到内存中,只接将缩放后的图片加载到内存中,可以实现吗?
BitmapFactory 提供了从不同资源创建 Bitmap 的解码方法:decodeByteArray()、decodeFile()、decodeResource() 等。
但是,这些方法在构造位图的时候会尝试分配内存,也就是它们会导致原图直接加载到内存了,不满足我们的需求。我们可以通过 BitmapFactory.Options 设置一些附加的标记,指定解码选项,以此来解决该问题。
如何操作呢?答案来了:将 inJustDecodeBounds 属性设置为 true,可以在解码时避免内存的分配,它会返回一个 null 的 Bitmap ,但是可以获取 outWidth、outHeight 和 outMimeType 值。利用该属性,我们就可以在图片不占用内存的情况下,在图片压缩之前获取图片的尺寸。
怎样才能对图片进行压缩呢?通过设置 BitmapFactory.Options 中 inSampleSize 的值就可以实现。其计算方式大概就是:计算出实际宽高和目标宽高的比率,然后选择宽和高中最小的比率作为 inSampleSize 的值,这样可以保证最终图片的宽和高。
3.2.2 设置 BitmapFactory.Options 属性
BitmapFactory.Options.inSampleSize 的核心思想是通过降低图片的分辨率来减少内存占用。它是在解码阶段生效的,因此它可以在图片加载到内存之前就完成压缩,避免不必要的内存消耗。
大概步骤如下所示:
要将 BitmapFactory.Options 的 inJustDecodeBounds 属性设置为 true,解析一次图片。注意这个地方是核心,这个解析图片并没有生成 bitmap 对象(也就是说没有为它分配内存控件),而仅仅是拿到它的宽高等属性。
然后将 BitmapFactory.Options 连同期望的宽度和高度一起传递到到 calculateInSampleSize 方法中,就可以得到合适的 inSampleSize 值了。这一步会压缩图片。
再解析一次图片,使用新获取到的 inSampleSize 值,并把 inJustDecodeBounds 设置为 false,就可以得到压缩后的图片了。此时才正式创建了 bitmap 对象,由于前面已经对它压缩了,所以你会发现此时所占内存大小已经很少了。
具体的实现代码:
思考:inJustDecodeBounds 这个参数是干什么的?如果设置为 true 则表示 decode 函数不会生成 bitmap 对象,仅是将图像相关的参数填充到 option 对象里,这样我们就可以在不生成 bitmap 而获取到图像的相关参数了。
为何设置两次inJustDecodeBounds
属性?
第一次:设置为
true
则表示 decode 函数不会生成bitmap
对象,仅是将图像相关的参数填充到option
对象里,这样我们就可以在不生成bitmap
而获取到图像的相关参数。第二次:将
inJustDecodeBounds
设置为false
再次调用decode
函数时就能生成bitmap
了。而此时的bitmap
已经压缩减小很多了,所以加载到内存中并不会导致 OOM。
3.3 图片质量压缩
图片质量压缩核心思想是:一种通过减少图像文件中像素的细节和信息来降低图像文件大小的技术。这种压缩方法通常会牺牲一定程度的图像质量,以换取更小的文件大小。
减少细节和信息:图片质量压缩通过减少图像文件中的细节和信息来降低文件大小。这可能包括减少颜色深度、降低图像分辨率、去除不可见的细节等。
压缩算法:常用的图片质量压缩算法是基于 JPEG 格式的压缩。JPEG 压缩算法通过调整图像的色彩和细节来实现压缩,可以通过控制压缩质量参数来调整压缩比例。
质量参数:JPEG 压缩算法中的质量参数通常是一个介于 0 到 100 之间的值,表示压缩的质量等级。
在 Android 中,对图片进行质量压缩,通常我们的实现方式如下所示:
在上述代码中,我们选择的压缩格式是 CompressFormat.JPEG,除此之外还有两个选择:
其一,CompressFormat.PNG,PNG 格式是无损的,它无法再进行质量压缩,quality 这个参数就没有作用了,会被忽略,所以最后图片保存成的文件大小不会有变化;
其二,CompressFormat.WEBP,这个格式是 google 推出的图片格式,它会比 JPEG 更加省空间,经过实测大概可以优化 30%左右。
Android 质量压缩逻辑,函数 compress 经过一连串的 java 层调用之后,最后来到了一个 native 函数:
具体看:Bitmap.cpp,最后调用了函数 encoder->encodeStream(…)编码保存本地。该函数是调用 skia 引擎来对图片进行编码压缩。
3.4 双线性采样压缩
核心思路是什么:这种压缩方法通过对图像进行插值计算,以平滑地减少图像的像素数量,同时尽可能保留图像的细节和质量。
双线性采样(Bilinear Resampling)在 Android 中的使用方式一般有两种:
看源码可以知道 createScaledBitmap 函数最终也是使用第二种方式的 matrix 进行缩放
双线性采样使用的是双线性內插值算法,这个算法不像邻近点插值算法一样,直接粗暴的选择一个像素,而是参考了源像素相应位置周围 2x2 个点的值,根据相对位置取对应的权重,经过计算之后得到目标图像。
3.5 高清图分片加载
如何理解高清图分片加载的核心思想:将大尺寸的高清图像分割成多个小块(片段),然后根据显示需求动态加载和拼接这些片段,以实现高清图像的显示。这种技术可以帮助减少内存占用和提高性能,同时保持高清图像的清晰度。
这种技术常用于需要展示高清图像的应用场景,如图片查看器、地图应用等,以实现高质量的图像显示效果。
适用场景 : 当一张图片非常大 , 在手机中只需要显示其中一部分内容 , BitmapRegionDecoder 非常有用 。
主要作用 : BitmapRegionDecoder 可以从图像中 解码一个矩形区域 。相当于手在滑动的过程中,计算当前显示区域的图片绘制出来。
基本使用流程 : 先创建,后解码 。调用 newInstance 方法 , 创建 BitmapRegionDecoder 对象 ;然后调用 decodeRegion 方法 , 获取指定 Rect 矩形区域的解码后的 Bitmap 对象。
3.6 鲁班图片综合压缩
一般情况下图片综合压缩的整体思路如下:
第一步进行采样率压缩;
第二步进行宽高的等比例压缩(微信对原图和缩略图限制了最大长宽或者最小长宽);
第三步就是对图片的质量进行压缩(一般 75 或者 70);
第四步就是采用 webP 的格式。
关于图片压缩的综合案例如下,具体可以参考:CompressServer
04.色彩格式及内存优化
4.1 RGB 颜色种类
RGB 色彩模式是工业界的一种颜色标准,通过对红(R)、绿(G)、蓝(B)三个颜色通道的变化以及它们相互之间的叠加来得到各式各样的颜色的,RGB 即是代表红、绿、蓝三个通道的颜色,这个标准几乎包括了人类视力所能感知的所有颜色,是运用最广的颜色系统之一。Android 中,像素的存储方式使用的色彩模式正是 RGB 色彩模式。
基本原理:RGB 色彩模式基于三种原色(红、绿、蓝),通过不同强度的这三种颜色的组合来产生各种其他颜色。通过调整每种原色的亮度和饱和度,可以生成数百万种不同的颜色。
颜色表示:在 RGB 色彩模式中,每种颜色由一个三元组(R,G,B)表示,其中 R、G、B 的取值范围通常是 0 到 255,表示每种颜色的强度。例如,(255, 0, 0) 表示纯红色,(0, 255, 0) 表示纯绿色,(0, 0, 255) 表示纯蓝色。
颜色混合:通过调整不同原色的强度,可以混合出各种中间色。例如,红色和绿色的混合会产生黄色。
色彩范围:RGB 色彩模式可以表示的颜色范围非常广泛,可以生成几乎所有可见颜色,包括各种饱和度和亮度的颜色。
4.2 ARGB 色彩模式
ARGB 色彩模式是一种在计算机图形学和图像处理中常用的色彩表示方式,它是在 RGB 色彩模式的基础上增加了一个 Alpha 通道,用于表示像素的透明度。
在 Android 中,我们常见的一些颜色设置,都是 RGB 色彩模式来描述像素颜色的,并且他们都带有透明度通道,也就是所谓的 ARGB。例如,我们常见的颜色定义如下:
以上设置中,颜色值都是使用 16 进制的数字来表示的。以上颜色值都是带有透明度(透明通道)的颜色值,格式是 AARRGGBB,透明度、红色、绿色、蓝色四个颜色通道,各占有 2 位,也就是一个颜色通道,使用了 1 个字节来存储。
4.3 改变色彩格式优化
Android 中有多种 RGB 模式,我们可以设置不同的格式,来控制图片像素颜色的显示质量和存储空间。
Android.graphics.Bitmap 类里有一个内部类 Bitmap.Config 类,它定义了可以在 Android 中使用的几种色彩格式:
解释一下这几个值分别代表了什么含义?我们已经知道了:A 代表透明度、R 代表红色、G 代表绿色、B 代表蓝色。
ALPHA_8:表示,只存在 Alpha 通道,没有存储色彩值,只含有透明度,每个像素占用 1 个字节的空间。
RGB_565:表示,R 占用 5 位二进制的位置,G 占用了 6 位,B 占用了 5 位。每个像素占用 2 个字节空间,并且不包含透明度。
ARGB_4444:表示,A(透明度)、R(红色)、G(绿色)、B(蓝色)4 个通道各占用 4 个 bit 位。每个像素占用 2 个字节空间。
ARGB_8888:表示,A(透明度)、R(红色)、G(绿色)、B(蓝色)4 个通道各占用 8 个 bit 位。每个像素占用 4 个字节空间。
RGBA_F16:表示,每个像素存储在 8 个字节上。此配置特别适合广色域和 HDR 内容。
HARDWARE:特殊配置,当位图仅存储在图形内存中时。 此配置中的位图始终是不可变的。
那么开发中一般选择哪一种比较合适呢
Android 中的图片在加载时,默认的色彩格式是 ARGB_8888,也就是每个像素占用 4 个字节空间,一张 2700 * 1900 像素的照片,加载到内存就需要 19.6M 内存空间(2592 * 1936 * 4 bytes)。
如果图片在 UI 组件中显示时,不需要太高的图片质量,例如显示一张缩略图(不透明图片)等场景,这时,我们就没必要使用 ARGB_8888 的色彩格式了,只需要使用 RGB_565 模式即可满足显示的需要。
那么,我们的优化操作就可以是:将 2700 * 1900 像素的原图,压缩到原图的低分辨率的缩略图 270 * 190 像素的图片,这时需要 0.2M 的内存。也就是从 19.6M 内存,压缩为 0.2 M 内存。
我们还可以进一步优化色彩格式,由 ARGB_8888 改为 RGB_565 模式,这时,目标图片需要的内存就变为 270 * 190 * 2 = 0.1M 了。图片内存空间又减小了一倍。
05.图片内存缓存设计
5.1 图片内存缓存思想
图片内存缓存的设计是优化应用性能的关键。内存缓存的核心思想是利用内存的高速读写特性,缓存最近使用的图片,从而避免重复加载和减少磁盘 I/O 操作。
快速访问:内存缓存的读写速度远高于磁盘缓存和网络加载,因此将最近使用的图片保存在内存中,可以显著提升图片加载速度。
有限资源管理:内存资源有限,因此需要合理管理缓存大小,避免占用过多内存导致应用崩溃或性能下降。
淘汰策略:当缓存达到上限时,需要淘汰部分缓存项以释放空间。常用的淘汰策略是 LRU(Least Recently Used,最近最少使用)。
5.2 Lru 内存缓存
LruCache 类特别适合用来缓存 Bitmap,它使用一个强引用的 LinkedHashMap 保存最近引用的对象,并且在缓存超出设定大小时,删除最近最少使用的对象。
给 LruCache 确定一个合适的缓存大小非常重要,我们需要考虑几个因素:
应用剩余多少可用内存?
需要有多少张图片同时显示到屏幕上?有多少图片需要准备好以便马上显示到屏幕?
设备的屏幕大小和密度是多少?高密度的设备需要更大的缓存空间来缓存同样数量的图片。
Bitmap 的尺寸配置是多少,花费多少内存?
图片被访问的频率如何?如果其中一些比另外一些访问更频繁,那么我们可能希望在内存中保存那些最常访问的图片,或者根据访问频率给 Bitmap 分组,为不同的 Bitmap 组设置多个 LruCache 对象。
是否可以在缓存图片的质量和数量之间寻找平衡点?有时,保存大量低质量的 Bitmap 会非常有用,加载更高质量的图片的任务可以交给另外一个后台线程处理。
缓存太小会导致额外的花销却没有明显的好处,缓存太大同样会导致 java.lang.OutOfMemory 的异常,并且使得你的程序只留下小部分的内存用来工作(缓存占用太多内存,导致其他操作会因为内存不够而抛出异常)。所以,我们需要分析实际情况之后,提出一个合适的解决方案。
LruCache 是 Android 提供的一个缓存类,通常运用于内存缓存,LruCache 是一个泛型类,它的底层是用一个 LinkedHashMap 以强引用的方式存储外界的缓存对象来实现的。
为什么使用 LinkedHashMap 来作为 LruCache 的存储,是因为 LinkedHashMap 有两种排序方式,一种是插入排序方式,一种是访问排序方式,默认情况下是以访问方式来存储缓存对象的;LruCache 提供了 get 和 put 方法来完成缓存的获取和添加,当缓存满时,会将最近最少使用的对象移除掉,然后再添加新的缓存对象。如下源码所示,底层是 LinkedHashMap。
在使用 LruCache 的时候,首先需要获取当前设备的内存容量,通常情况下会将总容量的八分之一作为 LruCache 的容量,然后重写 LruCache 的 sizeof 方法,sizeof 方法用于计算缓存对象的大小,单位需要与分配的容量的单位一致;
如何淘汰缓存,这个就要看 LinkedHashMap 集合的特点呢!LinkedHashMap 构造函数的第三个参数:accessOrder,传入 true 时, 元素会按访问顺序排列,最后访问的在遍历器最后端。在进行淘汰时,移除遍历器前端的元素,直至缓存总大小降低到指定大小以下。
5.3 Lru 缓存注意事项
看一个真实的场景:假设我们的 LruCache 可以缓存 80 张,每次刷新从网络获取 20 张图片且不重复,那么在刷新第五次的时候,根据 LruCache 缓存的规则,第一次刷新的 20 张图片就会从 LruCache 中移出,处于等待被系统 GC 的状态。如果我们继续刷新 n 次,等待被回收的张数就会累积到 20 * n 张。
会出现什么问题:会出现大量的 Bitmap 内存碎片,我们不知道系统什么时候会触发 GC 回收掉这些无用的 Bitmap,对于内存是否会溢出,是否会频繁 GC 导致卡顿等未知问题。
解决方案该怎么做?
第一种:在 3.0 以后引入了 BitmapFactory.Options.inBitmap,如果设置此项,需要解码的图片就会尝试使用该 Bitmap 的内存,这样取消了内存的动态分配,提高了性能,节省了内存。
第二种:把处于无用的状态的 Bitmap 放入 SoftReference。SoftReference 引用的对象会在内存溢出之前被回收。
关于 Lru 缓存案例和代码可以参考:AppLruCache
5.4 使用 Lru 磁盘缓存
内存缓存能够提高访问最近用过的 Bitmap 的速度,但是我们无法保证最近访问过的 Bitmap 都能够保存在缓存中。像类似 GridView 等需要大量数据填充的控件很容易就会用尽整个内存缓存。另外,我们的应用可能会被类似打电话等行为而暂停并退到后台,因为后台应用可能会被杀死,那么内存缓存就会被销毁,里面的 Bitmap 也就不存在了。一旦用户恢复应用的状态,那么应用就需要重新处理那些图片。
磁盘缓存可以用来保存那些已经处理过的 Bitmap,它还可以减少那些不再内存缓存中的 Bitmap 的加载次数。当然从磁盘读取图片会比从内存要慢,而且由于磁盘读取操作时间是不可预期的,读取操作需要在后台线程中处理。
注意:如果图片会被更频繁的访问,使用 ContentProvider 或许会更加合适,比如在图库应用中。
注意:因为初始化磁盘缓存涉及到 I/O 操作,所以它不应该在主线程中进行。但是这也意味着在初始化完成之前缓存可以被访问。为了解决这个问题,在上面的实现中,有一个锁对象(lock object)来确保在磁盘缓存完成初始化之前,应用无法对它进行读取。
内存缓存的检查是可以在 UI 线程中进行的,磁盘缓存的检查需要在后台线程中处理。磁盘操作永远都不应该在 UI 线程中发生。当图片处理完成后,Bitmap 需要添加到内存缓存与磁盘缓存中,方便之后的使用。
06.不同版本对 Bitmap 管理
6.1 演变进程
Android 2.3.3 (API level 10)以及之前,一个 Bitmap 的像素数据是存放在 Native 内存空间中的。这些数据与 Bitmap 对象本身是隔离的,Bitmap 本身被存放在 Dalvik 堆中。并且无法预测在 Native 内存中的像素级数据何时会被释放,这意味着程序容易超过它的内存限制并且崩溃。
Android 3.0 (API Level 11)开始,像素数据则是与 Bitmap 本身一起存放在 Dalvik 堆中。
Android 8.0(Android O)及之后的版本中,Bitmap 的像素数据的内存分配又回到了 Native 层,它是在 Native 堆空间进行分配的。
6.2 管理 Bitmap 内存
管理 Android 2.3.3 及以下版本的内存使用。在 Android 2.3.3 (API level 10) 以及更低版本上,推荐使用 recycle() 方法。 如果在应用中显示了大量的 Bitmap 数据,我们很可能会遇到 OutOfMemoryError 的错误。 recycle() 方法可以使得程序更快的释放内存。
管理 Android 3.0 及其以上版本的内存,从 Android 3.0 (API Level 11)开始,引进了 BitmapFactory.Options.inBitmap 字段。 如果使用了这个设置字段,decode 方法会在加载 Bitmap 数据的时候去重用已经存在的 Bitmap。这意味着 Bitmap 的内存是被重新利用的,这样可以提升性能,并且减少了内存的分配与回收。然而,使用 inBitmap 有一些限制,特别是在 Android 4.4 (API level 19)之前,只有同等大小的位图才可以被重用。
管理 Android 8.0 及其以上版本的内存。在 Android 8.0 及其以上版本,处理内存,也遵循 Android 3.0 以上版本同样的方式。同时,图片像素数据存储在 native 层,并且不占用 Java 堆的空间,这也代表着我们拥有更大的图片存储空间,可以加载质量更高、数据更多的图片到内存中。但是,内存依然不是无限的,应用还是要受到手机内存的限制,所以一定要注意这一点。
6.3 提高 Bitmap 复用
Android3.0 之后,并没有强调 Bitmap.recycle();而是强调 Bitmap 的复用。
使用 LruCache 对 Bitmap 进行缓存,当再次使用到这个 Bitmap 的时候直接获取,而不用重走编码流程。
Android3.0(API 11 之后)引入了 BitmapFactory.Options.inBitmap 字段,设置此字段之后解码方法会尝试复用一张存在的 Bitmap。这意味着 Bitmap 的内存被复用,避免了内存的回收及申请过程,显然性能表现更佳。
使用这个字段有几点限制:
声明可被复用的 Bitmap 必须设置 inMutable 为 true;
Android4.4(API 19)之前只有格式为 jpg、png,同等宽高(要求苛刻),inSampleSize 为 1 的 Bitmap 才可以复用;
Android4.4(API 19)之前被复用的 Bitmap 的 inPreferredConfig 会覆盖待分配内存的 Bitmap 设置的 inPreferredConfig;
Android4.4(API 19)之后被复用的 Bitmap 的内存必须大于需要申请内存的 Bitmap 的内存;
Android4.4(API 19)之前待加载 Bitmap 的 Options.inSampleSize 必须明确指定为 1。
Bitmap 复用的实验,代码如下所示,然后看打印的日志信息
从内存地址的打印可以看出,两个对象其实是一个对象,Bitmap 复用成功;
bitmapReuse 占用的内存(4346880)正好是 bitmap 占用内存(1228800)的四分之一;
getByteCount()获取到的是当前图片应当所占内存大小,getAllocationByteCount()获取到的是被复用 Bitmap 真实占用内存大小。虽然 bitmapReuse 的内存只有 4346880,但是因为是复用的 bitmap 的内存,因而其真实占用的内存大小是被复用的 bitmap 的内存大小(1228800)。这也是 getAllocationByteCount()可能比 getByteCount()大的原因。
07.图片其他方面优化
7.1 减少 PNG 图片使用
这里要介绍一种新的图片格式:Webp,它是由 Google 推出的一种既保留 png 格式的优点,又能够减少图片大小的一种新型图片格式。
在 Android 4.0(API level 14) 中支持有损的 WebP 图像,在 Android 4.3(API level 18) 和更高版本中支持无损和透明的 WebP 图像。
注意一下,Webp 格式图片仅仅只是减少图片的质量大小,并不会减少加载图片后的内存占用。
Android 为何推荐用 WebP 格式图片:WebP 格式的图片压缩率通常比 JPEG 和 PNG 更高,可以在保证图像质量的前提下显著减少文件大小。支持无损压缩,支持透明度等。
7.2 切割圆角优化
业务背景介绍:在显示图片是有时候需要显示圆角图片,我们应该都知道圆角显示肯定是更加耗费内存和性能,会导致图片的过度绘制等问题。
给控件设置圆角,代码层面一般怎么做?
第一种:比如给 TextView 设置 Shape 圆角
第二种:自定义控件实现,大概原理:要实圆角或者圆形的显示效果,就是对图片显示的内容区域进行“裁剪”,只显示指定的区域即可。
第三种:使用 Glide 加载图片设置圆角
clipPath 切割圆角的核心原理
第一步:图片是被绘制在画布上的,所以用 canvas 的 clipPath()方法先将画布裁剪成指定形状。由于 clipPath()方法不支持抗锯齿,图片边缘会有明显的毛糙感,体验并不理想。
setXfermode 切割圆角的核心原理
使用图像的 Alpha 合成模式。整个过程就是先绘制目标图像,也就是图片;再绘制原图像,即一个圆角矩形或者圆形,这样最终目标图像只显示和原图像重合的区域。如果是图片,需要通过 src 属性或者对应的方法来设置图片,否则不能达到预期效果。
第一步:核心逻辑是在 draw 方法中进行绘制,首先调用 canvas.saveLayer 设置离屏缓存,新建一个控件区域大小的图层
第二步:相当于使用 super.onDraw 绘制自己,这一步调用 super 方法即可
第三步:设置画笔 Paint 属性,先调用 setXfermode 设置混合模式,然后在调用 canvas.drawPath(path, paint)绘制 path,最后再清除 Xfermode
第四步:添加边框,只需要绘制一个指定样式的圆角矩形或者圆形即可。比如绘制圆角,调用 Path.addRoundRect 添加 path,然后调用 canvas.drawPath 绘制 path
目前设置控件圆角有哪些方式
第一种:比如给 TextView 设置 Shape 圆角,非常常见的使用
第二种:使用背景图片
第三种:自定义控件实现
第四种:使用 ViewOutlineProvider 裁剪 View
第五种:使用 CardView
第六种:使用 Glide 加载图片设置圆角
各种设置圆角的优缺点对比
第一种:shape 常见,简单直观。缺点是项目中 xml,越写越多
第二种:使用切图没什么说的,使用起来不方便
第三种:自定义控件,弥补 shape 上不足,采用 attr 属性设置圆角,那样圆角样式多,使用起来方便
第四种:用于实现 view 阴影和轮廓
第五种:使用 CardView,官方支持阴影和圆角控件
第六种:使用 Glide 加载圆角,一般用于图片设置,比较方便
具体案例可见:RoundCorners
7.3 如何给图片置灰色
大概的操作步骤。具体可以参考:PicCalculateUtils
第一步:获取原始图片的宽高,然后创建一个 bitmap 可变位图对象。
第二步:创建画板 canvas 对象,然后创建画笔 paint。然后调用 canvas.drawBitmap 方法绘制图片
第三步:对画笔进行修饰,设置画笔颜色属性,这里使用到了 ColorMatrix,核心就是设置饱和度为 0,即可绘制灰色内容
7.4 如何处理图片旋转呢
在 Android 中使用 ImageView 显示图片的时候发现图片显示不正,方向偏了或者倒过来了。
解决这个问题很自然想到的两步走,首先是要自动识别图像方向,计算旋转角度,然后对图像进行旋转并显示。
第一步:识别图像方向
首先在这里提一个概念 EXIF(Exchangeable Image File Format,可交换图像文件)。简而言之,Exif 是一个标准,用于电子照相机(也包括手机、扫描器等)上,用来规范图片、声音、视屏以及它们的一些辅助标记格式。
Exif 支持的格式如下:图像;压缩图像文件:JPEG、DCT;非压缩图像文件:TIFF;音频;RIFF、WAV
Android 提供了对 JPEG 格式图像 Exif 接口支持,可以读取 JPEG 文件 metadata 信息,参见 ExifInterface。这些 Metadata 信息总的来说大致分为三类:日期时间、空间信息(经纬度、高度)、Camera 信息(孔径、焦距、旋转角、曝光量等等)。
第二步:关于图像旋转
获取了图片的旋转方向后,然后再设置图像旋转。最后 Bitmap 提供的静态 createBitmap 方法,可以对图片设置旋转角度。具体看:PicCalculateUtils
7.5 保存图片且刷相册
大概的操作步骤如下所示。具体可看:ImageSaveUtils
第一步:创建图片文件,然后将 bitmap 对象写到图片文件中
第二步:通过 MediaStore 将图片插入到共享目录相册中
第三步:发送通知,通知相册中刷新插入图片的数据。注意,获取图片资源 uri 刷新即可,避免刷新所有数据造成等待时间过长。
MediaStore 是 Android 提供的一个内容提供者(Content Provider),用于管理设备上的媒体文件(如图片、视频、音频等)。通过 MediaStore,可以将图片文件插入到系统的媒体库中,使其在相册中可见。
7.6 统一图片域名优化
域名统一,减少了 10%+的重复图片下载和内存消耗。同时减少之前多域名图片加载时重复创建 HTTPS 请求的过程,减少图片加载时间。
统一图片域名优化 是一种通过将图片资源统一托管在同一个域名下,并结合 CDN(内容分发网络)和图片处理服务,来提升图片加载性能、降低服务器压力、优化用户体验的技术手段。
7.7 优化 H5 图片加载
通过拦截 WebView 图片加载的方式,让原生图片库来下载图片之后传递图片二进制数据给 WebView 显示。
采用 OkHttp 拦截资源缓存,下面是大概的思路。缓存的入口从 shouldInterceptRequest 出发
第一步,拿到 WebResourceRequest 对象中请求资源的 url 还有 header,如果开发者设置不缓存则返回 null
第二步,如果缓存,通过 url 判断拦截资源的条件,过滤非 http,音视频等资源,这个是可自由配置缓存内容比如 css,png,jpg,xml,txt 等
第三步,判断本地是否有 OkHttp 缓存数据,如果有则直接读取本地资源,通过 url 找到对应的 path 路径,然后读取文件流,组装数据返回。
第四步,如果没有缓存数据,创建 OkHttp 的 Request 请求,将资源网络请求交给 okHttp 来处理,并且用它自带的缓存功能,当然如果是请求失败或者异常则返回 null,否则返回正常数据
关于 webView 图片缓存的方案,可以直接参考:YCWebView
7.8 优化图片阴影效果
阴影效果有哪些实现方式
第一种:使用 CardView,但是不能设置阴影颜色
第二种:采用 shape 叠加,存在后期 UI 效果不便优化
第三种:UI 切图
第四种:自定义 View
第五种:自定义 Drawable
否定上面前两种方案原因分析?
第一个方案的 CardView 渐变色和阴影效果很难控制,只能支持线性或者环装形式渐变,这种不满足需要,因为阴影本身是一个四周一层很淡的颜色包围,在一个矩形框的层面上颜色大概一致,而且这个 CardView 有很多局限性,比如不能修改阴影的颜色,不能修改阴影的深浅。所以这个思路无法实现这个需求。
第二个采用 shape 叠加,可以实现阴影效果,但是影响 UI,且阴影部分是占像素的,而且不灵活。
第三个方案询问了一下 ui。他们给出的结果是如果使用切图的话那标注的话很难标,身为一个优秀的设计师大多对像素点都和敏感,界面上的像素点有一点不协调那都是无法容忍的。
网上一些介绍阴影效果方案
所有在深奥的技术,也都是为需求做准备的。也就是需要实践并且可以用到实际开发中,这篇文章不再抽象介绍阴影效果原理,理解三维空间中如何处理偏移光线达到阴影视差等,网上看了一些文章也没看明白或者理解。这篇博客直接通过调用 api 实现预期的效果。
多个 drawable 叠加,使用 layer-list 可以将多个 drawable 按照顺序层叠在一起显示,默认情况下,所有的 item 中的 drawable 都会自动根据它附上 view 的大小而进行缩放,layer-list 中的 item 是按照顺序从下往上叠加的,即先定义的 item 在下面,后面的依次往上面叠放
阴影是否占位?1.使用 CardView 阴影不占位,不能设置阴影颜色和效果。2.使用 shape 阴影是可以设置阴影颜色,但是是占位的
几种方案优缺点对比分析
CardView 优点:自带功能实现简单 缺点:自带圆角不一定可适配所有需求
layer(shape 叠加) 优点:实现形式简单 缺点:效果一般
自定义实现 优点:实现效果好可配置能力高 缺点:需要开发者自行开发
关于解决阴影效果,具体各种方案的对比可以参考这个 demo:AppShadowLib
7.9 图片资源的压缩
我们应用中使用的图片,设计师出的原图通常都非常大,他们通常会使用工具,经过一定的压缩,缩减到比较小一些的大小。
但是,这些图片通常都有一定的可压缩空间,我在之前的项目中,对图片进行了二次压缩,整体压缩率达到了 40%~50% ,效果还是非常不错的。
这里介绍下常用的,图片压缩的方法:
使用压缩工具对图片进行二次压缩。
根据最终图片是否需要透明度展示,优先选择不透明的图片格式,例如,我们应该避免使用 png 格式的图片。
对于色彩简单,例如,一些背景之类的图片,可以选择使用布局文件来定义(矢量图),这样就会非常节省内存了。
如果包含透明度,优先使用 WebP 等格式图像。
图片在上线前进行压缩处理,不但可以减少内存的使用,如果图片是网络获取的,也可以减少网络加载的流量和时间。推荐一个图片压缩网站:tinypng网站
08.笔记汇总一下
1.1 为什么说图片比较占用内存:一个手机拍摄的 2700 * 1900 像素的照片,需要 5.1M 的存储空间,但是在图像解码配置 ARGB_8888 时,它加载到内存需要 19.6M 内存空间
1.2 加载网络图片大概流程:1.获取网络资源文件流;2.decodeStream 加载到 bitmap 中;3.然后压缩图片;4.然后处理图片变换;5.图片缓存策略;
1.3 三方库加载图片逻辑大概是怎样:从网络拉取图片;然后解码图片;然后进行压缩;接着会有图片常用圆角裁剪等处理;然后三级缓存加载的图片;当然加载图片过程涉及同步加载和异步加载;最后设置到具体 view 控件上。
1.4 BitmapFactory 可以加载哪些内容:提供了四类方法来加载 Bitmap:decodeFile 加载文件、decodeResource 加载资源、decodeStream 加载流、decodeByteArray 加载字节数组。
1.5 如何理解图片大小和占用内存:图片大小是经过相对应的压缩算法将原图每个像素点信息转换用另一种数据格式表示;图片内存是这张图片加载进内存所占用的大小。
1.6 Bitmap 能直接存储吗:bitmap 是一个对象,如果要存储成本地可以查看的图片文件,则必须对 bitmap 进行编码,然后通过 io 流写到本地 file 文件上。
1.7 Bitmap 创建的对象内存分配在哪里:8.0 之前版本为 Bitmap 像素从 Java heap 申请内存。 8.0 版本后为 Bitmap 像素从 Native heap 申请内存。
1.8 如果你是谷歌开发,图片框架如何设计:大多数图片框架加载流程,概括来说,图片加载包含封装,解析,下载,解码,变换,缓存,显示等操作。
2.1 如何计算图片占用内存:bitmap 内存大小 = 图片长度 x 图片宽度 x 单位像素占用的字节数(这个就跟编码方式有关了)
2.2 加载本地资源图片占用内存怎么计算:它占用的内存 = width * height * nTargetDensity/inDensity 一个像素所占的内存。
2.3 一个像素占用多大内存:ARGB_8888: 每个像素 4 字节. 共 32 位,默认设置。RGB_565:共 16 位,2 字节,只存储 RGB 值。
2.5 影响 Bitmap 内存因素有哪些:图片加载的分辨率;编码格式;设备的屏幕密度等因素。
2.6 加载 xhdpi 和 xxhdpi 图片有何区别:相同的图片,显示在控件上面所占用内存是不一样的。因为两个文件夹对应的 dpi 是不一样的。
2.7 图片放在 res 不同目录,加载的内存是一样的吗:在加载 res 目录下的资源图片时,会根据图片存放的不同目录做一次分辨率的转换,新图的高度 = 原图高度 * (设备的 dpi / 目录对应的 dpi )
3.1 常见图片压缩有哪些:1.质量压缩不会对内存产生影响;2.内存压缩,按照比例缩小图片加载减少内存占用
3.2.1 如何理解图片尺寸压缩:原理是通过降低图片的宽度和高度(即减少像素数量),从而减少图片的存储大小。
3.2.2 BitmapFactory.Options.inSampleSize 核心思想是什么:是通过降低图片的分辨率来减少内存占用。它是在解码阶段生效的,因此它可以在图片加载到内存之前就完成压缩,避免不必要的内存消耗。
3.2.3 inJustDecodeBounds 这个参数是干什么的:如果设置为 true 则表示 decode 函数不会生成 bitmap 对象,仅是将图像相关的参数填充到 option 对象里,这样我们就可以在不生成 bitmap 而获取到图像的相关参数。
3.3 如何理解图片质量压缩:通过减少图像文件中像素的细节和信息来降低图像文件大小的技术。这种压缩方法通常会牺牲一定程度的图像质量,以换取更小的文件大小。
3.4 如何理解双线性采样压缩:这种压缩方法通过对图像进行插值计算,以平滑地减少图像的像素数量,同时尽可能保留图像的细节和质量。
3.5 高清图分片加载的核心思想:将大尺寸的高清图像分割成多个小块(片段),根据显示需求动态加载和拼接这些片段,以实现高清图像的显示。帮助减少内存占用和提高性能,保持高清图像的清晰度。
4.1 如何理解 RGB 色彩模式:一种基于红、绿、蓝三种原色的彩色表示方式,通过不同比例的这三种颜色的组合来表示各种颜色。
4.2 如何理解 ARGB 色彩模式:在 RGB 色彩模式基础上增加了透明度通道,用于控制像素的透明度,提供了更多的图像处理和显示选项。
4.3 Android 中色彩格式如何选择:1.RGB 格式表示红蓝绿;2.ARGB 格式表示增加一个 Alpha 透明通道;3.十六进制格式颜色
5.1 Android 图片内存缓存思想是什么:内存缓存的核心思想是利用内存的高速读写特性,缓存最近使用的图片,从而避免重复加载和减少磁盘 I/O 操作。
5.2 Lru 内存缓存核心思想是什么:一种基于 最近最少使用(LRU) 策略的缓存算法,广泛应用于内存缓存设计中。其核心思想是通过淘汰最近最少使用的缓存项,来高效管理有限的缓存空间。
5.3 Lru 缓存注意事项有哪些:
6.1 Bitmap 演变进程是什么:不同版本对 Bitmap 管理有一些差异,主要涉及到内存管理、资源回收和性能优化等方面。
7.1 为何推荐用 WebP 格式图片:WebP 格式的图片压缩率通常比 JPEG 和 PNG 更高,可以在保证图像质量的前提下显著减少文件大小。支持无损压缩,支持透明度等。
7.4 如何处理图片旋转:解决这个问题很自然想到的两步走,首先是要自动识别图像方向,计算旋转角度,然后对图像进行旋转并显示。
7.5 如何保存图片到相册:MediaStore 是 Android 提供的一个内容提供者(Content Provider),用于管理设备上的媒体文件(如图片、视频、音频等)。
7.6 如何理解统一图片域名优化:将图片资源统一托管在同一个域名下,并结合 CDN(内容分发网络)和图片处理服务,来提升图片加载性能、降低服务器压力、优化用户体验的技术手段。
7.7 如何理解优化 H5 图片加载:通过拦截 WebView 图片加载的方式,让原生图片库来下载图片之后传递图片二进制数据给 WebView 显示。
版权声明: 本文为 InfoQ 作者【杨充】的原创文章。
原文链接:【http://xie.infoq.cn/article/91c697ea256c17ddb0ada11a1】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论