最近自己负责的项目在发布了新版本后,Fabric 收到了一些关于 ImageView onDraw 的问题,而且都是在 Android Oreo 8.0 (API 26)上出现的,分析异常日志可以清晰发现都是 bitmap 解码出错,关键日志异常:java.lang.IllegalStateException: Software rendering doesn’t support hardware bitmaps。同时新版本向日葵把图片加载库Glide从 3.7.1 版本更新到了 4.7.1,App 内部图片展示和操作代码都没有做更改。
分析
既然 App 代码没有做更改,那么问题就很有可能出在了 Glide 加载器和 Android 8.0系统。所以有必要了解这2者在新版本有哪些更新。
不同Android版本时Bitmap内存模型
Bitmap内存优化[^Bitmap内存优化]
[^Bitmap内存优化]: https://juejin.im/entry/5ad0213f6fb9a028df2306cd
我们知道Android系统中,一个进程的内存可以简单分为Java内存和native内存两部分,而Bitmap对象占用的内存,有Bitmap对象内存和像素数据内存两部分,在不同的Android系统版本中,其所存放的位置也有变化。Android Developers上列举了从API 8 到API 26之间的分配方式:
API级别 | API 10- | API 11 ~ API 25 | API 26+ |
---|---|---|---|
Bitmap对象存放 | Java heap | Java heap | Java heap |
像素(pixel data)数据存放 | native heap | Java heap | native heap |
最新的Android 8.0之后,谷歌又把像素存放的位置,从java heap该回到了native heap,API 11的那次改动,是源于native的内存释放不及时,会导致OOM,因此才将像素数据保存到java heap,从而保证Bitmap对象释放时,能够同时把像素数据内存也释放掉。至于为什么在8.0上改变了Bitmap像素数据的存放方式,我猜想和8.0中的GC算法调整有关系。GC算法的优化,使得Bitmap占用的大内存区域,在GC后也能够比较快速的回收、压缩,重新利用。
Bitmap.Config.HARDWARE
Android 8.0 Bitmap.Config配置枚举新增了HARDWARE^Bitmap.Config,来配置图片像素数据存储在GPU里,去掉了内存存储的相同一份数据,来节省内存开销,加速UI渲染。如果要使用hardware,在bitmap decode时,设置option.inPreferredConfig = Bitmap.Config.HARDWARE;
即可。如果要获取存储到显存上的bitmap,需要使用PixelCopy
类把bitmap数据拷贝到内存中。PixelCopy类有几个静态方法,传递一个surface或window,指定一个bitmap,就可以把surface或window上的bitmap拷贝到你指定的bitmap上了。
Glide 4.x
Glide4.+版本开始,适配了8.0系统的变化,解码默认是禁用hardware,如果外面设置了其它值,会打开hardware开关使用系统新特性。
解决
由于应用内对于图片显示没有严格的要求,所以使用RGB_565
每像素4个字节加载图片来节省内存。在Android 8.0系统官方对bitmap的存储又进行了调整,图片像素(pixel data)数据存放到了native heap,如果图片只用作展示,不进行缩放、matrix处理的时候,图片数据只会在GPU存储一份用来渲染UI,否则图片需要在运存上也存在一份数据,来进行后续其它操作。
Glide全局配置类代码(线上):
1 |
|
Glide关于DecodeFormat文档解释
1 |
|
可以看到Glide 4.7.1上默认使用了PREFER_ARGB_8888_DISALLOW_HARDWARE
,来禁用硬件位图,阻止Glide使用Android O+ 上android.graphics.Bitmap.Config#HARDWARE
。
而应用设置了PREFER_RGB_565
,从而打开了硬件位图存储的开关,只要应用内有读取变换bitmap
的操作都有奔溃的问题。基于这个问题,目前做了disable hardware配置,使用Glide默认配置来解决图片解码问题。后续计划分出来只加载和变化的图片,使用不用的配置策略,兼容系统新API。
PS:为单个请求设置禁止hardware,可进行如下设置。
1 | RequestOptions options = new RequestOptions().disallowHardwareConfig(); |
总结
- 新版本Glide 4.x适配了Android O+系统,做了bitmap加载优化
- Android O+ 系统修改了图片像素数据存储位置,从java heap改到native heap
- Android O+ 系统新增了
Bitmap.Config.HARDWARE
选项,hardware标志不是关于质量,而是关于像素存储位置(graphics memory),这样看来hardware更像个黑客 - 如果使用了hardware功能,像从java层面获取bitmap byte数据,可以使用
PixelCopy
类来赋值获取,友情提示:从surface拷贝数据到内存很缓慢哦 :) - 如果你还想知道更多关于hardware的解释,请移步这里Hardware Bitmaps
更多关于Glide
本人创建了一个github repo,包含了Glide3.x和4.x版本的入门和高级使用演示。如果你想知道更多关于Glide的使用,可以参考GlideTest项目。备注:master分支是3.7.1版本演示,4.7.1分支是4.7.1版本演示。