LeaksDbHelper.kt
TLDR
The LeaksDbHelper
class in the leakcanary.internal.activity.db
package is responsible for managing the database used in the LeakCanary library for storing heap analysis information. It extends the SQLiteOpenHelper
class and provides methods for creating, upgrading, and downgrading the database.
Methods
This file does not contain any additional methods.
Classes
LeaksDbHelper
The LeaksDbHelper
class is responsible for managing the database used in the LeakCanary library for storing heap analysis information. It extends the SQLiteOpenHelper
class and provides methods for creating, upgrading, and downgrading the database.
package leakcanary.internal.activity.db
import android.content.ContentValues
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import leakcanary.internal.Serializables
import leakcanary.internal.toByteArray
import shark.HeapAnalysis
import shark.HeapAnalysisSuccess
import shark.LeakTrace
internal class LeaksDbHelper(context: Context) : SQLiteOpenHelper(
context, DATABASE_NAME, null, VERSION
) {
override fun onCreate(db: SQLiteDatabase) {
db.execSQL(HeapAnalysisTable.create)
db.execSQL(LeakTable.create)
db.execSQL(LeakTable.createSignatureIndex)
db.execSQL(LeakTraceTable.create)
}
override fun onUpgrade(
db: SQLiteDatabase,
oldVersion: Int,
newVersion: Int
) {
if (oldVersion < 23) {
recreateDb(db)
return
}
if (oldVersion < 24) {
db.execSQL("ALTER TABLE heap_analysis ADD COLUMN dump_duration_millis INTEGER DEFAULT -1")
}
if (oldVersion < 25) {
// Fix owningClassName=null in the serialized heap analysis.
// https://github.com/square/leakcanary/issues/2067
val idToAnalysis = db.rawQuery("SELECT id, object FROM heap_analysis", null)
.use { cursor ->
generateSequence {
if (cursor.moveToNext()) {
val id = cursor.getLong(0)
val analysis = Serializables.fromByteArray<HeapAnalysis>(cursor.getBlob(1))
id to analysis
} else {
null
}
}
.filter {
it.second is HeapAnalysisSuccess
}
.map { pair ->
val analysis = pair.second as HeapAnalysisSuccess
val unreachableObjects = try {
analysis.unreachableObjects
} catch (ignored: NullPointerException) {
// This currently doesn't trigger but the Kotlin compiler might change one day.
emptyList()
} ?: emptyList() // Compiler doesn't know it but runtime can have null.
pair.first to analysis.copy(
unreachableObjects = unreachableObjects,
applicationLeaks = analysis.applicationLeaks.map { leak ->
leak.copy(leak.leakTraces.fixNullReferenceOwningClassName())
},
libraryLeaks = analysis.libraryLeaks.map { leak ->
leak.copy(leak.leakTraces.fixNullReferenceOwningClassName())
}
)
}.toList()
}
db.inTransaction {
idToAnalysis.forEach { (id, heapAnalysis) ->
val values = ContentValues()
values.put("object", heapAnalysis.toByteArray())
db.update("heap_analysis", values, "id=$id", null)
}
}
}
}
private fun List<LeakTrace>.fixNullReferenceOwningClassName(): List<LeakTrace> {
return map { leakTrace ->
leakTrace.copy(
referencePath = leakTrace.referencePath.map { reference ->
val owningClassName = try {
// This can return null at runtime from previous serialized version without the field.
reference.owningClassName
} catch (ignored: NullPointerException) {
// This currently doesn't trigger but the Kotlin compiler might change one day.
null
}
if (owningClassName == null) {
reference.copy(owningClassName = reference.originObject.classSimpleName)
} else {
reference
}
})
}
}
override fun onDowngrade(
db: SQLiteDatabase,
oldVersion: Int,
newVersion: Int
) {
recreateDb(db)
}
private fun recreateDb(db: SQLiteDatabase) {
db.execSQL(HeapAnalysisTable.drop)
db.execSQL(LeakTable.drop)
db.execSQL(LeakTraceTable.drop)
onCreate(db)
}
companion object {
internal const val VERSION = 25
internal const val DATABASE_NAME = "leaks.db"
}
}