1. 什么是内存泄漏?
android开发主要使用java(kotlin)语言,是自动内存管理的语言,当我们使用一个对象的时候,会自动给对象分配内存,当我们不再使用这个对象之后,自动回收分配给对象的内存。但是某些情况下,在对象使用完后并没有将其的内存进行回收,结果导致这一块内存没被使用但也不能再被使用。
Android的app运行机制是每一个应用的在运行的专门的虚拟机中,会给它们分配固定的内存上限,当发生内存泄漏时,就是虚拟机中的内存无法再使用,当泄漏的内存内存越来越多,不再足够app使用,就会导致OOM(ou t of memory,内存溢出),导致应用崩溃,进程被杀掉。因为这个机制,在应用被杀死之后,会将所有内存释放出来,那些无法使用的内存又可以使用了。
2. 为什么会发生内存泄漏?
内存回收管理策略是进行可达性判断。当内存与GC Roots之间存在可达路径时,表示当前资源正在被引用,虚拟机无法对其进行回收,如图中的黄色节点。反过来,如果圆点与GC Roots之间不存在可达路径,则虚拟机会在GC过程中将其回收掉,如图中蓝色节点。
这也就意味着当一个生命周期比较长的对象持有一个生命周期比较短的对象,当生命周期短的生命周期结束的时候,就会导致GC不会回收这个对象,当无法通过这个生命周期长的对象访问这个这个生命周期短的对象或者不再使用这个生命周期较短的对象,就会发生内存泄漏。
3. Android中的内存泄漏
3.1. 静态变量
静态变量是贯穿整个应用的生命周期的,当静态变量在activity生命周期结束以后并没有把对应的值清空时,就会造成Activity泄漏。
3.1.1. 静态变量或者集合持有Activity引用
当定义了静态的activity的变量或者存储activity的集合,把运行的Activity实例对象存储在在这两个静态变量中。当这两个静态变量在activity生命周期结束以后并没有把对应的值清空时,就会造成Activity泄漏。
companion object{
var activity:Activity?=null
val activitySet:Set<Activity> =HashSet<Activity>()
}
3.1.2. 单例中保存Activity引用
单类模式中,对象也是通过静态变量存储,如果传入了Activity的引用,也会像上一个情况一样,也会在activity生命周期结束以后无法回收内存,造成内存泄漏。
与上一种的区别就是没有直接通过变量存储Activity引用,而是存储在这个变量对象里面。
class Singleton private constructor(val context: Context) {
companion object {
private var instance: Singleton? = null
@Synchronized
fun get(context: Context): Singleton {
if (instance == null) {
instance = Singleton(context)
}
return instance!!
}
}
}
3.1.3. 静态变量存储View
View在创建的过程中,需要传入一个context对象,并且会存储这个context对象,一般这个context也是Activity对象,如果将View像上面两种情况进行存储,也会导致Activity的泄漏。
public View(Context context) {
mContext = context;
......
}
3.2. 非静态内部类
非静态内部类会持有外部类的引用,这就会留下隐患,当外部Activity生命周期结束时,如果无法将其内部类销毁,就会导致外Activity的泄漏。
3.2.1. 静态存储非静态内部类
静态存储非静态内部类的静态对象,这样当Activity生命周期结束时,其内部类因为静态存储并不会被销毁,而非静态内部类又持有这个Activity的引用,导致Activity的泄漏。
class AActivity : AppCompatActivity() {
companion object {
var a: A? = null
}
inner class A
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onCreate(savedInstanceState, persistentState)
a = A()
}
}
3.2.2. 内部类进行耗时操作
内部类进行耗时操作时,释放Activity时也无法释放内部类,内部类又持有外部Activity对象,造成内存泄漏。
class AActivity : AppCompatActivity() {
inner class A:Runnable{
override fun run() {
while (true){
}
}
}
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onCreate(savedInstanceState, persistentState)
Thread(A()).start()
}
}
3.2.3. 匿名内部类Handler延迟执行
使用Handler发送一条延时消息,在延时的过程中如果Activity被关闭掉,message将存储在消息队列中等待执行,这个message会持有一个Handler的引用,Handler又会持有Activity的引用,这些引用会在message执行完成前一直存在,发生内存泄漏。
class AActivity : AppCompatActivity() {
val handler = object : Handler() {
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
val a = 2
}
}
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onCreate(savedInstanceState, persistentState)
handler.sendEmptyMessageDelayed(1,10*60*1000)
}
}
3.3. 注册未取消
开发过程中很多时候会有注册,在注册之后,context会持久的被观察者持有,如果没进行反注册,就会造成内存泄漏。
3.3.1. Sensor未注销
当我们需要使用系统服务,比如访问某些传感器的时候,这些服务会运行在自己的线程里面。如果我们需要在一个事件发生时随时收到通知,就需要传入监听器,这样服务就会持有Activity的引用,如果在Activity销毁时没有将注册取消,就会导致Activity泄漏。
class AActivity : AppCompatActivity(), SensorEventListener {
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onCreate(savedInstanceState, persistentState)
val sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
val sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL)
sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_FASTEST)
}
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
TODO("Not yet implemented")
}
override fun onSensorChanged(event: SensorEvent?) {
TODO("Not yet implemented")
}
}
3.3.2. 广播未取消注册
如果在Activity通过registerRceiver()方法注册一个广播,这个广播不只被Activity引用,还会被AMS等系统服务引用,而广播又持有Activity的引用(onReceive()中的context),如果在Activity生命周期结束时没有进行广播取消注册,就会导致Activity泄漏。
class AActivity : AppCompatActivity() {
val receiver=object :BroadcastReceiver(){
override fun onReceive(context: Context?, intent: Intent?) {
}
}
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onCreate(savedInstanceState, persistentState)
registerReceiver(receiver,IntentFilter())
}
}
3.4. 属性动画
ValueAnimator实现了一个接口:AnimationFrameCallback,在调用start()启动动画时,会将这个接口存入到AnimationHandler中,而这个AnimationHandler是一个单类模式。
并且animator一般会持有某个view的引用,以将动画显示出来,而view又回持有activity的引用,因此,如果这个动画时无线循环,activity销毁时没停止属性动画,会造成内存泄漏。
class AActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onCreate(savedInstanceState, persistentState)
val view = TextView(this)
ValueAnimator()
.apply {
duration = 1000
repeatMode = ValueAnimator.REVERSE
repeatCount = ValueAnimator.INFINITE
addUpdateListener {
view.setBackgroundColor(it.animatedValue as Int)
}
start()
}
}
}
3.5. 资源对象未关闭
创建一个流时,为了方便访问,会占用各种各样的系统资源,比如file descriptor,而这并不会被GC自动回收,必须调用close释放,虽然在finalize()中有调用,但这种方法的执行速度不确定,还是手动调用更好一些。
class AActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onCreate(savedInstanceState, persistentState)
val file = FileInputStream("a.txt")
}
}
3.6. webview
webview引发的内存泄漏是因为AwContents中传入了一个回调component callbacks,他的绑定与解除绑定分别在onAttachedToWindow和onDetachedFromWindow中,而在onDetachedFromWindow中,会判断是否已经调用了destroy()进行销毁,如果销毁过了就没法执行后面的解除绑定的过程,webview又持有activity对象,造成内存泄漏。
3.7. bitmap不调用recycle()会造成内存泄漏吗?
在android3.0以前的版本中,将bitmap加载到内存中时,实际上是由两部分组成。第一部分包含有关bitmap的一些信息,存储在Java使用的内存中;第二部分包含像素信息(字节数组),第二部分在C使用的内存中。所以需要调用Bitmap.recycle(),释放C内存,然后交由GC回收Java的内存。否则将会造成C++部分的内存泄漏。
但在3.0之后,位图数据已经不会存储在C++这部分的内存中,所以在释放内存时使用recycle()并不是必须的,而是可以置空交给GC机制管理。
在Bitmap.recycle()源码中,有一段这样的说明:
This is an advanced call, and normally need not be called, since the normal GC process will free up this memory when there are no more references to this bitmap.
所以现在来说,bitmap不调用recycle()并不会引起内存泄漏,等到没有对bitmap对象的引用之后,将会被GC释放出来。
4. 如何避免内存泄漏
- Activity销毁时,将持有它的对象全部清空。
- 将强引用转变成弱引用。
- 必须使用context可以尝试用ApplicationContext代替,它的生命周期是整个app,不会引起内存泄漏。
- 内部类使用静态内部类,如果需要外部的引用,可以传入弱引用。
- 逻辑上的完整,注册记得反注册,绑定记得解绑,程序中有延时或者计时等在退出时要检测关闭。
- 业务中的情况会复杂很多,让人防不胜防,不可避免的会有泄漏的情况,这时候就可以使用一些库比如LeakCanary,去检测和定位内存泄漏的地方。
最后
这篇文章是我在学习的过程中进行的一些简单的总结,自己并没有解决实际中泄漏的经验,这篇文档说来说去的也只是纸上谈兵,欢迎各位大佬对其中的错误和不足进行更正和补充。