<lambda>null1 package leakcanary.internal.activity.db
2
3 import android.content.ContentValues
4 import android.database.sqlite.SQLiteDatabase
5 import android.os.AsyncTask
6 import leakcanary.internal.LeakDirectoryProvider
7 import leakcanary.internal.Serializables
8 import leakcanary.internal.toByteArray
9 import leakcanary.internal.friendly.checkNotMainThread
10 import leakcanary.internal.friendly.mainHandler
11 import org.intellij.lang.annotations.Language
12 import shark.HeapAnalysis
13 import shark.HeapAnalysisFailure
14 import shark.HeapAnalysisSuccess
15 import shark.SharkLog
16 import java.io.File
17 import java.util.concurrent.CopyOnWriteArrayList
18
19 internal object HeapAnalysisTable {
20
21 /**
22 * CopyOnWriteArrayList because registered listeners can remove themselves from this list while
23 * iterating and invoking them, which would trigger a ConcurrentModificationException (see #2019).
24 */
25 private val updateListeners = CopyOnWriteArrayList<() -> Unit>()
26
27 @Language("RoomSql")
28 const val create = """CREATE TABLE heap_analysis
29 (
30 id INTEGER PRIMARY KEY AUTOINCREMENT,
31 created_at_time_millis INTEGER,
32 dump_duration_millis INTEGER DEFAULT -1,
33 leak_count INTEGER DEFAULT 0,
34 exception_summary TEXT DEFAULT NULL,
35 object BLOB
36 )"""
37
38 @Language("RoomSql")
39 const val drop = "DROP TABLE IF EXISTS heap_analysis"
40
41 fun onUpdate(block: () -> Unit): () -> Unit {
42 updateListeners.add(block)
43 return {
44 updateListeners.remove(block)
45 }
46 }
47
48 fun insert(
49 db: SQLiteDatabase,
50 heapAnalysis: HeapAnalysis
51 ): Long {
52 val values = ContentValues()
53 values.put("created_at_time_millis", heapAnalysis.createdAtTimeMillis)
54 values.put("dump_duration_millis", heapAnalysis.dumpDurationMillis)
55 values.put("object", heapAnalysis.toByteArray())
56 when (heapAnalysis) {
57 is HeapAnalysisSuccess -> {
58 val leakCount = heapAnalysis.applicationLeaks.size + heapAnalysis.libraryLeaks.size
59 values.put("leak_count", leakCount)
60 }
61 is HeapAnalysisFailure -> {
62 val cause = heapAnalysis.exception.cause!!
63 val exceptionSummary = "${cause.javaClass.simpleName} ${cause.message}"
64 values.put("exception_summary", exceptionSummary)
65 }
66 }
67
68 return db.inTransaction {
69 val heapAnalysisId = db.insertOrThrow("heap_analysis", null, values)
70 if (heapAnalysis is HeapAnalysisSuccess) {
71 heapAnalysis.allLeaks
72 .forEach { leakingInstance ->
73 LeakTable.insert(
74 db, heapAnalysisId, leakingInstance
75 )
76 }
77 }
78 heapAnalysisId
79 }.apply { notifyUpdateOnMainThread() }
80 }
81
82 private fun notifyUpdateOnMainThread() {
83 checkNotMainThread()
84 mainHandler.post {
85 updateListeners.forEach { it() }
86 }
87 }
88
89 inline fun <reified T : HeapAnalysis> retrieve(
90 db: SQLiteDatabase,
91 id: Long
92 ): T? {
93 return db.rawQuery(
94 """
95 SELECT
96 object
97 FROM heap_analysis
98 WHERE id=$id
99 """, null
100 )
101 .use { cursor ->
102 if (cursor.moveToNext()) {
103 val analysis = Serializables.fromByteArray<T>(cursor.getBlob(0))
104 if (analysis == null) {
105 delete(db, id, null)
106 }
107 analysis
108 } else {
109 null
110 }
111 }
112 }
113
114 fun retrieveAll(db: SQLiteDatabase): List<Projection> {
115 return db.rawQuery(
116 """
117 SELECT
118 id
119 , created_at_time_millis
120 , leak_count
121 , exception_summary
122 FROM heap_analysis
123 ORDER BY created_at_time_millis DESC
124 """, null
125 )
126 .use { cursor ->
127 val all = mutableListOf<Projection>()
128 while (cursor.moveToNext()) {
129 val summary = Projection(
130 id = cursor.getLong(0),
131 createdAtTimeMillis = cursor.getLong(1),
132 leakCount = cursor.getInt(2),
133 exceptionSummary = cursor.getString(3)
134 )
135 all.add(summary)
136 }
137 all
138 }
139 }
140
141 fun delete(
142 db: SQLiteDatabase,
143 heapAnalysisId: Long,
144 heapDumpFile: File?
145 ) {
146 if (heapDumpFile != null) {
147 AsyncTask.SERIAL_EXECUTOR.execute {
148 val path = heapDumpFile.absolutePath
149 val heapDumpDeleted = heapDumpFile.delete()
150 if (heapDumpDeleted) {
151 LeakDirectoryProvider.filesDeletedRemoveLeak += path
152 } else {
153 SharkLog.d { "Could not delete heap dump file ${heapDumpFile.path}" }
154 }
155 }
156 }
157
158 db.inTransaction {
159 db.delete("heap_analysis", "id=$heapAnalysisId", null)
160 LeakTable.deleteByHeapAnalysisId(db, heapAnalysisId)
161 }
162 notifyUpdateOnMainThread()
163 }
164
165 fun deleteAll(db: SQLiteDatabase) {
166 db.inTransaction {
167 rawQuery(
168 """
169 SELECT
170 id,
171 object
172 FROM heap_analysis
173 """, null
174 )
175 .use { cursor ->
176 val all = mutableListOf<Pair<Long, HeapAnalysis>>()
177 while (cursor.moveToNext()) {
178 val id = cursor.getLong(0)
179 val analysis = Serializables.fromByteArray<HeapAnalysis>(cursor.getBlob(1))
180 if (analysis != null) {
181 all += id to analysis
182 }
183 }
184 all.forEach { (id, _) ->
185 db.delete("heap_analysis", "id=$id", null)
186 LeakTable.deleteByHeapAnalysisId(db, id)
187 }
188 AsyncTask.SERIAL_EXECUTOR.execute {
189 all.forEach { (_, analysis) ->
190 analysis.heapDumpFile.delete()
191 }
192 }
193 }
194 }
195 notifyUpdateOnMainThread()
196 }
197
198 class Projection(
199 val id: Long,
200 val createdAtTimeMillis: Long,
201 val leakCount: Int,
202 val exceptionSummary: String?
203 )
204 }