Android内存泄漏
根本原因
内存泄漏的根本原因是一个长生命周期对象持有一个短生命周期对象,造成短生命周期对象没有办法被回收所导致的。
常见的内存泄漏:
- 单例模式引发的内存泄漏:
原因:单例模式里的静态实例持有对象的引用,导致对象无法被回收,常见为持有Activity的引用
解决:改为持有Application的引用,或者不持有使用的时候传递。 - 集合操作不当引发的内存泄漏:
原因:集合只增不减
解决:有对应的删除或卸载操作 - 线程的操作不当引发的内存泄漏:
原因:线程持有对象的引用在后台执行,与对象的生命周期不一致
解决:静态实例+弱引用(WeakReference)方式,使其生命周期一致 - 匿名内部类/非静态内部类操作不当引发的内存泄漏:
原因:内部类持有对象引用,导致无法释放,比如各种回调
解决:保持生命周期一致,改为静态实例+对象的弱引用方式(WeakReference) - 常用的资源未关闭回收引发的内存泄漏:
原因:BroadcastReceiver,File,Cursor,IO流,Bitmap等资源使用未关闭
解决:使用后有对应的关闭和卸载机制 - Handler使用不当造成的内存泄漏:
原因:Handler持有Activity的引用,其发送的Message中持有Handler的引用,当队列处理Message的时间过长会导致Handler无法被回收
解决:静态实例+弱引用(WeakReference)方式 - 动画引起的内存泄漏:
原因:动画会持有view对象,进而持有activity
解决:页面destroy时释放动画
示例
- 单例持有activity context引起的泄漏
class LeakSingleton private constructor(context: Context) {
companion object {
@Volatile
private var instance: LeakSingleton? = null
fun getInstance(context: Context): LeakSingleton? {
instance ?: synchronized(this) {
instance ?: LeakSingleton(context).also {
instance = it
}
}
return instance
}
}
}
可以看到LeakSingleton有一个Context入参,如果不小心传入activity的context就会引起内存泄漏,如下面这样的代码:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val leakSingleton = LeakSingleton.getInstance(this)
}
}
在使用时很有可能使用者不清楚LeakSingleton是否会存在内存泄漏问题,也就很可能传入一个短生命周期的context作为入参,导致内存泄漏。显然要求每个使用者都清楚LeakSingleton的内部实现从而避免内存泄漏是不现实的,我们要从源头解决问题。
解决泄漏: 对LeakSingleton进行改造,在传入context时不直接使用该context,而是去获取context的applicationContext得到和单例生命周期一样长的context,从而避免持有短生命周期对象。改造后的单例如下:
class LeakSingleton private constructor(context: Context) {
companion object {
@Volatile
private var instance: LeakSingleton? = null
fun getInstance(context: Context): LeakSingleton? {
instance ?: synchronized(this) {
//使用applicationContext避免泄漏
instance ?: LeakSingleton(context.applicationContext).also {
instance = it
}
}
return instance
}
}
}
- handler引起的内存泄漏
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
handler.sendEmptyMessageDelayed(1, 5000)
}
private val handler = Handler {
if (it.what == 1) {
tv_leak.text = "leak text"
}
false
}
}
这段代码里实现了一个简单的handler,功能就是收到消息后给tv_leak设置一段文字。这样直接使用handler是很容易造成内存泄漏的。可以看到在这例子里,activity发送了一个延迟五秒的消息给handler,如果在发送完后用户按下返回键,activity应该进行销毁,但是因为我们这里的handler是一个类变量,它持有了activity的引用,导致在这五秒内activity并不能被正确的回收,从而造成内存泄漏。
解决泄漏:通常handler的解决方法比较简单,不要直接使用handler,而是采用静态实例+弱引用(WeakReference)方式。静态实例并不会持有外部类的引用,而弱引用保证了gc时对象能被及时回收。修改后的代码如下:
class MainActivity : AppCompatActivity() {
private val handler by lazy {
MyHandler(this)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
handler.sendEmptyMessageDelayed(1, 5000)
}
//伴生对象
companion object {
private class MyHandler(activity: MainActivity) : Handler() {
//弱引用解决内存泄漏
private val weakActivity = WeakReference<MainActivity>(activity)
override fun handleMessage(msg: Message?) {
super.handleMessage(msg)
val activity = weakActivity.get() ?: return
if (msg?.what == 1) {
activity.tv_leak.text = "leak text"
}
}
}
}
override fun onDestroy() {
super.onDestroy()
handler.removeCallbacksAndMessages(null)
}
}
- 动画引起的内存泄漏
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
startAnimation(tv_leak)
}
private fun startAnimation(view: View) {
val animator = ObjectAnimator.ofFloat(view,
"TranslationX", 10f, 10f, 10f)
animator.repeatCount = ValueAnimator.INFINITE
animator.interpolator = LinearInterpolator()
animator.duration = 8000
animator.start()
}
}
在这段代码中开启了一个view的属性动画,对tv_leak做平移操作,但是页面返回时,并没有停止动画。由于属性动画会持有进行动画的view的引用,而view持有activity的引用,如果没有页面退出时没有停止动画,则会造成内存泄漏。
解决泄漏: 在页面退出时取消动画。同时如果从当前页面跳转到其他页面,也应该在onPause中暂停动画,以避免资源浪费,在回到该页面时在onResume中继续动画播放。
class MainActivity : AppCompatActivity() {
private lateinit var animator: ObjectAnimator
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
startAnimation(tv_leak)
}
override fun onResume() {
super.onResume()
animator.resume()
}
override fun onPause() {
super.onPause()
animator.pause()
}
override fun onDestroy() {
super.onDestroy()
animator.cancel()
}
private fun startAnimation(view: View) {
animator = ObjectAnimator.ofFloat(view,
"TranslationX", 10f, 10f, 10f)
animator.repeatCount = ValueAnimator.INFINITE
animator.interpolator = LinearInterpolator()
animator.duration = 8000
animator.start()
}
}
总结
在日常的开发中多注意可能存在的内存泄漏情况,在使用静态变量时要注意是否会大内存对象,如bitmap。检测内存泄漏可以使用leakCanary或者android studio自带的Profiler进行检测。