前言
在https://27house.cn/archives/2396中已经介绍了,使用WeakReference可以与ReferenceQueue来判断是否执行了GC。按照这个理论,当GC的时候,就可以检测一下Activity是否泄露!
实现
代码如下所示,其中GcMonitor的代码在https://27house.cn/archives/2396中,这样就可以将泄露信息做自定义处理或者dump上报
data class UILeakMonitorConfig(
val checkInterval: Long = 1000 * 60,
val leakProcessor: ((leakActivitys: List<DestroyedActivityInfo>) -> Unit)? = null
)
object UILeakMonitor {
private const val TAG = "UILeakMonitor"
private var gcMonitor: GcMonitor? = null
private var config = UILeakMonitorConfig()
private val waitGcActivitys = mutableListOf<DestroyedActivityInfo>()
fun start(app: Application, config: UILeakMonitorConfig = UILeakMonitorConfig()) {
this.config = config
app.registerActivityLifecycleCallbacks(object : SimpleActivityLifecycleCallbacks() {
override fun onActivityDestroyed(activity: Activity) {
initGcMonitor()
pushDestroyedActivityInfo(activity)
}
})
}
private fun pushDestroyedActivityInfo(activity: Activity) {
val activityInfo = DestroyedActivityInfo(
key = generateKey(),
activityName = activity::class.java.name,
activityRef = WeakReference(activity),
activityDestroyMs = System.currentTimeMillis()
)
waitGcActivitys.add(activityInfo)
}
private fun initGcMonitor() {
if (gcMonitor == null) {
gcMonitor = GcMonitor()
gcMonitor?.startMonitor {
Log.i(TAG, "触发了GC")
Handler(Looper.getMainLooper()).post {
gc()
}
}
}
}
private fun gc() {
if (Debug.isDebuggerConnected()) {
return
}
val iterator = waitGcActivitys.iterator()
val leakedActivitys = mutableListOf<DestroyedActivityInfo>()
while (iterator.hasNext()) {
val destroyedActivityInfo = iterator.next()
if (destroyedActivityInfo.activityRef?.get() == null) {
iterator.remove()
Log.i(TAG, "${destroyedActivityInfo.activityName} 已经被回收")
continue
}
if (System.currentTimeMillis() - destroyedActivityInfo.activityDestroyMs < config.checkInterval) {
Log.i(
TAG,
"${destroyedActivityInfo.activityName} 小于${config.checkInterval},不作为泄露"
)
continue
}
leakedActivitys.add(destroyedActivityInfo)
}
config.leakProcessor?.invoke(leakedActivitys)
}
private fun generateKey(): String {
return UUID.randomUUID().toString()
}
}
data class DestroyedActivityInfo(
val key: String,
val activityName: String,
val activityRef: WeakReference<Activity>? = null,
val activityDestroyMs: Long
)