图片、glide优化
图片优化
[[图片低配系统 oom 优化]]
一、图片压缩的意义是什么
1.节约流量、2.降低服务器带宽、3.降低 app 内存占用
二、图片压缩的分类
1.质量压缩(图片存储卡大小)
2.尺寸压缩 (内存)
三、Android6.0与 7.0压缩 JPEG 图片的区别
如果是一张的话压缩处理,大量图片的话用 lru
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024)
若发生OOM,则使用try catch将其捕获,然后清除图片cache,尝试降低bitmap format
由于webview存在内存系统泄漏,还有图库占用内存过多的问题,可以采用单独的进程。
Chrome Devtool 在浏览器调试H5
图片:html用webview加载了也就10几行代码,古董机也可以加载出来
view 的大小比图片小如何优化
PNG 图片部分:
我们修改 imageview 的大小并不会影响 PNG 图片占用内存大小
SVG 矢量图部分:
同样我们修改 imageview 的大小并不会SVG影响图片占用内存大小,需要注意的是 SVG 图片实际的显示分辨率是由SVG 的 xml文件中的 width/height 决定的,所以SVG 既然支持缩放,那我们就把宽高写小了好了,这样可以省内存。
总结
图片压缩大概分为两类,质量压缩和尺寸压缩。例如一些固定宽高的图片,feed,头像等等就可以对尺寸压缩,或者请求指定宽高的图片。修改图片的格式、质量、编码格式。在内存不足的时候可以清除内存缓存,在滚动的时候可以暂停图片加载,停下来的时候再去加载。glide也可以裁剪(请求属性不申请内存)。
onTrimMemory,调用 Glide.cleanMemroy() 清理掉所有的内存缓存。(内部是LruBitmapPool )
升级到 Glide4.0,使用 asDrawable 代替 asBitmap,drawable 更省内存。(或者下载下来,用空间换时间)
对于一些低端设备,我们可以将图片格式从 ARGB_8888 变为 RGB_565,这样一个简单的调整,可以让图片内存的占用减少一半;又例如在适当的时机,主动回收掉一些图片缓存
低端机判断:
总内存小、Cpu (1.5G 低端)、SDK不使用application作为context。当context为application时,会把imageView是生命周期延长到整个运行过程中,imageView不能被回收,从而造成OOM异常。
当列表在滑动的时候,调用Glide的pauseRequests()取消请求,滑动停止时,调用resumeRequests()恢复请求。
1 |
|
createBitmap
Bitmap.createBitmap () 方法,从名字上就可以看出,它是为了创建一个 Bitmap 对象,绘制的时候用。
利用 Glide 的来优化此步骤,就需要用到 BitmapPool。BitmapPool 本身是一个接口,我们通常会使用到它的实现类 LruBitmapPool,从名称就可以看出,它基于 LRU 的规则,在一定的内存限制下,缓存和管理一些可供重用的 Bitmap 对象。
1 |
|
IcaChe 中存的是 Btmap 对象, reusepool 中存的是 bitmap 的对象可以复用, 不必再创建。
超长图
对于不失真的超长图,超大图,subsampling-scale-image-view,他的原理其实是使用系统的API BitmapRegionDecoder ,它可以用来显示图片指定的一个矩形区域。通过自定义View重写onTouchEvent()判断手势滑动位置,从而改变矩形区域的位置,重新计算mRect(ruai kte)去获取部分bitmap绘制到屏幕上。invidedata刷新ondraw。
重复图片监控
重复图片指的是 Bitmap 的像素数据完全一致,但是有多个不同的对象存在。这个监控不需要太多的样本量,一般只在内部使用。之前我实现过一个内存 Hprof 的分析工具,它可以自动将重复 Bitmap 的图片和引用链输出。下图是一个简单的例子,你可以看到两张图片的内容完全一样,通过解决这张重复图片可以节省 1MB 内存
三级缓存
- 先读取内存缓存, 因为优先加载, 速度最快,内存缓存没有再读取本地缓存, 次优先加载, 速度也快,本地没有再加载网络缓存, 速度慢,浪费流量在网络缓存中从网络下载图片,并且保存在本地和内存中,在下载的时候可以对图片进行压缩
- 服务器端下载的图片是使用 Http的缓存机制,每次执行将本地图片的时间发送给服务器,如果俩次访问的时间间隔短,返回码是 304,会读取网络缓存(说明服务端的图片和本地的图片是相同的,直接使用本地保存的图片),如果返回码是 200,则开始下载新的图片并实现缓存。在从服务器获取到图片后,需要再在本地和内存中分别存一份,这样下次直接就可以从内存中直接获取了,这样就加快了显示的速度,提高了用户的体验。
GIF
还有GIF,FrameSequence是Android framework中里的一个工具包。 它封装了: libgif (gif编解码库c++ ),并提供Java API播放gif。使用它要比glide加载GIF效果效果要好,glide加载加载GIF图片CPU占用高,并且内存占用一直在增加。使用FrameSequence库就可以解决,不过每次判断去怎么加载很麻烦,可以使用glide的AppGlideModule apt注解解析器来自动生成代码。判断GIF图片时将InputStream转成FrameSequenceDrawable解析。这个还可以做播放Webp动画。
其他
自定义GlideModule。设置MemoryCache和BitmapPool大小,在未复用的情况下,每张图片都需要一块内存。而使用复用的时候,如果存在能被复用的图片会重复使用该图片的内存。所以复用不能减少程序正在使用的内存大小,而是解决了频繁申请内存导致的内存抖动、碎片等问题。
优化bitmap
inBitmap
- inBitmap是在BitmapFactory中的内部类Options的一个变量,简单而言,使用该变量可以复用旧的Bitmap的内存而不用重新分配以及销毁旧Bitmap,进而改善运行效率。
- 4.4之前的版本inBitmap只能够重用相同大小的Bitmap内存区域。简单而言,被重用的Bitmap需要与新的Bitmap规格完全一致,否则不能重用。
- 4.4之后的版本系统不再限制旧Bitmap与新Bitmap的大小,只要保证旧Bitmap的大小是大于等于新Bitmap大小即可。
- 除上述规则之外,旧Bitmap必须是mutable的,这点也很好理解,如果一个Bitmap不支持修改,那么其内存自然也重用不了
- Glide内部也使用了inBitmap作为缓存复用的一种方式。
1 |
|
三级缓存
lruCache,磁盘缓存、bitmap复用
Glide 也支持异步加载 Bitmap,异步加载,就涉及到线程的切换问题
我们也通过 Glide 加载一个图片资源,然后获得缓存的图片文件。其实只需要将 asBitmap() 换成 asFile() 即可。
1 |
|
尺寸压缩
options.insamplesize = calculateInsampleSize(options,reqWidth: 100,reqHeight: 100); //算出采样率(临近采样压缩算法)
一般会这两种算法一起用压缩图片
1 |
|
质量压缩
bitmap.compress时可以指定它的的格式和图片质量
webp<jpeg<png(无损压缩)
1 |
|
加载大图片
正常一张720x1080的图片在内存中占多少空间?怎么加载大图片?如何面对大的 bitmap如何处理?
- 看他的渲染方式,占用的字节不同的。RGB888每个像素占用4个字节,很容易内存溢出
- 图片的总大小 = 图片的总像素 * 每个像素占用的大小
- 计算机把图片所有像素信息全部解析出来,保存至内存,很容易内存溢出
加载大图片
可以对图片的宽高和质量进行压缩,步骤:
获取屏幕宽高,获取图片宽高,图片的宽高除以屏幕宽高,算出宽和高的缩放比例,取较大值作为图片的缩放比例,且大于1才缩放,然后按缩放比例加载图片
获取屏幕宽高 、获取图片宽高
//请求图片属性但不申请内存
opts.inJustDecodeBounds = true;
在不失真的条件下显示一张超高清的图片或者长图?
针对这个问题,我自己一般用以下两种方法解决:
- 使用WebView来加载该图片;
- 网络大图:Http协议中的Range / Accept-Range ,他可以指定获取body的一部分,从而达到分块加载图片的效果;
- subsampling-scale-image-view(底层就是BitmapRegionDecoder)
- BitmapRegionDecoder 可以用来显示图片指定的一个矩形区域。通过自定义View重写onTouchEvent()判断手势滑动位置,从而改变矩形区域的位置,重新计算mRect(ruai k te)去获取部分bitmap绘制到屏幕上。invidedata刷新ondraw
1 |
|
https://blog.csdn.net/lmj623565791/article/details/49300989/
GestureDetector 方便点
Glide
glide 问题
使用Glide时,注意对传入的Acticity与Fragment进行判断,避免传入已经销毁Acticity,造成IllegalArgumentException异常。可以参考这篇Glide类似You cannot start a load for a destroyed activity异常简单分析
优化GIF
FrameSequence是Android framework中里的一个工具包。 它封装了: giflib (gif编解码库c++ ),并提供Java API播放gif。使用它要比glide加载GIF效果效果要好,glide加载加载GIF图片CPU占用高,并且内存占用一直在增加。使用FrameSequence库就可以解决,不过每次判断去怎么加载很麻烦,可以使用glide的AppGlideModule apt注解解析器来自动生成代码。判断GIF图片时将InputStream转成FrameSequenceDrawable解析。这个还可以做播放Webp动画。
1 |
|
Glide加载Gif图片的原理:将gif根据每一帧解析成很张图片,然后在依次设置给ImageView。
FrameSequenceDrawable原理
利用了两个Bitmap对象,其中一个用于绘制到屏幕上,另外一个用于解析下一张要展示的图片,利用了HandlerThread在子线程解析,每次解析的时候获取上一张图片的展示时间,然后使用Drawable自身的scheduleSelf方法在指定时间替换图片,在达到替换时间时,会调用draw方法,在draw之前先去子线程解析下一张要展示的图片,然后重复这个步骤,直到播放结束或者一直播放。
需要自己编译
bitmap 复用
加载进度需要自己实现
Glide默认缓存
内存缓存最大空间(maxSize)=每个进程可用的最大内存 * 0.4
磁盘缓存大小: 250 * 1024 * 1024(250MB)
1 |
|
磁盘缓存目录磁盘缓存目录: 项目/cache/image_manager_disk_cache
1 |
|
优化方案
几乎所有的 OOM 错误都是因为宿主应用出了问题,而不是 Glide 本身。 应用里两种常见的 OOM 错误分别是:
过大的内存分配 (Excessively large allocations)
内存泄露(Memory leaks, 被分配的内存没有被释放)
- 引入largeHeap属性,让系统为App分配更多的独立内存。
- 禁止Glide内存缓存。设置skipMemoryCache(true)。内存缓存是CPU,速度更快。
- 自定义GlideModule。设置MemoryCache和BitmapPool大小。
- 升级到Glide4.0,使用asDrawable代替asBitmap,drawable更省内存。(或者下载下来,用空间换时间)
Bitmap 储存的是 像素信息(把所有信息都保存下来了,一个像素4个字节,想想有多大),Drawable 储存的是 对 Canvas 的一系列操作。而 BitmapDrawable 储存的是「把 Bitmap 渲染到 Canvas 上」这个操作。 - ImageView的scaleType为fitXY时,改为fitCenter/centerCrop/fitStart/fitEnd显示。
- 不使用application作为context。当context为application时,会把imageView是生命周期延长到整个运行过程中,imageView不能被回收,从而造成OOM异常。
- 当列表在滑动的时候,调用Glide的pauseRequests()取消请求,滑动停止时,调用resumeRequests()恢复请求。
1 |
|
- Try catch某些大内存分配的操作。考虑在catch里面尝试一次降级的内存分配操作。例如decode bitmap的时候,catch到OOM,可以尝试把采样比例再增加一倍之后,再次尝试decode。
- BitmapFactory.Options和BitmapFactory.decodeStream获取原始图片的宽、高,绕过Java层加载Bitmap,再调用Glide的override(width,height)控制显示。(??)
- onTrimMemory,调用 Glide.cleanMemroy() 清理掉所有的内存缓存。(内部是LruBitmapPool )
- 如果是处于 lowMemory 的时候,将图片的 DecodeFormat 设置为 RGB_565
- 使用 glide 自己的圆角图片。bitmapTransfrom