(4.4.1.3)Android内存篇:android的内存优化

目录

一、为什么要进行内存优化

二、如何进行内存优化

三、开源式方案

3.1 使用多进程

四、节流式方案

4.1 数据结构优化

4.1.1 使用更加轻量的数据结构

4.1.2 避免在Android里面使用Enum

4.1.3 谨慎使用“抽象”编程

4.2  对象复用

4.2.1 StringBuilder

4.2.2 复用系统自带的资源

4.2.3  ListView/GridView的ConvertView复用

4.2.4 避免在onDraw方法里面执行对象的创建

4.3 Bitmap

4.3.1  加载Bitmap:缩放比例、解码格式、局部加载

4.3.2 使用更小的图片

4.3.3 Bitmap对象的复用inBitmap高级属性

4.3.4 注意临时Bitmap对象的及时回收

4.3.5 Bitmap 分配及回收追踪监控

4.4  资源文件

4.4.1 资源文件需要选择合适的文件夹进行存放

4.4.2 优化布局层次,减少内存消耗

4.5 避免内存泄漏 

4.5.1 兜底回收内存

4.6 兜底保护

五、 其他方案

5.1 避免内存抖动

5.2  onTrimMemory(int):

六、工具

# 参考文献


一、为什么要进行内存优化

1. 系统分配的内存是有限的(沙盒机制),运行内存限制,OOM导致APP崩溃
2. 高内存占用的应用会被kill
3. APP性能:流畅性、响应速度和用户体验。 


二、如何进行内存优化

  1. 开源
    1. 使用多进程
      1.  对于webview,图库等,由于存在内存系统泄露或者占用内存过多的问题,我们可以采用单独的进程。微信当前也会把它们放在单独的tools进程中
  2. 节流
    1. 数据结构优化
      1. StringBuilder
      2. 使用更加轻量的数据结构
      3. 避免在Android里面使用Enum
      4. 谨慎使用“抽象”编程
      5. 谨慎使用依赖注入框架
    2. 对象复用
      1. 复用系统中自带的资源
      2. ListView/GridView的ConvertView复用
      3. 避免在onDraw方法里面执行对象的创建(因为onDraw在界面,图像或者View一有变化的化就会重新调用,如果在里面执行对象的创建的话,就会影响绘制的时间)
    3. 避免内存泄漏     
      1. Activity 泄露检测
      2. Native 内存泄漏检测
      3. 考虑使用Application Context而不是Activity Context
      4. 谨慎使用static对象
      5. 注意监听器的注销
      6. 注意缓存容器中的对象泄漏
      7.  注意WebView的泄漏
      8. 注意Cursor对象是否及时关闭
      9. 兜底回收内存
    4. Bitmap
      1. 加载Bitmap:缩放比例、解码格式、局部加载
      2. 使用更小的图片:无alfa通道的图片用jpk降低apk大小
      3. 注意临时Bitmap对象的及时回收
      4. 避免Bitmap的浪费
      5. Bitmap 分配及回收追踪
    5. 资源文件
      1. 资源文件需要选择合适的文件夹进行存放
      2. 优化布局层次,减少内存消耗
    6. 使用ProGuard来剔除不需要的代码    
    7. Try catch某些大内存的分配的操作
    8. 兜底保护
  3. - 其他
    1. 避免抖动等
    2. onTrimMemory中需要根据传递的参数类型进行判断,合理的选择释放自身的一些内 存占用,一方面可以提高系统的整体运行流畅度,另外也可以避免自己被系统判断为优先需要杀掉的应用

    

三、开源式方案

3.1 使用多进程

对于webview,图库等,由于存在内存系统泄露或者占用内存过多的问题,我们可以采用单独的进程。微信当前也会把它们放在单独的tools进程中


四、节流式方案


4.1 数据结构优化

4.1.1 使用更加轻量的数据结构

例如,我们可以考虑使用ArrayMap/SparseArray而不是HashMap等传统数据结构。

HashMap相比起Android专门为移动操作系统编写的ArrayMap容器,在大多数情况下,都显示效率低下,更占内存。

通常的HashMap 的实现方式更加消耗内存,因为它需要一个额外的实例对象来记录Mapping操作。另外,SparseArray更加高效,在于他们避免了对key与 value的自动装箱(autoboxing),并且避免了装箱后的解箱。

4.1.2 避免在Android里面使用Enum

Android官方培训课程提到过“Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android.”,具体原理请参考[《Android性能优化典范(三)》](https://www.csdn.net/article/2015-08-12/2825447-android-performance-patterns-season-3),所以请避免在Android里面使用到枚举。

4.1.3 谨慎使用“抽象”编程

很多时候,开发者会使用抽象类作为”好的编程实践”,因为抽象能够提升代码的灵活性与可维护性。然而,抽象会导致一个显著的额外内存开销:他们需要同等量的代码用于可执行,那些代码会被mapping到内存中,因此如果你的抽象没有显著的提升效率,应该尽量避免他们。


4.2  对象复用

大多数对象的复用,最终实施的方案都是利用对象池技术,要么是在编写代码时显式地在程序里创建对象池,然后处理好复用的实现逻辑。要么就是利用系统框架既有的某些复用特性,减少对象的重复创建,从而降低内存的分配与回收

4.2.1 StringBuilder

在有些时候,代码中会需要使用到大量的字符串拼接的操作,这种时候有必要考虑使用StringBuilder来替代频繁的“+”

4.2.2 复用系统自带的资源

Android系统本身内置了很多的资源,比如字符串、颜色、图片、动画、样式以及简单布局等,这些资源都可以在 应用程序中直接引用。

这样做不仅能减少应用程序的自身负重,减小APK的大小,还可以在一定程度上减少内存的开销,复用性更好。

但是也有必要留意 Android系统的版本差异性,对那些不同系统版本上表现存在很大差异、不符合需求的情况,还是需要应用程序自身内置进去

4.2.3  ListView/GridView的ConvertView复用

注意在ListView/GridView等出现大量重复子组件的视图里对ConvertView的复用

4.2.4 避免在onDraw方法里面执行对象的创建

(因为onDraw在界面,图像或者View一有变化的化就会重新调用,如果在里面执行对象的创建的话,就会影响绘制的时间)

4.3 Bitmap

4.3.1  加载Bitmap:缩放比例、解码格式、局部加载

Bitmap是一个极容易消耗内存的大胖子,减小创建出来的Bitmap的内存占用可谓是重中之重,通常来说有以下2个措施:

1. inSampleSize:缩放比例,在把图片载入内存之前,我们需要先计算出一个合适的缩放比例,避免不必要的大图载入。
2. decode format:解码格式,选择ARGB_8888/RBG_565/ARGB_4444/ALPHA_8,存在很大差异。

4.3.2 使用更小的图片

在涉及给到资源图片时,我们需要特别留意这张图片是否存在可以压缩的空间,是否可以使用更小的图片。

尽量使用更小的图片不 仅可以减少内存的使用,还能避免出现大量的InflationException。假设有一张很大的图片被XML文件直接引用,很有可能在初始化视图时会 因为内存不足而发生InflationException,这个问题的根本原因其实是发生了OOM

4.3.3 Bitmap对象的复用inBitmap高级属性

利用inBitmap的高级特性提高Android系统在Bitmap分配与释放执行效率(注:3.0以及4.4以后存在一些使用限制上的差异)。

使 用inBitmap属性可以告知Bitmap解码器去尝试使用已经存在的内存区域,新解码的Bitmap会尝试去使用之前那张Bitmap在Heap中所 占据的pixel data内存区域,而不是去问内存重新申请一块区域来存放Bitmap。

利用这种特性,即使是上千张的图片,也只会仅仅只需要占用屏幕所能够显示的图片数 量的内存大小

4.3.4 注意临时Bitmap对象的及时回收

虽然在大多数情况下,我们会对Bitmap增加缓存机制,但是在某些时候,部分Bitmap是需要及时回 收的。例如临时创建的某个相对比较大的bitmap对象,在经过变换得到新的bitmap对象之后,应该尽快回收原始的bitmap,这样能够更快释放原 始bitmap所占用的空间。

4.3.5 Bitmap 分配及回收追踪监控

Bitmap 一直以来都是 Android App 的内存消耗大户,很多 Java 甚至 native 内存问题的背后都是不当持有了大量大小很大的 Bitmap。


与此同时,Bitmap 有几个特点方便我们对它们进行监控:

- 创建场景较为单一。 Bitmap 通常通过在 Java 层调用 Bitmap.create 直接创建,或者通过 BitmapFactory 从文件或网络流解码。正好,我们有一层对 Bitmap 创建接口调用的封装,基本囊括微信内创建 Bitmap 的全部场景(包括调用外部库产生 Bitmap 也封装在这层接口内)。这层统一接口有利于我们在创建 Bitmap 时进行统一监控,而不需要进行插桩或 hook 等较为 hack 的方法。
- 创建频率较低。 Bitmap 创建的行为不如 malloc 等通用内存分配频繁,本身往往也伴随着耗时较长的解码或处理,因此在创建 Bitmap 时加入监控逻辑,其性能要求不会特别高。即使是获取完整的 Java 堆栈甚至做一些筛选,其耗时相比起解码或者其他图像处理也是微不足道,我们可以执行稍微复杂的逻辑。
- Java 对象的生命周期。 Bitmap 对象的生命周期和普通 Java 对象一样服从 JVM 的 GC,因此我们可以通过 WeakReference 等手段来跟踪 Bitmap 的销毁,而不用像创建一样对销毁也一并跟踪。

针对上述特点,我们加入了一个针对 Bitmap 的高性价比监控:

**在接口层中将所有被创建出来的 Bitmap 加入一个 WeakHashMap,同时记录创建 Bitmap 的时间、堆栈等信息,然后在适当的时候查看这个 WeakHashMap 看看哪些 Bitmap 仍然存活来判断是否出现 Bitmap 滥用或泄漏。**


4.4  资源文件

4.4.1 资源文件需要选择合适的文件夹进行存放

我们知道hdpi/xhdpi/xxhdpi等等不同dpi的文件夹下的图片在不同的设备上会经 过scale的处理。例如我们只在hdpi的目录下放置了一张100100的图片,那么根据换算关系,xxhdpi的手机去引用那张图片就会被拉伸到 200200。需要注意到在这种情况下,内存占用是会显著提高的。对于不希望被拉伸的图片,需要放到assets或者nodpi的目录下

4.4.2 优化布局层次,减少内存消耗

越扁平化的视图布局,占用的内存就越少,效率越高。我们需要尽量保证布局足够扁平化,当使用系统提供的View无法实现足够扁平的时候考虑使用自定义View来达到目的。

1. 如果布局既可以通过RelativeLayout又可以通过LinearLayout实现的话,选择通过LinearLayout实现,因为RelativeLayout的功能相对于LinearLayout来说更加复杂,布局过程需要更多的CPU开销;如果某些布局能够通过RelativeLayout来实现,也可以通过嵌套LinearLayout来实现,这时候应该选择RelativeLayout来实现,因为嵌套相当于增加了布局的层级,带来的性能损失比布局RelativeLayout更大;
2. 使用<include>和<merge>标签来进行布局重用,减少布局的层级
3. 使用<ViewStub>标签,这个标签能够做到动态加载我们的布局,不同于我们通过GONE的方式来设置某个View是否可见,ViewStub是View的一种,但是它没有大小,没有绘制功能,也不参与布局,资源消耗非常低,这点是不同于我们通过GONE方式设置View不可见的,因为通过GONE方式设置View不可见了说到底还是会绘制View的,只不过你看不到而已

4.5 避免内存泄漏 


4.5.1 兜底回收内存

Activity泄漏会导致该Activity引用到的Bitmap、DrawingCache等无法释放,对内存造成大的压力,兜底回收是指对于已泄漏Activity,尝试回收其持有的资源,泄漏的仅仅是一个Activity空壳,从而降低对内存的压力。

做法也非常简单,在Activity onDestory时候从view的rootview开始,递归释放所有子view涉及的图片,背景,DrawingCache,监听器等等资源,让Activity成为一个不占资源的空壳,泄露了也不会导致图片资源被持有。

```
Drawable d = iv.getDrawable();
   if (d != null) {
       d.setCallback(null);
   }        
   iv.setImageDrawable(null);
```   

4.6 兜底保护

在用户无感知的情况下,在接近触发系统异常前,选择合适的场景杀死进程并将其重启,使得应用的内存占用回到正常情况,这不为是一种好的兜底方式。

- 微信是否在主界面退到后台 且 位于后台的时间超过 30 分钟
- 当前时间为凌晨 2~5 点
- 不存在前台服务(存在通知栏,音乐播放栏等情况)
- java heap 必须大于当前进程最大可分配的 85% || native 内存大于 800M || vmsize 超过了 4G(微信 32bit)的 85%
- 非大量的流量消耗(每分钟不超过 1M) && 进程无大量 CPU 调度情况 

在满足以上几种条件下,杀死当前微信主进程并通过 push 进程重新拉起及初始化,来进行兜底保护。在用户角度,当用户将微信切回前台时,不会看到初始化界面,还是位于主界面中,所以也不会感到突兀


五、 其他方案

5.1 避免内存抖动

Memory Churn内存抖动,内存抖动是因为在短时间内大量的对象被创建又马上被释放。瞬间产生大量的对象会严重占用内存区域,当达到阀值,剩余空间不够的时候,会触发GC从而导致刚产生的对象又很快被回收。

即使每次分配的对象占用了很少的内存,但是他们叠加在一起会增加Heap的压力,从而触发更多其他类型的GC。这个操作有可能会影响到帧率,并使得用户感知到性能问题。

最常见产生内存抖动的例子就是在 ListView 的 getView 方法中未复用 convertView 导致 View 的频繁创建和释放,针对这个问题的处理方式那当然就是复用 convertView;

或者是 String 拼接创建大量小的对象(比如在一些频繁调用的地方打字符串拼接的 log 的时候);

如果是其他的问题,就需要通过 Memory Monitor 去观察内存的实时分配释放情况,找到内存抖动的地方修复它,或者如果当出现下面这种情况下的 OOM 时,也是由于内存碎片导致无法分配内存: 


5.2  onTrimMemory(int):

- Android 系统从4.0开始还提供了onTrimMemory()的回调,当系统内存达到某些条件的时候,所有正在运行的应用都会收到这个回调
- 同时在这个回调里面 会传递以下的参数,代表不同的内存使用情况,收到onTrimMemory()回调的时候,需要根据传递的参数类型进行判断,合理的选择释放自身的一些内 存占用,一方面可以提高系统的整体运行流畅度,另外也可以避免自己被系统判断为优先需要杀掉的应用


六、工具

- [Android内存申请分析](https://mp.weixin.qq.com/s/b_lFfL1mDrNVKj_VAcA2ZA)
- [Android 性能优化 - 彻底解决内存泄漏](https://blog.csdn.net/wanghao200906/article/details/79305126)
- [Android性能优化篇之内存优化--内存优化分析工具](https://www.jianshu.com/p/b6684e5880d9)
- [Android Monitor工具详解大全](https://mp.weixin.qq.com/s/kcbEto2ljhhCSNknIWtbzA)
- [Android-App性能优化](https://mp.weixin.qq.com/s/M9_ggbxxGuvQj8inDSNTJw)
    


# 参考文献

- [内存优化,避免OOM总结](http://www.csdn.net/article/2015-09-18/2825737/3)
- [微信 Android 终端内存优化实践](https://mp.weixin.qq.com/s/KtGfi5th-4YHOZsEmTOsjg)
- [Android内存优化杂谈](https://mp.weixin.qq.com/s/Z7oMv0IgKWNkhLon_hFakg)
- [Android-APP内存优化](https://blog.csdn.net/xjh_shin/article/details/79842728)
- [Android内存优化建议](https://blog.csdn.net/hzw19920329/article/details/52318422)
- [Android 性能优化之内存泄漏检测以及内存优化(上)](https://blog.csdn.net/self_study/article/details/61919483)
- [Android 性能优化之内存泄漏检测以及内存优化(中)](https://blog.csdn.net/self_study/article/details/66969064)
- [Android 性能优化之内存泄漏检测以及内存优化(下)](https://blog.csdn.net/self_study/article/details/68946441)
- [《Android性能优化典范(三)》](https://www.csdn.net/article/2015-08-12/2825447-android-performance-patterns-season-3)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值