AndroidResourceIdNames.kt
TLDR
This file defines the AndroidResourceIdNames
class which is responsible for storing and retrieving resource names based on resource IDs in an Android project. The class provides methods for retrieving a resource name given its ID and for saving resource names to memory. Additionally, there are utility methods for reading the associated resource names from a heap dump and for resetting the instance (used for testing purposes).
Methods
get
This method allows retrieving a resource name given its ID. It takes an integer parameter id
representing the resource ID and returns a string representing the resource name. If the ID is not found, the method returns null
.
saveToMemory
This method is used to save resource names to memory. It takes two function parameters, getResourceTypeName
and getResourceEntryName
, which delegate to Android's resource-related functions. The method internally iterates over resource IDs, retrieves their corresponding names using the provided functions, and stores the resource IDs and names in memory.
readFromHeap
This method reads the previously saved resource names from a heap dump by taking a HeapGraph
parameter. It retrieves the resource IDs and names stored in the heap dump's AndroidResourceIdNames
instance and constructs a new instance of AndroidResourceIdNames
with the retrieved data.
resetForTests
This internal method is used for resetting the instance of AndroidResourceIdNames
. It is typically used for testing purposes.
Classes
AndroidResourceIdNames
This class stores and retrieves resource names based on resource IDs. It has a private constructor and a companion object with utility methods for saving to memory, reading from a heap dump, and resetting the instance. The class also provides a subscript operator get
for retrieving resource names based on their IDs.
package shark
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.IntArrayDump
class AndroidResourceIdNames private constructor(
private val resourceIds: IntArray,
private val names: Array<String>
) {
operator fun get(id: Int): String? {
val indexOfId = resourceIds.binarySearch(id)
return if (indexOfId >= 0) {
names[indexOfId]
} else {
null
}
}
companion object {
internal const val FIRST_APP_RESOURCE_ID = 0x7F010000
internal const val RESOURCE_ID_TYPE_ITERATOR = 0x00010000
@Volatile
@JvmStatic
private var holderField: AndroidResourceIdNames? = null
/**
* @param getResourceTypeName a function that delegates to Android
* Resources.getResourceTypeName but returns null when the name isn't found instead of
* throwing an exception.
*
* @param getResourceEntryName a function that delegates to Android
* Resources.getResourceEntryName but returns null when the name isn't found instead of
* throwing an exception.
*/
@Synchronized fun saveToMemory(
getResourceTypeName: (Int) -> String?,
getResourceEntryName: (Int) -> String?
) {
if (holderField != null) {
return
}
// This is based on https://jebware.com/blog/?p=600 which itself is based on
// https://stackoverflow.com/a/6646113/703646
val idToNamePairs = mutableListOf<Pair<Int, String>>()
findIdTypeResourceIdStart(getResourceTypeName)?.let { idTypeResourceIdStart ->
var resourceId = idTypeResourceIdStart
while (true) {
val entry = getResourceEntryName(resourceId) ?: break
idToNamePairs += resourceId to entry
resourceId++
}
}
val resourceIds = idToNamePairs.map { it.first }
.toIntArray()
val names = idToNamePairs.map { it.second }
.toTypedArray()
holderField = AndroidResourceIdNames(resourceIds, names)
}
private fun findIdTypeResourceIdStart(getResourceTypeName: (Int) -> String?): Int? {
var resourceTypeId = FIRST_APP_RESOURCE_ID
while (true) {
when (getResourceTypeName(resourceTypeId)) {
null -> return null
"id" -> return resourceTypeId
else -> resourceTypeId += RESOURCE_ID_TYPE_ITERATOR
}
}
}
fun readFromHeap(graph: HeapGraph): AndroidResourceIdNames? {
return graph.context.getOrPut(AndroidResourceIdNames::class.java.name) {
val className = AndroidResourceIdNames::class.java.name
val holderClass = graph.findClassByName(className)
holderClass?.let {
val holderField = holderClass["holderField"]!!
holderField.valueAsInstance?.let { instance ->
val resourceIdsField = instance[className, "resourceIds"]!!
val resourceIdsArray = resourceIdsField.valueAsPrimitiveArray!!
val resourceIds =
(resourceIdsArray.readRecord() as IntArrayDump).array
val names = instance[className, "names"]!!.valueAsObjectArray!!.readElements()
.map { it.readAsJavaString()!! }
.toList()
.toTypedArray()
AndroidResourceIdNames(resourceIds, names)
}
}
}
}
internal fun resetForTests() {
holderField = null
}
}
}