main

square/leakcanary

Last updated at: 29/12/2023 09:38

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
    }
  }
}