🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[TOC] 内存优化应该优先去做见效快的地方,图片内存优化是内存优化的重点,可能一张图片没有回收就会造成几M内存的浪费 # 常规的图片内存优化方法 我们都知道,图片所占内存=宽*高*一像素占用内存 所以优化图片内存主要有以下几个思路 1.缩放减小宽高 2.减少每个像素所占用的内存 3.内存复用,避免重复分配内存 4.对于大图,可以采取局部加载的策略 ## 减少图片宽高 有时图片宽高为`200*200`,而`View`宽高为`100*100`,这种时候如果展示`200*200`的图片没有意义,应该对图片进行缩放 这种情况一般通过`inSampleSize`实现 ~~~java BitampFactory.Options options = new BitmapFactory.Options(); // 设置为4就是宽和高都变为原来1/4大小的图片 options.inSampleSize = 4; BitmapFactory.decodeSream(is, null, options); ~~~ ## 减少每个像素所占用的内存 在`API29`中,将`Bitmap`分为`ALPHA_8`, `RGB_565`, `ARGB_4444`, `ARGB_8888`, `RGBA_F16`, `HARDWARE`六个等级。 * `ALPHA_8`:不存储颜色信息,每个像素占1个字节; * `RGB_565`:仅存储`RGB`通道,每个像素占2个字节,对`Bitmap`色彩没有高要求,可以使用该模式; * `ARGB_4444`:已弃用,用`ARGB_8888`代替; * `ARGB_8888`:每个像素占用4个字节,保持高质量的色彩保真度,默认使用该模式; * `RGBA_F16`:每个像素占用8个字节,适合宽色域和`HDR`; * `HARDWARE`:一种特殊的配置,减少了内存占用同时也加快了`Bitmap`的绘制。 每个等级每个像素所占用的字节也都不一样,所存储的色彩信息也不同。同一张100像素的图片,`ARGB_8888`就占了400字节,`RGB_565`才占200字节 所以在某些场景中,修改图片格式可以达到减少一半内存的效果 ## 内存复用,避免重复分配内存 `Bitmap`所占内存比较大,如果我们频繁创建与回收`Bitmap`,那么很容易造成内存抖动,所以我们应该尽量复用`Bitmap`内存 在 `Android 3.0(API 级别 11)`开始,系统引入了 `BitmapFactory.Options.inBitmap` 字段。如果设置了此选项,那么采用 `Options` 对象的解码方法会在生成目标 `Bitmap` 时尝试复用 `inBitmap`,这意味着 `inBitmap` 的内存得到了重复使用,从而提高了性能,同时移除了内存分配和取消分配。不过 `inBitmap` 的使用方式存在某些限制,在 `Android 4.4(API 级别 19)`之前系统仅支持复用大小相同的位图,4.4 之后只要 `inBitmap` 的大小比目标 `Bitmap` 大即可 ## 大图局部加载策略 对于图片加载还有种情况,就是单个图片非常巨大,并且还不允许压缩。比如显示:世界地图、清明上河图、微博长图等 首先不压缩,按照原图尺寸加载,那么屏幕肯定是不够大的,并且考虑到内存的情况,不可能一次性整图加载到内存中 所以这种情况的优化思路一般是局部加载,通过`BitmapRegionDecoder`来实现 ~~~java //设置显示图片的中心区域 BitmapRegionDecoder bitmapRegionDecoder = BitmapRegionDecoder.newInstance(inputStream, false); BitmapFactory.Options options = new BitmapFactory.Options(); options.inPreferredConfig = Bitmap.Config.RGB_565; Bitmap bitmap = bitmapRegionDecoder.decodeRegion(new Rect(width / 2 - 100, height / 2 - 100, width / 2 + 100, height / 2 + 100), options); mImageView.setImageBitmap(bitmap); 复制代码 ~~~ ## 小结 上面所说的这些关于`Bitmap`的内存优化策略其实都比较简单,而且我们在开发中可能很少用到 因为我们常用的图片框架比如`Glide`已经将这些都封装在里面了,所以一般情况下我们加载图片时不需要做这些特殊操作 关于`Glide`对于加载图片都做了哪些优化,有兴趣的同学可以参考:[【带着问题学】Glide做了哪些优化?](https://juejin.cn/post/6970683481127043085 "https://juejin.cn/post/6970683481127043085") # 图片兜底策略 针对因`activity`、`fragment`泄漏导致的图片泄漏,我们可以在`onDetachedFromWindow`时机进行了监控和兜底,具体流程如下: ![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3a851196e2f5439fa05070ac0c37eb90~tplv-k3u1fbpfcp-watermark.awebp) 通过这种方式可以方便地解决因`Activity`导致的图片泄漏问题 #线上大图监控方案 当运营在线上配置了不合理大小的图片时,如果我们及时发现,也会带来内存问题 如果图片本身大小就不合理,我们在这个基础上谈图片优化也没有什么意义,因此大图监控这也是个比较常见的需求 下面介绍几种大图监控的方案: ## `ArtHook` 方案 该方案采用`weishu`大佬写的`epic`库实现,通过对`ART`虚拟机的`hook`,`hook ImageView`的 `setImageBitmap` 等方法 解析对比方法参数中的 `bitmap` 宽高和 `ImageView` 实例的宽高,也可以获得`bitmap`的实际大小 如果图片宽高比`ImageView`宽高大,或者图片大小超出了阈值,就可以把相关信息上报 这种方案的优点在于: 1.侵入性极低,一次初始化配置即可`hook`全局的目标`View`控件 2.可以获取代码调用堆栈,方便开发者快速定位 而缺点则在于: 兼容性存在问题,使用了`hook`系统`API` ,不能用于线上 ## `BaseActivity` 方案 大部分应用工程在业务发展的过程中都会沉淀封装自己的`BaseActivity` ,通过在`BaseActivity onDestroy`中动态地检测各个`View`控件,从而获知图片加载情况 ~~~kotlin class BaseActivity : Activity(){ fun onDestory(){ if(isOpenCheckBitmap){ checkBitmapFromView() } } fun checkBitmapFromView(){ //1、遍历activity中的各个View控件 //2、获取View控件加载的Bitmap //3、对比Bitmap宽高与View宽高 } } 复制代码 ~~~ 这种方案的优点在于: 1.兼容性强,无任何反射 2.加入简单,没有什么复杂逻辑 缺点在于: 1.侵入性太强,需要修改`BaseActivity` 2.`BaseActivity.onDestory`本身可能被重写,并不安全 ## `ASM`方案 该方案在编译流程进行插桩,通过匹配`setImageBitmap` 、 `setBackground` 等关键方法,插入`Bitmap`大小检测逻辑 这种方案优点在于: 1.编译时期插桩,对开发过程无侵入性 缺点在于: 1.通过插桩的方式打点,可能会增加编译期耗时 2.`ASM`代码维护成本较高,使用起来不是那么方便 ## `registerActivityLifecycleCallback`方案 通过`registerActivityLifecycleCallback`监听`Activity`生命周期,在`onStop`时进行`Bitmap`大小检测的逻辑 ~~~kotlin private fun registerActivityLifecycleCallback(application: Application) { application.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks { override fun onActivityStopped(activity: Activity) { checkBitmapIsTooBig(childViews) } }) } ~~~ 这种方案对原始代码无侵入性,同时使用起来比较简单,也没有兼容性问题,应该属于比较良好的方案 详细实现可见:[BitmapCanary 诞生](https://juejin.cn/post/6956138531789996040#heading-14 "https://juejin.cn/post/6956138531789996040#heading-14") # 参考资料 [【从入门到实用】android内存优化深入解析](https://juejin.cn/post/6975876569990447134)