HeapAnalysisTable.kt
TLDR
This file, HeapAnalysisTable.kt
, contains an object named HeapAnalysisTable
which has various methods for manipulating the heap_analysis
table in a SQLite database. The methods include inserting new data, retrieving data, and deleting data from the table.
Methods
onUpdate
A method that registers a listener that will be called whenever an update is made to the heap_analysis
table. It returns a function that can be called to remove the listener.
insert
Inserts a new HeapAnalysis
object into the heap_analysis
table. The method takes an instance of SQLiteDatabase
and a HeapAnalysis
object as parameters.
notifyUpdateOnMainThread
Notifies all registered update listeners on the main thread that an update has been made to the heap_analysis
table.
retrieve
Retrieves a HeapAnalysis
object from the heap_analysis
table based on the specified id
. The method takes an instance of SQLiteDatabase
and the id
as parameters. It returns a nullable type based on the type parameter provided.
retrieveAll
Retrieves a list of Projection
objects representing summaries of all rows in the heap_analysis
table. The method takes an instance of SQLiteDatabase
as a parameter.
delete
Deletes a row from the heap_analysis
table based on the specified heapAnalysisId
. The method also deletes the corresponding heap dump file if provided.
deleteAll
Deletes all rows from the heap_analysis
table. The method takes an instance of SQLiteDatabase
as a parameter.
Classes
No classes are defined in this file.
package leakcanary.internal.activity.db
import android.content.ContentValues
import android.database.sqlite.SQLiteDatabase
import android.os.AsyncTask
import leakcanary.internal.LeakDirectoryProvider
import leakcanary.internal.Serializables
import leakcanary.internal.toByteArray
import leakcanary.internal.friendly.checkNotMainThread
import leakcanary.internal.friendly.mainHandler
import org.intellij.lang.annotations.Language
import shark.HeapAnalysis
import shark.HeapAnalysisFailure
import shark.HeapAnalysisSuccess
import shark.SharkLog
import java.io.File
import java.util.concurrent.CopyOnWriteArrayList
internal object HeapAnalysisTable {
/**
* CopyOnWriteArrayList because registered listeners can remove themselves from this list while
* iterating and invoking them, which would trigger a ConcurrentModificationException (see #2019).
*/
private val updateListeners = CopyOnWriteArrayList<() -> Unit>()
@Language("RoomSql")
const val create = """CREATE TABLE heap_analysis
(
id INTEGER PRIMARY KEY AUTOINCREMENT,
created_at_time_millis INTEGER,
dump_duration_millis INTEGER DEFAULT -1,
leak_count INTEGER DEFAULT 0,
exception_summary TEXT DEFAULT NULL,
object BLOB
)"""
@Language("RoomSql")
const val drop = "DROP TABLE IF EXISTS heap_analysis"
fun onUpdate(block: () -> Unit): () -> Unit {
updateListeners.add(block)
return {
updateListeners.remove(block)
}
}
fun insert(
db: SQLiteDatabase,
heapAnalysis: HeapAnalysis
): Long {
val values = ContentValues()
values.put("created_at_time_millis", heapAnalysis.createdAtTimeMillis)
values.put("dump_duration_millis", heapAnalysis.dumpDurationMillis)
values.put("object", heapAnalysis.toByteArray())
when (heapAnalysis) {
is HeapAnalysisSuccess -> {
val leakCount = heapAnalysis.applicationLeaks.size + heapAnalysis.libraryLeaks.size
values.put("leak_count", leakCount)
}
is HeapAnalysisFailure -> {
val cause = heapAnalysis.exception.cause!!
val exceptionSummary = "${cause.javaClass.simpleName} ${cause.message}"
values.put("exception_summary", exceptionSummary)
}
}
return db.inTransaction {
val heapAnalysisId = db.insertOrThrow("heap_analysis", null, values)
if (heapAnalysis is HeapAnalysisSuccess) {
heapAnalysis.allLeaks
.forEach { leakingInstance ->
LeakTable.insert(
db, heapAnalysisId, leakingInstance
)
}
}
heapAnalysisId
}.apply { notifyUpdateOnMainThread() }
}
private fun notifyUpdateOnMainThread() {
checkNotMainThread()
mainHandler.post {
updateListeners.forEach { it() }
}
}
inline fun <reified T : HeapAnalysis> retrieve(
db: SQLiteDatabase,
id: Long
): T? {
return db.rawQuery(
"""
SELECT
object
FROM heap_analysis
WHERE id=$id
""", null
)
.use { cursor ->
if (cursor.moveToNext()) {
val analysis = Serializables.fromByteArray<T>(cursor.getBlob(0))
if (analysis == null) {
delete(db, id, null)
}
analysis
} else {
null
}
}
}
fun retrieveAll(db: SQLiteDatabase): List<Projection> {
return db.rawQuery(
"""
SELECT
id
, created_at_time_millis
, leak_count
, exception_summary
FROM heap_analysis
ORDER BY created_at_time_millis DESC
""", null
)
.use { cursor ->
val all = mutableListOf<Projection>()
while (cursor.moveToNext()) {
val summary = Projection(
id = cursor.getLong(0),
createdAtTimeMillis = cursor.getLong(1),
leakCount = cursor.getInt(2),
exceptionSummary = cursor.getString(3)
)
all.add(summary)
}
all
}
}
fun delete(
db: SQLiteDatabase,
heapAnalysisId: Long,
heapDumpFile: File?
) {
if (heapDumpFile != null) {
AsyncTask.SERIAL_EXECUTOR.execute {
val path = heapDumpFile.absolutePath
val heapDumpDeleted = heapDumpFile.delete()
if (heapDumpDeleted) {
LeakDirectoryProvider.filesDeletedRemoveLeak += path
} else {
SharkLog.d { "Could not delete heap dump file ${heapDumpFile.path}" }
}
}
}
db.inTransaction {
db.delete("heap_analysis", "id=$heapAnalysisId", null)
LeakTable.deleteByHeapAnalysisId(db, heapAnalysisId)
}
notifyUpdateOnMainThread()
}
fun deleteAll(db: SQLiteDatabase) {
db.inTransaction {
rawQuery(
"""
SELECT
id,
object
FROM heap_analysis
""", null
)
.use { cursor ->
val all = mutableListOf<Pair<Long, HeapAnalysis>>()
while (cursor.moveToNext()) {
val id = cursor.getLong(0)
val analysis = Serializables.fromByteArray<HeapAnalysis>(cursor.getBlob(1))
if (analysis != null) {
all += id to analysis
}
}
all.forEach { (id, _) ->
db.delete("heap_analysis", "id=$id", null)
LeakTable.deleteByHeapAnalysisId(db, id)
}
AsyncTask.SERIAL_EXECUTOR.execute {
all.forEach { (_, analysis) ->
analysis.heapDumpFile.delete()
}
}
}
}
notifyUpdateOnMainThread()
}
class Projection(
val id: Long,
val createdAtTimeMillis: Long,
val leakCount: Int,
val exceptionSummary: String?
)
}