<lambda>null1 package leakcanary.internal.activity.screen
2
3 import android.content.Context
4 import android.content.res.Resources
5 import android.graphics.Bitmap
6 import android.graphics.Bitmap.Config.ARGB_8888
7 import android.graphics.Canvas
8 import android.graphics.Color
9 import android.graphics.Paint
10 import android.graphics.Paint.Style.FILL
11 import android.graphics.Paint.Style.STROKE
12 import android.graphics.Rect
13 import com.squareup.leakcanary.core.R
14 import leakcanary.internal.navigation.getColorCompat
15 import shark.HprofRecord
16 import shark.HprofRecord.HeapDumpEndRecord
17 import shark.HprofRecord.HeapDumpRecord.GcRootRecord
18 import shark.HprofRecord.HeapDumpRecord.HeapDumpInfoRecord
19 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ClassDumpRecord
20 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.InstanceDumpRecord
21 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ObjectArrayDumpRecord
22 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.BooleanArrayDump
23 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.ByteArrayDump
24 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.CharArrayDump
25 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.DoubleArrayDump
26 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.FloatArrayDump
27 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.IntArrayDump
28 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.LongArrayDump
29 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.ShortArrayDump
30 import shark.HprofRecord.LoadClassRecord
31 import shark.HprofRecord.StackTraceRecord
32 import shark.HprofRecord.StringRecord
33 import shark.StreamingHprofReader
34 import shark.StreamingRecordReaderAdapter.Companion.asStreamingRecordReader
35 import java.io.File
36 import kotlin.math.ceil
37 import kotlin.math.max
38
39 internal object HeapDumpRenderer {
40
41 private class HasDensity(resources: Resources) {
42 val density = resources.displayMetrics.density
43
44 val Int.dp
45 get() = this * density
46
47 val Float.dp
48 get() = this * density
49 }
50
51 @Suppress("LongMethod")
52 fun render(
53 context: Context,
54 heapDumpFile: File,
55 sourceWidth: Int,
56 sourceHeight: Int,
57 /**
58 * If [sourceBytesPerPixel] > 0 then [sourceHeight] will be ignored.
59 */
60 sourceBytesPerPixel: Int
61 ): Bitmap = with(HasDensity(context.resources)) {
62 val recordPositions = mutableListOf<Pair<Int, Long>>()
63 var currentRecord: HprofRecord? = null
64
65 val otherColor = context.getColorCompat(R.color.leak_canary_heap_other)
66 val stackTraceColor = context.getColorCompat(R.color.leak_canary_heap_stack_trace)
67 val hprofStringColor = context.getColorCompat(R.color.leak_canary_heap_hprof_string)
68 val loadClassColor = context.getColorCompat(R.color.leak_canary_heap_load_class)
69 val classDumpColor = context.getColorCompat(R.color.leak_canary_heap_class_dump)
70 val instanceColor = context.getColorCompat(R.color.leak_canary_heap_instance)
71 val objectArrayColor = context.getColorCompat(R.color.leak_canary_heap_object_array)
72 val booleanArrayColor = context.getColorCompat(R.color.leak_canary_heap_boolean_array)
73 val charArrayColor = context.getColorCompat(R.color.leak_canary_heap_char_array)
74 val floatArrayColor = context.getColorCompat(R.color.leak_canary_heap_float_array)
75 val doubleArrayColor = context.getColorCompat(R.color.leak_canary_heap_double_array)
76 val byteArrayColor = context.getColorCompat(R.color.leak_canary_heap_byte_array)
77 val shortArrayColor = context.getColorCompat(R.color.leak_canary_heap_short_array)
78 val intArrayColor = context.getColorCompat(R.color.leak_canary_heap_int_array)
79 val longArrayColor = context.getColorCompat(R.color.leak_canary_heap_long_array)
80 val colors = mapOf(
81 StringRecord::class to hprofStringColor,
82 LoadClassRecord::class to loadClassColor,
83 ClassDumpRecord::class to classDumpColor,
84 InstanceDumpRecord::class to instanceColor,
85 ObjectArrayDumpRecord::class to objectArrayColor,
86 BooleanArrayDump::class to booleanArrayColor,
87 CharArrayDump::class to charArrayColor,
88 FloatArrayDump::class to floatArrayColor,
89 DoubleArrayDump::class to doubleArrayColor,
90 ByteArrayDump::class to byteArrayColor,
91 ShortArrayDump::class to shortArrayColor,
92 IntArrayDump::class to intArrayColor,
93 LongArrayDump::class to longArrayColor,
94 StackTraceRecord::class to stackTraceColor,
95 HeapDumpEndRecord::class to otherColor,
96 GcRootRecord::class to otherColor
97 )
98
99 val appHeapColor = context.getColorCompat(R.color.leak_canary_heap_app)
100 val imageHeapColor = context.getColorCompat(R.color.leak_canary_heap_image)
101 val zygoteHeapColor = context.getColorCompat(R.color.leak_canary_heap_zygote)
102 val stringColor = context.getColorCompat(R.color.leak_canary_heap_instance_string)
103
104 var lastPosition = 0L
105
106 val reader = StreamingHprofReader.readerFor(heapDumpFile).asStreamingRecordReader()
107 val hprofStringCache = mutableMapOf<Long, String>()
108 val classNames = mutableMapOf<Long, Long>()
109 reader.readRecords(
110 setOf(HprofRecord::class)
111 ) { position, record ->
112 lastPosition = position
113 when (record) {
114 is StringRecord -> {
115 hprofStringCache[record.id] = record.string
116 }
117 is LoadClassRecord -> {
118 classNames[record.id] = record.classNameStringId
119 }
120 }
121 val localCurrentRecord = currentRecord
122 when {
123 localCurrentRecord is HeapDumpInfoRecord -> {
124 val colorForHeapInfo =
125 when (hprofStringCache[localCurrentRecord.heapNameStringId]) {
126 // The primary heap on which your app allocates memory.
127 "app" -> appHeapColor
128 // The system boot image, containing classes that are preloaded during boot time.
129 // Allocations here are guaranteed to never move or go away.
130 "image" -> imageHeapColor
131 // The copy-on-write heap where an app process is forked from in the Android system.
132 "zygote" -> zygoteHeapColor
133 // JNI heap: The heap that shows where Java Native Interface (JNI) references are allocated and released.
134 // default heap: When no heap is specified by the system
135 else -> otherColor
136 }
137 recordPositions.add(colorForHeapInfo to position)
138 currentRecord = record
139 }
140 localCurrentRecord is InstanceDumpRecord
141 && hprofStringCache[classNames[localCurrentRecord.classId]] == "java.lang.String"
142 && (record !is InstanceDumpRecord || hprofStringCache[classNames[record.classId]]
143 != "java.lang.String")
144 -> {
145 recordPositions.add(stringColor to position)
146 currentRecord = record
147 }
148 currentRecord == null -> {
149 recordPositions.add(otherColor to position)
150 currentRecord = record
151 }
152 currentRecord!!::class != record::class -> {
153 recordPositions.add(colors.getValue(currentRecord!!::class) to position)
154 currentRecord = record
155 }
156 }
157 }
158 val heapLength = lastPosition
159
160 var height: Int
161 val bytesPerPixel: Double
162
163 if (sourceBytesPerPixel > 0) {
164 bytesPerPixel = sourceBytesPerPixel.toDouble()
165 height = ceil((heapLength / bytesPerPixel) / sourceWidth)
166 .toInt()
167 } else {
168 height = sourceHeight
169 bytesPerPixel = heapLength * 1.0 / (sourceWidth * height)
170 }
171
172 val bitmap: Bitmap =
173 Bitmap.createBitmap(sourceWidth, height, ARGB_8888)
174
175 val canvas = Canvas(bitmap)
176
177 val legend = mapOf(
178 "Hprof string" to hprofStringColor,
179 "Class name" to loadClassColor,
180 "App heap" to appHeapColor,
181 "Image heap" to imageHeapColor,
182 "Zygote heap" to zygoteHeapColor,
183 "Other heap" to otherColor,
184 "Class content" to classDumpColor,
185 "Instance" to instanceColor,
186 "String" to stringColor,
187 "Object array" to objectArrayColor,
188 "Boolean array" to booleanArrayColor,
189 "Char array" to charArrayColor,
190 "Float array" to floatArrayColor,
191 "Double array" to doubleArrayColor,
192 "Byte array" to byteArrayColor,
193 "Short array" to shortArrayColor,
194 "Int array" to intArrayColor,
195 "Long array" to longArrayColor,
196 "Stack trace" to stackTraceColor,
197 "Heap End" to otherColor
198 )
199
200 val legendTextPaint = Paint(Paint.ANTI_ALIAS_FLAG)
201
202 legendTextPaint.color = Color.WHITE
203 legendTextPaint.style = FILL
204 canvas.drawPaint(legendTextPaint)
205
206 val legendSquareFillPaint = Paint()
207 legendSquareFillPaint.style = FILL
208 val legendSquareStrokePaint = Paint()
209 legendSquareStrokePaint.style = STROKE
210 legendSquareStrokePaint.strokeWidth = 0.8f.dp
211 legendSquareStrokePaint.color = Color.BLACK
212
213 legendTextPaint.color = Color.BLACK
214 legendTextPaint.textSize = 16.dp
215
216 val metrics = legendTextPaint.fontMetrics
217 val textHeight = metrics.descent - metrics.ascent
218
219 val xBounds = Rect()
220 legendTextPaint.getTextBounds("x", 0, 1, xBounds)
221 val squareSize = xBounds.height()
222 val squarePaddingTop = (textHeight - squareSize) / 2
223 val squareToTextPadding = 4.dp
224 val blockToBlockPadding = 8.dp
225
226 var maxTextWidth = 0f
227 for (name in legend.keys) {
228 maxTextWidth = max(maxTextWidth, legendTextPaint.measureText(name))
229 }
230
231 val padding = 8.dp
232 var blockLeft = padding
233 var blockTop = padding
234 val legendWidth = sourceWidth - 2 * padding
235 for ((name, color) in legend) {
236 if (blockLeft + squareSize + squareToTextPadding + maxTextWidth > legendWidth) {
237 blockLeft = padding
238 blockTop += textHeight
239 }
240
241 legendSquareFillPaint.color = color
242 canvas.drawRect(
243 blockLeft, blockTop + squarePaddingTop, blockLeft + squareSize,
244 blockTop + squarePaddingTop + squareSize,
245 legendSquareFillPaint
246 )
247 canvas.drawRect(
248 blockLeft, blockTop + squarePaddingTop, blockLeft + squareSize,
249 blockTop + squarePaddingTop + squareSize,
250 legendSquareStrokePaint
251 )
252 blockLeft += squareSize + squareToTextPadding
253 canvas.drawText(name, blockLeft, blockTop - metrics.ascent, legendTextPaint)
254 blockLeft += maxTextWidth
255 blockLeft += blockToBlockPadding
256 }
257 val legendHeight = blockTop + textHeight + padding
258 val source = Rect(0, 0, sourceWidth, legendHeight.toInt())
259 val destination = Rect(0, (height - legendHeight).toInt(), sourceWidth, height)
260 canvas.drawBitmap(bitmap, source, destination, null)
261 height -= legendHeight.toInt()
262
263 val pixelPaint = Paint(Paint.ANTI_ALIAS_FLAG.inv())
264 pixelPaint.style = FILL
265
266 var recordIndex = 0
267 for (y in 0 until height) {
268 for (x in 0 until sourceWidth) {
269 val bitmapPosition = y * sourceWidth + x
270 val heapPosition = (bitmapPosition * bytesPerPixel).toInt()
271 while (heapPosition > recordPositions[recordIndex].second && recordIndex < recordPositions.lastIndex) {
272 recordIndex++
273 }
274 pixelPaint.color = recordPositions[recordIndex].first
275 canvas.drawPoint(x.toFloat(), y.toFloat(), pixelPaint)
276 }
277 }
278 return bitmap
279 }
280 }
281