Android 内存优化

286 阅读6分钟

在Android设备,内存是一项稀缺资源,如果处理不好,会导致系统性能下降,比如页面卡顿,内存泄漏以致于OOM(内存溢出),APP退至后台更容易被系统Kill

为什么内存处理不好,会导致上述的问题呢,我们是不是会产生一些疑问。

  • Android中每个App可以一直分配吗?
  • Android中内存,是怎样划分的?
  • Android中哪些对象是内存大户?
  • Android是怎么回收内存的?
  • Android使用工具来分析?
  • Android应该从哪些方面进行优化的?

Android App的内存分配

Android应用程序都运行在自己的进程中,并且被分配了一个独立的虚拟机,这些进程之间是相互隔离的,每个应用程序都有自己的内存空间。通常,Android应用程序的默认最大内存值在64MB到256MB之间,随着机器的硬件性能提高,Android的内存配额会提高

Android中内存划分

每个App运行在独立的虚拟机里面,那他们的内存划分是怎样的呢

738b4710b912c8fcd86d7ee514729b4cd78821f2.webp

  1. 方法区:保存了类的信息,静态变量,常量等
  2. :Java堆是所有线程共享的一块内存区域,在虚拟机启动时创建。对于绝大多数应用来说,Java堆是JVM所管理的内存中最大的一块
  3. 虚拟机栈:保存了局部变量表、操作栈、动态链接、方法出口、对象指针
  4. 本地方法栈:本地方法栈和Java虚拟机栈非常类似,本地方法栈用于Native本地方法的调用
  5. 程序计数器:当前线程所执行的字节码的行号指示器,记录当前线程执行到了第几行。线程私有

Android中的内存大户有哪些?

  1. Bitmap
  2. 数据库/IO流对象
  3. Webview对象
  4. 媒体播放器
  5. 布局对象
  6. 大对象的集合
  7. 字符串

Android是怎么回收内存的?

JVM的内存回收机制

  • 针对kotlin和java编写的代码,运行在Java虚拟机(JVM)上, 因此它们共享相同的内存回收机制, JVM提供了自动内存管理和垃圾回收机制, 开发人员不需要显式调用内存分配和释放

  • 如何确保JVM能正确回收Java对象

  • 及时释放对象引用
  • 避免循环引用
  • 尽量避免静态引用
  • 关闭资源

C/C++内存回收

  • 在C和C++中,内存的分配和释放是开发人员手动管理内存的生命周期,确保在不再使用内存时及时释放它,以避免内存泄漏

Android内存分析工具

  • Android profiler Android studio 自带工具
  • LeakCanary 用于检测Android内存泄漏的开源库
  • MAT

Andorid内存优化

1. 查看内存占用

  • adb shell命令: adb shell dumpsys meminfo 包名
  • 使用Android studio的profiler

2.内存泄漏

内存泄漏,就是对象被持有导致无法释放或不能按照对象正常的生命周期进行释放;在Android中,页面退出销毁了,但是还被其他长于它生命周期的对象所引用,导致垃圾回收机制无法回收,会一直常驻在内存中,如果用户长时间使用App,会导致App内存增多,当到达分配给每个App内存的阈值时,就会报OOM内存错误

  • Android中常见的内存泄漏

    1. 由于匿名类和内部类会隐式持有外部类的引用,如果不注意处理,可能导致外部类无法被正确回收。
    2. 如果在使用Handler时,未正确处理消息队列和对外部类弱引用,可能导致外部类无法被回收。
    3. 单例对象导致的内存对象
    4. 广播注册未解注
    5. 数据库cursor,IO资源未关闭
    6. 属性动画在页面结束未取消
  • 分析工具 Android Profiler,LeakCanary

  • 如何避免内存泄漏 以下是一些常见的内存泄漏避免方法:

    1. 及时释放对象:在不再需要对象时,及时将其引用置空,
    2. 使用弱引用:对于可能导致内存泄漏的对象引用
    3. 使用静态对象:静态对象生命周期长,容易导致内存泄漏,当不再使用时,应立即清除。
    4. 使用匿名类和内部类:匿名类和内部类隐式地持有外部类的引用,注意内部类的生命周期是否长于外部内
    5. 使用单例模式,应注意单例的资源释放
    6. 使用Handler:使用静态内部类和对外部类的弱引用来避免Handler导致的内存泄漏,或者页面退出时,删除所有消息

3. 内存抖动

短时间内频繁创建的对象和销毁对象,会导致内存抖动,可以通过Android profiler分析

  • 常见的内存抖动
  1. 自定义动画View的Ondraw频繁的创建对象

  2. listview,recycleview的item里面的频繁的创建对象

  3. 字符串硬拼接

4. 内存优化

图片优化:

如果你的App中有很多图片显示,图片优化是必不可少的, 首先计算一张图片加载到内存中,需要多大内存?

默认情况下: Size = imgWidth * imgHeight *图片对应的位图配置(默认是ARGB_8888 = 4Byte)

  • drawable下图片资源,放在不同的drawable目录下会有不同:

    当使用BitmapFactory.decodeResource方法时,默认没有指定options: 内存大小 Size = (imgWidth * deviceDpi / drawableDpi )* (imgHeight * deviceDpi / drawableDpi )* 位图配置, 所以慎用该方法,如果图片资源放置的目录不对,会导致加载到内存中的图片陡增

    当使用setImageDrawable,setBackgroundResource,setImageResource方法设置图片资源时 当图片放置的drawableDpi <= 目标deviceDpi时,内存Size=imgWidth * imgHeight 图片对应的位图配置,当图片放置的drawableDpi > 目标deviceDpi, 内存Size = (imgWidth * deviceDpi / drawableDpi )(imgHeightdeviceDpi / drawableDpi ) 位图配置,出现发虚,出现锯齿现象

image.png

  • 网络图片加载 Size = imgWidth * imgHeight * 图片对应的位图配置 用好BitmapFactory.Options

a. 如果显示图片没有透明通道,比如jpeg格式,编码时偏好选择RGB_565,内存比ARGB_8888省一半(设置合适的inPreferredConfig)

b. 采样图片,计算inSampleSize

c. 图片复用,设置inBitmap(需保证图片可变,inMutable=true)

d. 使用图片缓存池(对不用的图片放入缓存,待新的图片解码时,从缓存池中读取复用)

e. 对于超大图,采用区域解码

数据库/IO流对象

  • 全局应保证一个数据库实例,杜绝new多个数据库实例; 当数据库不再使用时,关闭数据库
  • IO流使用完成后,记得close,使用try catch finally是,在finally关闭资源

Webview内存优化

  • 确保Webview不再使用时,Destory

媒体播放器

  • 全局应保证一个媒体播放器,杜绝创建多个实例;不再使用时,记得关闭

单例的大集合数据

  • 当集合中的数据不再使用时,及时清理

字符串优化

  • 在多个字符串拼接时,避免使用String字符串硬拼接,使用stringBuffer,Stringbuilder