<lambda>null1 package leakcanary.internal.activity.screen
2
3 import android.app.AlertDialog
4 import android.view.View
5 import android.view.View.OnAttachStateChangeListener
6 import android.view.View.VISIBLE
7 import android.view.ViewGroup
8 import android.widget.EditText
9 import android.widget.ListView
10 import android.widget.TextView
11 import android.widget.Toast
12 import com.squareup.leakcanary.core.R
13 import leakcanary.internal.activity.db.Io
14 import leakcanary.internal.activity.db.executeOnIo
15 import leakcanary.internal.activity.ui.SimpleListAdapter
16 import leakcanary.internal.navigation.Screen
17 import leakcanary.internal.navigation.activity
18 import leakcanary.internal.navigation.inflate
19 import shark.HeapField
20 import shark.HeapObject.HeapClass
21 import shark.HeapObject.HeapInstance
22 import shark.HeapObject.HeapObjectArray
23 import shark.HeapObject.HeapPrimitiveArray
24 import shark.HeapValue
25 import shark.HprofHeapGraph.Companion.openHeapGraph
26 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.BooleanArrayDump
27 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.ByteArrayDump
28 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.CharArrayDump
29 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.DoubleArrayDump
30 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.FloatArrayDump
31 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.IntArrayDump
32 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.LongArrayDump
33 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.ShortArrayDump
34 import shark.ValueHolder.BooleanHolder
35 import shark.ValueHolder.ByteHolder
36 import shark.ValueHolder.CharHolder
37 import shark.ValueHolder.DoubleHolder
38 import shark.ValueHolder.FloatHolder
39 import shark.ValueHolder.IntHolder
40 import shark.ValueHolder.LongHolder
41 import shark.ValueHolder.ReferenceHolder
42 import shark.ValueHolder.ShortHolder
43 import java.io.Closeable
44 import java.io.File
45
46 internal class HprofExplorerScreen(
47 private val heapDumpFile: File
48 ) : Screen() {
49 override fun createView(container: ViewGroup) =
50 container.inflate(R.layout.leak_canary_hprof_explorer).apply {
51 container.activity.title = resources.getString(R.string.leak_canary_loading_title)
52
53 var closeable: Closeable? = null
54
55 addOnAttachStateChangeListener(object : OnAttachStateChangeListener {
56 override fun onViewAttachedToWindow(view: View) {
57 }
58
59 override fun onViewDetachedFromWindow(view: View) {
60 Io.execute {
61 closeable?.close()
62 }
63 }
64 })
65
66 executeOnIo {
67 val graph = heapDumpFile.openHeapGraph()
68 closeable = graph
69 updateUi {
70 container.activity.title =
71 resources.getString(R.string.leak_canary_explore_heap_dump)
72 val titleView = findViewById<TextView>(R.id.leak_canary_explorer_title)
73 val searchView = findViewById<View>(R.id.leak_canary_search_button)
74 val listView = findViewById<ListView>(R.id.leak_canary_explorer_list)
75 titleView.visibility = VISIBLE
76 searchView.visibility = VISIBLE
77 listView.visibility = VISIBLE
78 searchView.setOnClickListener {
79 val input = EditText(context)
80 AlertDialog.Builder(context)
81 .setIcon(android.R.drawable.ic_dialog_alert)
82 .setTitle("Type a fully qualified class name")
83 .setView(input)
84 .setPositiveButton(android.R.string.ok) { _, _ ->
85 executeOnIo {
86 val partialClassName = input.text.toString()
87 val matchingClasses = graph.classes
88 .filter { partialClassName in it.name }
89 .toList()
90
91 if (matchingClasses.isEmpty()) {
92 updateUi {
93 Toast.makeText(
94 context, "No class matching [$partialClassName]", Toast.LENGTH_LONG
95 )
96 .show()
97 }
98 } else {
99 updateUi {
100 titleView.text =
101 "${matchingClasses.size} classes matching [$partialClassName]"
102 listView.adapter = SimpleListAdapter(
103 R.layout.leak_canary_simple_row, matchingClasses
104 ) { view, position ->
105 val itemTitleView = view.findViewById<TextView>(R.id.leak_canary_row_text)
106 itemTitleView.text = matchingClasses[position].name
107 }
108 listView.setOnItemClickListener { _, _, position, _ ->
109 val selectedClass = matchingClasses[position]
110 showClass(titleView, listView, selectedClass)
111 }
112 }
113 }
114 }
115 }
116 .setNegativeButton(android.R.string.cancel, null)
117 .show()
118 }
119 }
120 }
121 }
122
123 private fun View.showClass(
124 titleView: TextView,
125 listView: ListView,
126 selectedClass: HeapClass
127 ) {
128 executeOnIo {
129 val className = selectedClass.name
130 val instances = selectedClass.directInstances.toList()
131 val staticFields = selectedClass.readStaticFields()
132 .fieldsAsString()
133 updateUi {
134 titleView.text =
135 "Class $className (${instances.size} instances)"
136 listView.adapter = SimpleListAdapter(
137 R.layout.leak_canary_simple_row, staticFields + instances
138 ) { view, position ->
139 val itemTitleView =
140 view.findViewById<TextView>(R.id.leak_canary_row_text)
141 if (position < staticFields.size) {
142 itemTitleView.text = staticFields[position].second
143 } else {
144 itemTitleView.text = "@${instances[position - staticFields.size].objectId}"
145 }
146 }
147 listView.setOnItemClickListener { _, _, position, _ ->
148 if (position < staticFields.size) {
149 val staticField = staticFields[position].first
150 onHeapValueClicked(titleView, listView, staticField.value)
151 } else {
152 val instance = instances[position - staticFields.size]
153 showInstance(titleView, listView, instance)
154 }
155 }
156 }
157 }
158 }
159
160 private fun View.showInstance(
161 titleView: TextView,
162 listView: ListView,
163 instance: HeapInstance
164 ) {
165 executeOnIo {
166 val fields = instance.readFields()
167 .fieldsAsString()
168 val className = instance.instanceClassName
169 updateUi {
170 titleView.text = "Instance @${instance.objectId} of class $className"
171 listView.adapter = SimpleListAdapter(
172 R.layout.leak_canary_simple_row, fields
173 ) { view, position ->
174 val itemTitleView =
175 view.findViewById<TextView>(R.id.leak_canary_row_text)
176 itemTitleView.text = fields[position].second
177 }
178 listView.setOnItemClickListener { _, _, position, _ ->
179 val field = fields[position].first
180 onHeapValueClicked(titleView, listView, field.value)
181 }
182 }
183 }
184 }
185
186 private fun View.showObjectArray(
187 titleView: TextView,
188 listView: ListView,
189 instance: HeapObjectArray
190 ) {
191 executeOnIo {
192 val elements = instance.readElements()
193 .mapIndexed { index: Int, element: HeapValue ->
194 element to "[$index] = ${element.heapValueAsString()}"
195 }
196 .toList()
197 val arrayClassName = instance.arrayClassName
198 val className = arrayClassName.substring(0, arrayClassName.length - 2)
199 updateUi {
200 titleView.text = "Array $className[${elements.size}]"
201 listView.adapter = SimpleListAdapter(
202 R.layout.leak_canary_simple_row, elements
203 ) { view, position ->
204 val itemTitleView =
205 view.findViewById<TextView>(R.id.leak_canary_row_text)
206 itemTitleView.text = elements[position].second
207 }
208 listView.setOnItemClickListener { _, _, position, _ ->
209 val element = elements[position].first
210 onHeapValueClicked(titleView, listView, element)
211 }
212 }
213 }
214 }
215
216 private fun View.showPrimitiveArray(
217 titleView: TextView,
218 listView: ListView,
219 instance: HeapPrimitiveArray
220 ) {
221 executeOnIo {
222 val (type, values) = when (val record = instance.readRecord()) {
223 is BooleanArrayDump -> "boolean" to record.array.map { it.toString() }
224 is CharArrayDump -> "char" to record.array.map { "'$it'" }
225 is FloatArrayDump -> "float" to record.array.map { it.toString() }
226 is DoubleArrayDump -> "double" to record.array.map { it.toString() }
227 is ByteArrayDump -> "byte" to record.array.map { it.toString() }
228 is ShortArrayDump -> "short" to record.array.map { it.toString() }
229 is IntArrayDump -> "int" to record.array.map { it.toString() }
230 is LongArrayDump -> "long" to record.array.map { it.toString() }
231 }
232 updateUi {
233 titleView.text = "Array $type[${values.size}]"
234 listView.adapter = SimpleListAdapter(
235 R.layout.leak_canary_simple_row, values
236 ) { view, position ->
237 val itemTitleView =
238 view.findViewById<TextView>(R.id.leak_canary_row_text)
239 itemTitleView.text = "$type ${values[position]}"
240 }
241 listView.setOnItemClickListener { _, _, _, _ ->
242 }
243 }
244 }
245 }
246
247 private fun View.onHeapValueClicked(
248 titleView: TextView,
249 listView: ListView,
250 heapValue: HeapValue
251 ) {
252 if (heapValue.isNonNullReference) {
253 when (val objectRecord = heapValue.asObject!!) {
254 is HeapInstance -> {
255 showInstance(titleView, listView, objectRecord)
256 }
257 is HeapClass -> {
258 showClass(titleView, listView, objectRecord)
259 }
260 is HeapObjectArray -> {
261 showObjectArray(titleView, listView, objectRecord)
262 }
263 is HeapPrimitiveArray -> {
264 showPrimitiveArray(titleView, listView, objectRecord)
265 }
266 }
267 }
268 }
269
270 private fun Sequence<HeapField>.fieldsAsString(): List<Pair<HeapField, String>> {
271 return map { field ->
272 field to "${field.declaringClass.simpleName}.${field.name} = ${field.value.heapValueAsString()}"
273 }
274 .toList()
275 }
276
277 private fun HeapValue.heapValueAsString(): String {
278 return when (val heapValue = holder) {
279 is ReferenceHolder -> {
280 if (isNullReference) {
281 "null"
282 } else {
283 when (val objectRecord = asObject!!) {
284 is HeapInstance -> {
285 if (objectRecord instanceOf "java.lang.String") {
286 "${objectRecord.instanceClassName}@${heapValue.value} \"${objectRecord.readAsJavaString()!!}\""
287 } else {
288 "${objectRecord.instanceClassName}@${heapValue.value}"
289 }
290 }
291 is HeapClass -> {
292 "Class ${objectRecord.name}"
293 }
294 is HeapObjectArray -> {
295 objectRecord.arrayClassName
296 }
297 is HeapPrimitiveArray -> objectRecord.arrayClassName
298 }
299 }
300 }
301 is BooleanHolder -> "boolean ${heapValue.value}"
302 is CharHolder -> "char ${heapValue.value}"
303 is FloatHolder -> "float ${heapValue.value}"
304 is DoubleHolder -> "double ${heapValue.value}"
305 is ByteHolder -> "byte ${heapValue.value}"
306 is ShortHolder -> "short ${heapValue.value}"
307 is IntHolder -> "int ${heapValue.value}"
308 is LongHolder -> "long ${heapValue.value}"
309 }
310 }
311 }
312