Layout Inspector — debuggear la UI en vivo
¿Alguna vez pasaste 30 minutos ajustando margins en XML, corriendo la app, viendo que no cambió nada, ajustando de nuevo? Layout Inspector termina con eso.
Abrilo desde Tools → Layout Inspector con la app corriendo. Muestra el árbol de vistas completo en tiempo real, con cada View seleccionable y sus atributos visibles — tamaño, padding, margin, visibilidad, el texto que realmente se está renderizando, y de qué layout XML viene cada nodo.
Los casos de uso reales
View invisible que ocupa espacio: el clásico — algo invisible pero INVISIBLE (no GONE) que bloquea touches o desplaza elementos. En Layout Inspector lo ves inmediatamente: la View aparece en el árbol aunque no se vea en pantalla, con visibility = INVISIBLE marcado.
Overlap de Views: dos Views superpuestas donde una tapa a la otra. El árbol muestra el orden de renderizado exacto. Podés seleccionar cada una y ver sus bounds.
ConstraintLayout que no respeta las constraints: seleccionás la View problemática y ves exactamente qué constraints tiene aplicadas, cuáles están activas y el tamaño resultante. Compara el XML teórico con la realidad en el dispositivo.
Compose + View System mixto: cuando usás AndroidView o ComposeView, Layout Inspector muestra ambos mundos en el mismo árbol.
Live updates en Compose
Para Jetpack Compose, Layout Inspector tiene una vista específica que muestra el árbol de composables con sus parámetros y el estado actual. Podés ver en tiempo real cómo cambia el árbol cuando el estado cambia — sin necesidad de agregar logs.
# Acceso rápido:
# Android Studio → Tools → Layout Inspector
# O: View → Tool Windows → Layout Inspector
# Requiere:
# - App corriendo en debug (dispositivo o emulador)
# - API 29+ para Live Updates en tiempo real
# - API 23-28 para snapshot estático
Memory Profiler — encontrar memory leaks
Los memory leaks en Android son silenciosos. La app no crashea de inmediato — simplemente usa cada vez más memoria hasta que el sistema la mata o el usuario nota que está lenta. El Memory Profiler es la herramienta para encontrarlos.
Abrilo desde View → Tool Windows → Profiler → Memory.
El flujo para detectar un leak
# 1. Iniciar el profiler con la app corriendo
# 2. Navegar al flujo sospechoso (abrir y cerrar una Activity varias veces)
# 3. Forzar un GC (botón del cubo de basura en el Profiler)
# 4. Tomar un heap dump (botón de descarga)
# 5. En el heap dump, filtrar por tu package name
# 6. Buscar instancias de clases que deberían haberse destruido
# (ej: si cerraste una Activity 5 veces, no debería haber 5 instancias)
Lo que buscás: múltiples instancias de una Activity/Fragment que ya fue cerrada. Si ves MainActivity x 4 después de navegar y volver 4 veces, tenés un leak.
Los leaks más comunes que vas a encontrar
// ❌ LEAK 1: binding no anulado en Fragment
class MiFragment : Fragment() {
private var _binding: FragmentMiBinding? = null
// Si olvidás esto en onDestroyView, la View queda referenciada:
override fun onDestroyView() {
super.onDestroyView()
_binding = null // OBLIGATORIO
}
}
// ❌ LEAK 2: listener de sistema no removido
class MiActivity : AppCompatActivity() {
private val sensorManager by lazy { getSystemService(SensorManager::class.java) }
override fun onResume() {
super.onResume()
sensorManager.registerListener(this, sensor, SENSOR_DELAY_NORMAL)
}
override fun onPause() {
super.onPause()
sensorManager.unregisterListener(this) // si olvidás esto → leak
}
}
// ❌ LEAK 3: coroutine que captura el Context
viewModelScope.launch {
val context = applicationContext // OK
// val context = activity // ❌ captura la Activity en el ViewModel
}
LeakCanary como complemento
Para detección automática en desarrollo, LeakCanary es el complemento perfecto al Memory Profiler. Agrega debugImplementation("com.squareup.leakcanary:leakcanary-android:2.14") y recibís una notificación con el stack trace exacto del leak cuando ocurre, sin necesidad de abrir el Profiler.
CPU Profiler — encontrar jank y código lento
Cuando la app se traba o el scroll no es fluido, el CPU Profiler te muestra exactamente qué código está tardando.
En Profiler → CPU, iniciá una grabación de System Trace (la más útil para UI), reproducí el problema, y detené la grabación. El resultado muestra los frames dibujados, cuánto tardó cada uno, y en qué código se gastó el tiempo.
# Los tres modos de grabación:
# System Trace — overhead mínimo, ideal para UI y jank. USALO PARA SCROLL.
# Java/Kotlin Method Trace — muestra cada llamada de método. Más overhead.
# Callstack Sample — sampling periódico, buen balance. Para código general.
Lo que vas a ver cuando hay jank: frames que superan los 16ms aparecen en rojo en el timeline. Hacés click en uno y ves exactamente qué método del hilo principal tardó demasiado — generalmente una query a la base de datos, un decode de imagen, o un onBindViewHolder con lógica pesada.
// Caso real: onBindViewHolder tardando más de 16ms
// El Profiler muestra: MiAdapter.onBindViewHolder → Glide.load → ...
// Diagnóstico: estás cargando imágenes síncronamente en el bind
// ❌ MAL — decode de bitmap en el hilo principal
override fun onBindViewHolder(holder: MiViewHolder, position: Int) {
val bitmap = BitmapFactory.decodeFile(item.imagePath) // I/O en main thread!
holder.imageView.setImageBitmap(bitmap)
}
// ✓ BIEN — Glide lo hace en background
override fun onBindViewHolder(holder: MiViewHolder, position: Int) {
Glide.with(holder.itemView).load(item.imagePath).into(holder.imageView)
}
Network Inspector — ver las llamadas HTTP en vivo
Network Inspector muestra todas las llamadas de red de tu app en tiempo real: URL, método, headers, cuerpo del request, cuerpo de la respuesta, tiempo de respuesta, tamaño. Sin configurar nada, sin Charles Proxy, sin interceptores.
Abrilo desde App Inspection → Network Inspector.
Lo que resuelve sin configuración extra
- La API devuelve datos diferentes a los esperados: ves el JSON crudo tal como llegó. Comparás con lo que tu modelo parseó. El error se hace obvio.
- Una llamada se hace N veces cuando debería hacerse una: el inspector muestra todas las llamadas en orden cronológico. Si ves la misma URL repetida, tenés un bug de arquitectura.
- Headers de autenticación incorrectos: expandís el request y ves exactamente qué headers mandaste. Si el Authorization header está mal formateado, lo ves al instante.
- Respuesta lenta: cada llamada muestra el tiempo exacto. Identificás cuál endpoint está tardando.
# Requiere:
# - OkHttp como cliente HTTP (Retrofit lo usa por default)
# - App corriendo en debug en API 26+ con Android Studio 4.0+
# - NO requiere logging interceptor ni configuración adicional
Network Inspector vs logging interceptorPara debugging durante el desarrollo, Network Inspector es más cómodo que un logging interceptor porque no contamina el Logcat y tiene una UI dedicada con formato legible. El interceptor sigue siendo útil para logging estructurado en staging o para ambientes donde no tenés Android Studio conectado.
Database Inspector — ver y editar Room en vivo
Database Inspector es la herramienta que más tiempo ahorra cuando debuggeás problemas de persistencia. Muestra las tablas de tu base de datos Room en tiempo real, mientras la app corre.
Abrilo desde App Inspection → Database Inspector.
Lo que podés hacer
- Ver el contenido de cada tabla en tiempo real. Si tu app insertó datos y no aparecen en la UI, verificás inmediatamente si el problema es en el insert (Room) o en la observación (Flow/ViewModel).
- Ejecutar queries SQL directamente contra la base de datos en vivo. Debuggeás una query compleja sin necesidad de agregar un botón de debug en la UI.
- Modificar datos mientras la app corre. Podés insertar, actualizar o eliminar filas y ver cómo reacciona tu UI — sin escribir código de seeding.
- Ver las migraciones aplicadas y verificar que el schema es el esperado.
# Caso real que resuelve en 2 minutos:
# "Guardé el dato pero no aparece en la lista"
# Sin Database Inspector:
# 1. Agregar un log en el DAO
# 2. Agregar un log en el repositorio
# 3. Agregar un log en el ViewModel
# 4. Correr la app, reproducir el bug, analizar los logs
# → 15 minutos mínimo
# Con Database Inspector:
# 1. Abrir la tabla en el inspector
# 2. Ejecutar la acción que debería guardar el dato
# 3. Ver si aparece en la tabla
# → 30 segundos, y sabés exactamente en qué capa está el bug
App Inspection — WorkManager y más
App Inspection es el panel que agrupa varios inspectores: Database, Network, y también Background Task Inspector para WorkManager.
El Background Task Inspector muestra todos los workers de WorkManager encolados, su estado actual (ENQUEUED, RUNNING, SUCCEEDED, FAILED), los datos de entrada y salida, las constraints y cuándo está programado para correr. Es la herramienta correcta cuando tus workers no se ejecutan cuando esperás.
# En Background Task Inspector podés:
# - Ver todos los workers activos y su estado
# - Cancelar un worker manualmente
# - Ver el historial de ejecuciones
# - Inspeccionar los datos de input/output de cada worker
# - Verificar si las constraints están bloqueando la ejecución
Logcat avanzado — filtros que no sabías que existían
La mayoría usa Logcat como un grep básico por tag. Tiene mucho más.
# Filtros por package — el más útil:
# En el dropdown de Logcat, seleccioná tu app
# Solo ves los logs de tu proceso, no el ruido del sistema
# Filtros combinados en la barra de búsqueda:
tag:MiViewModel # solo logs con ese tag
level:ERROR # solo errores
package:ar.pensa.miapp # logs de tu package
# Combinar:
tag:MiViewModel level:DEBUG
# Key=value format (Android Studio Hedgehog+):
tag:Retrofit & level:DEBUG
# Regex en la búsqueda:
# Activar con el ícono de regex (⊕) en la barra de búsqueda
tag:Mi(View|Frag).* # tag que matchea MiView* o MiFrag*
Structured logging para facilitar el filtrado
// En lugar de strings arbitrarios, usar tags consistentes
// hace el filtrado en Logcat mucho más útil
object Log {
private const val TAG_NETWORK = "Network"
private const val TAG_DB = "Database"
private const val TAG_NAV = "Navigation"
fun network(msg: String) = android.util.Log.d(TAG_NETWORK, msg)
fun db(msg: String) = android.util.Log.d(TAG_DB, msg)
fun nav(msg: String) = android.util.Log.d(TAG_NAV, msg)
}
// Uso:
Log.network("GET /productos → 200 OK (342ms)")
Log.nav("ProductosFragment → DetalleFragment (id=42)")
Tricks de adb que ahorran tiempo
El debugger de Android Studio es poderoso, pero hay operaciones que desde la terminal son más rápidas.
# Tomar un screenshot directamente al disco:
adb exec-out screencap -p > screenshot.png
# Grabar la pantalla (máx 3 min):
adb shell screenrecord /sdcard/video.mp4
# Ctrl+C para detener
adb pull /sdcard/video.mp4
# Simular rotación de pantalla:
adb shell settings put system user_rotation 1 # landscape
adb shell settings put system user_rotation 0 # portrait
# Simular modo avión:
adb shell svc wifi disable
adb shell svc wifi enable
# Simular batería baja (para testear WorkManager constraints):
adb shell dumpsys battery set level 5
adb shell dumpsys battery reset
# Forzar dark mode:
adb shell cmd uimode night yes
adb shell cmd uimode night no
# Ver todos los permisos de tu app:
adb shell dumpsys package ar.pensa.miapp | grep permission
# Instalar APK sin desinstalar (conserva datos):
adb install -r app-debug.apk
# Limpiar datos de la app (equivalente a "limpiar datos" en configuración):
adb shell pm clear ar.pensa.miapp
# Simular process death (ver lección de background):
# 1. App en background (presionar Home)
adb shell am kill ar.pensa.miapp
# 2. Volver a la app desde el recents
# Input de texto desde la terminal (útil para formularios en tests):
adb shell input text "[email protected]"
# Tap en coordenadas:
adb shell input tap 540 960
# Swipe:
adb shell input swipe 540 1500 540 500 300 # de abajo hacia arriba, 300ms
adb logcat con filtros desde la terminal
# Filtrar por tag y nivel:
adb logcat MiTag:D *:S
# MiTag:D → mostrar MiTag con nivel DEBUG o mayor
# *:S → silenciar todo lo demás (SILENT)
# Filtrar por package (más útil):
adb logcat --pid=$(adb shell pidof ar.pensa.miapp)
# Guardar logs a un archivo:
adb logcat -d > logs.txt # -d: dump y salir
# Limpiar el buffer de logcat:
adb logcat -c
# Ver solo crashes:
adb logcat AndroidRuntime:E *:S
El debugger de Android Studio — breakpoints que no sabías que existían
# Breakpoint condicional:
# Click derecho en el breakpoint → Condition
# if (lista.size > 10) → solo rompe si la lista tiene más de 10 elementos
# Evita tener que presionar Resume decenas de veces
# Log breakpoint (non-suspending):
# Click derecho → Evaluate and log
# Imprime una expresión en Logcat SIN detener la ejecución
# Ideal cuando "si pongo un breakpoint cambia el comportamiento"
# Exception breakpoint:
# Run → View Breakpoints → + → Exception Breakpoint
# Rompe cuando se lanza una excepción específica, incluso si está en código de librería
# Muy útil para encontrar de dónde viene un crash difícil de reproducir
# Watch expressions:
# En la ventana del debugger → + en Watches
# Evaluás expresiones complejas en el contexto del breakpoint
# Ej: "viewModel.uiState.value.productos.size"
La combinación correcta según el problema
# "La UI no muestra lo que espero"
# → Layout Inspector primero (¿el problema es de layout?)
# → Database Inspector si es un problema de datos
# → Network Inspector si los datos vienen de la red
# "La app está lenta"
# → CPU Profiler con System Trace (¿jank en el hilo principal?)
# → Memory Profiler (¿leak de memoria?)
# "Un dato no llega o llega mal"
# → Network Inspector (¿la respuesta de la API es correcta?)
# → Database Inspector (¿se guardó correctamente en Room?)
# → Logcat con filtros (¿el mapper transformó bien los datos?)
# "El background task no se ejecuta"
# → App Inspection → Background Task Inspector
# → adb shell am set-standby-bucket ... para forzar el bucket correcto
# "Crash en producción que no puedo reproducir"
# → Crashlytics + mapping.txt para el stack trace completo
# → Exception breakpoint en Android Studio para reproducir localmente
# → adb logcat AndroidRuntime:E para ver el crash en el emulador