持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第6天,点击查看活动详情
本篇文章主要是介绍业务开发过程中,通过案例分析引入避免内存泄漏的几种思维,借助这些思维写出更加安全稳定的代码,希望能给你带来帮助。
避免长生命周期对象持有短生命周期对象
业务开发中碰到过这样的一个场景,通过Application.registerActivityLifecycleCallbacks()
方法注入ActivityLifecycleCallbacks
监听所有Activity
的创建,并在onActivityResumed(Activity)
方法中将参数Activity
保存到Application
中,这样就能够实现在任何地方获取当前正在显示的界面Activity实例
,代码如下:
class MainApplication : Application() {
lateinit var mCurrentActivity: Activity
override fun onCreate() {
super.onCreate()
registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
override fun onActivityResumed(activity: Activity) {
mCurrentActivity = activity
}
})
}
比如根据网络响应结果弹出一个弹窗,不可能在每个界面Activity中都做一个弹窗的响应处理,这个时候就需要利用这个mCurrentActivity
进行Dialog
的显示。
这样就会产生一个问题,Applciation
的生命周期是比Activity
生命周期长的,而一个短周期对象被一个长周期对象就有概率产生内存泄漏的风险,所以业务开发中要避免这样做。
注册监听回调同时要注意反注册
当前有这么一个业务场景:有一个网络监听工具类,界面可以选择性的向这个工具类中添加回调监听,从而获取当前的网络状态及变化。
- 定义一个网络监听回调类:
interface NetworkCallback {
/**
* 断网
*/
fun netDown(): Unit
/**
* 连上网络
*/
fun connected(): Unit
/**
* 网络较差
*/
fun connectWorse(): Unit
}
- 定义一个网络监听回调的集合管理类,进行回调监听的添加移除、网络状况的通知分发:
object NetworkMonitor {
private var mCallbacks: CopyOnWriteArrayList<NetworkCallback> = CopyOnWriteArrayList()
//注册监听
fun register(callback: NetworkCallback) {
mCallbacks.add(callback)
}
//有网分发
fun dispatchConnected() {
mCallbacks.forEach {
it.connected()
}
}
}
- 某个界面
MainActivity2
添加网络回调监听:
class MainActivity2 : AppCompatActivity() {
private lateinit var mBinding: ActivityMain2Binding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mBinding = ActivityMain2Binding.inflate(layoutInflater)
setContentView(mBinding.root)
NetworkMonitor.register(object: NetworkCallback {
override fun netDown() {
}
override fun connected() {
}
override fun connectWorse() {
}
})
}
}
这样看起来有毛病吗,有很大的毛病,总所周知:
匿名内部类默认会持有外部类的引用。在当前的这个案例中,通过
NetworkMonitor
添加的NetworkCallback
实现类会默认持有外部类MainActivity2
,这样NetworkMonitor
这个全局网络管理类就间接持有了MainActivity2
的引用,如果没有反注册监听回调,那么MainActivity2
就无法正常销毁了,同样也出现了上面的长生命周期对象持有了短生命周期对象的引用
,这样就直接引发了内存泄漏。
当前这个案例中该怎么解决这个问题呢,有两种方式:
- 增加一个反注册方法
unregister()
,当界面销毁时移除该回调对象:
//移除监听
fun unregister(callback: NetworkCallback) {
mCallbacks.remove(callback)
}
这种需要我们每次比如都要在onDestroyed()
中手动调用unregister()
方法移除监听,忘了就巴比Q了。
- 借助
LifecycleEventObserver
绑定界面生命周期,实现自动移除:
object NetworkMonitor {
private var mCallbacks: CopyOnWriteArrayList<NetworkCallback> = CopyOnWriteArrayList()
//注册监听
fun register(activity: LifecycleOwner, callback: NetworkCallback) {
mCallbacks.add(callback)
val callbackWrapper = NetworkCallbackWrapper(callback)
activity.lifecycle.addObserver(callbackWrapper)
}
//有网分发
fun dispatchConnected() {
mCallbacks.forEach {
it.connected()
}
}
//网络监听回调包装类
class NetworkCallbackWrapper(private val callback: NetworkCallback): LifecycleEventObserver {
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (event == Lifecycle.Event.ON_DESTROY) {
source.lifecycle.removeObserver(this)
mCallbacks.remove(callback)
}
}
}
}
这里的业务代码只是列出了一部分,主要是为了讲解这个案例,其他的情况暂不进行考虑。
可以看到我增加了一个NetworkCallbackWrapper
包装类,包装网络监听回调类NetworkCallback
,并实现了LifecycleEventObserver
监听界面生命周期,通过改造后的register()
方法传入LifecycleOwner
添加到界面的生命周期监听集合中,这样当监听到界面销毁时,就移除监听,避免内存泄漏。
有没有感觉这种方式非常的眼熟哈,LiveData
的源码中对于观察者的处理就是如此,感兴趣的可以去看下LiveData
的observe()
方法以及观察者包装类LifecycleBoundObserver
。
所以我们可以得出结论,设计一个管理类提供注册监听的时候,一定要提高反注册的方法提供给业务方。
总结
上面介绍的这两种案例是业务开发中非常常见的,所以在进行这种场景开发中一定需要谨记这两种思维:
-
避免长生命周期对象持有短生命周期对象;
-
注册监听回调同时要注意反注册;
同时要善用内存泄漏监听库LeakCanary
在Debug
环境下对应用测试。