Jank es un término que se refiere a tirones notables o “tartamudeos” en la animación de la interfaz causados por la pérdida de fotogramas individuales. En aplicaciones móviles, Jank ocurre cuando el tiempo de renderizado del fotograma supera el presupuesto asignado por la frecuencia de actualización de la pantalla. Según Android Developers, 2025, Jank es la causa principal de la sensación subjetiva de “lentitud”: una aplicación puede ser funcionalmente perfecta, pero el usuario la percibe como lenta debido a un FPS inestable.
Puntos clave
Jank es un término del ámbito de los gráficos por computadora que designa un defecto visual en el que la animación se mueve a trompicones en lugar de deslizarse suavemente. En el desarrollo móvil, Jank se mide como la cantidad de fotogramas perdidos (skipped frames) por unidad de tiempo. Si el sistema no logra preparar un fotograma para el momento del VSync, la pantalla repite el fotograma anterior: se produce una pausa de 16.6 ms a 60 Hz. Un solo fotograma perdido puede pasar desapercibido, pero una serie de 3 a 5 fotogramas perdidos consecutivos crea una sensación de “tartamudeo” de 50 a 80 ms que el usuario percibe claramente.
Jank es especialmente crítico para animaciones que deben funcionar a velocidad constante: desplazamiento de feeds, animación de apertura de menús, efectos de paralaje, transiciones entre pantallas. Según un estudio de UX de Google (2024), una aplicación con un índice de Jank superior al 3% de las sesiones de desplazamiento recibe un 22% más de reseñas de una estrella que una aplicación con un índice inferior al 0.5%. La herramienta Android Vitals rastrea automáticamente Jank y lo clasifica por gravedad: moderado, severo y crítico.
Las causas de Jank se dividen en varias categorías. La primera es Layout Jank: causada por llamadas frecuentes a requestLayout() debido a cambios en el tamaño de las Views, animaciones LayoutTransition o carga dinámica de contenido. Cada llamada a requestLayout desencadena Measure + Layout para todo el subárbol de Views, lo que puede ocupar de 5 a 30 ms. La segunda es Draw Jank: relacionada con el overdraw y el uso de drawables pesados. La tercera es Thread Jank: bloqueo del hilo principal debido a operaciones síncronas: carga de archivos, trabajo con la BD en el hilo principal, decodificación de Bitmap.
La cuarta categoría es GC Jank: recolección de basura (Garbage Collection) en ART/Dalvik o Swift ARC. Cuando se acumulan muchos objetos en el montón, el GC inicia una pausa Stop-The-World de 5 a 15 ms. En Android, las pausas de GC ocurren con mayor frecuencia durante asignaciones frecuentes en bucles: creación de objetos en onDraw(), asignación en adaptadores, expresiones lambda no utilizadas. La quinta es IPC Jank: comunicación entre procesos (ContentProvider, Binder) en el hilo principal. La sexta es Rendering Jank: renderizado lento de la GPU debido a sombreadores no óptimos o texturas de gran tamaño.
| Tipo de Jank | Causa | Duración típica | Herramienta de detección |
|---|---|---|---|
| Layout | requestLayout, relayout | 5–30 ms | Perfetto, Systrace |
| Draw | Overdraw, drawables pesados | 3–20 ms | GPU Profiling |
| Thread | Bloqueo del hilo principal | 10–200 ms | Android Studio Profiler |
| GC | Garbage Collection | 5–15 ms | Memory Profiler |
| Rendering | Carga de GPU | 10–50 ms | GPU Tracer, Xcode GPU |
En Android, el diagnóstico de Jank comienza con el trazado del sistema Perfetto. Perfetto registra la actividad de todos los hilos, la CPU, la GPU y el planificador. Un indicador evidente de Jank son las líneas Choreographer.doFrame y Choreographer.doCallbacks: si el intervalo entre dos llamadas consecutivas a doFrame supera los 16.6 ms, se ha perdido un fotograma. Perfetto muestra la causa exacta: qué system call, lock o GC provocó el retraso. En Android Studio Profiler, la funcionalidad equivalente está disponible a través de CPU Profiler.
Para la detección automática de Jank en producción se utiliza FrameMetricsAggregator, una API que recopila estadísticas de cada fotograma y las agrega por sesión. En Android 12+ apareció PerformanceHintManager, una API para indicar al sistema la frecuencia de fotogramas objetivo. Si la aplicación indica que funciona en un escenario de 120 FPS, el sistema puede aumentar la frecuencia de CPU/GPU para prevenir Jank. Para un registro simple de todos los fotogramas perdidos, basta con suscribirse a Choreographer.FrameCallback.
El código en Kotlin se suscribe a Choreographer.FrameCallback y registra cada fotograma perdido indicando la duración del retraso. La devolución de llamada se invoca en cada VSync.
class JankDetector {
private val frameBudget = 16_666_666L
private var previousFrameTime = 0L
private val callback =
Choreographer.FrameCallback { currentTime ->
if (previousFrameTime != 0L) {
val frameDuration =
currentTime - previousFrameTime
val skippedFrames =
(frameDuration / frameBudget) - 1
if (skippedFrames > 0) {
Log.w("Jank",
"Skipped $skippedFrames frames")
}
}
previousFrameTime = currentTime
Choreographer.getInstance()
.postFrameCallback(this)
}
fun start() {
Choreographer.getInstance()
.postFrameCallback(callback)
}
}
En iOS, el diagnóstico de Jank se realiza mediante Instruments con la plantilla Core Animation. Instruments muestra el FPS en tiempo real, la cantidad de renderizados fuera de pantalla y las pruebas de impacto. Los principales indicadores de Jank en iOS: barras rojas en la escala de tiempo de Core Animation (superación del presupuesto del fotograma), un valor alto de Renderer (indica renderizado fuera de pantalla) y un FPS bajo. Para la monitorización en producción, MetricKit recopila informes con la métrica MXAnimatoryMetric, que incluye el FPS medio, el tiempo de fotograma P50 y P95.
El diagnóstico nativo de Jank en iOS incluye CADisplayLink con comprobación de timestamp y targetTimestamp. Si el timestamp actual está significativamente por detrás del targetTimestamp, significa que se ha perdido uno o varios fotogramas. Apple también recomienda usar os_signpost para el perfilado personalizado: colocar un signpost-interval al inicio y al final del renderizado del fotograma y observar en Instruments qué intervalos superan los 16.6 ms. En SwiftUI, para el diagnóstico de Jank se utiliza UIView.invalidateIntrinsicContentSize: las llamadas frecuentes a este método indican un Layout inestable.
El código en Swift detecta fotogramas perdidos mediante CADisplayLink. Si la diferencia entre timestamp y targetTimestamp supera los 16.6 ms, se registra un Jank.
class JankMonitor {
private var displayLink: CADisplayLink?
private var totalJank = 0
func start() {
displayLink = CADisplayLink(
target: self,
selector: #selector(detectJank)
)
displayLink?.add(to: .current,
forMode: .common)
}
@objc
private func detectJank() {
guard let link = displayLink else { return }
let delay = link.targetTimestamp
- link.timestamp
if delay > 0.0167 {
totalJank += 1
}
}
}
Para el perfilado de Jank se utilizan tanto herramientas integradas del sistema operativo como SDK de terceros. En Android, la herramienta clave es Perfetto (que sustituyó a Systrace). Perfetto permite grabar trazas de hasta 30 segundos y analizarlas a través de la interfaz web ui.perfetto.dev. Muestra una escala de tiempo precisa con la actividad de Choreographer, los hilos de renderizado (RenderThread) y la GPU. Para el análisis detallado de problemas con la GPU se utiliza AGI (Android GPU Inspector), que muestra no solo el tiempo del fotograma sino también la carga de bloques específicos de la GPU: sombreadores, rasterizador, unidad de texturas.
En iOS, el equivalente es Instruments con las plantillas Core Animation, Metal System Trace y GPU Driver. Core Animation muestra el FPS y el tiempo del fotograma, Metal System Trace muestra el trabajo de la GPU con detalle hasta cada draw call. Para el perfilado en dispositivos reales bajo carga se utilizan Firebase Performance (recopila la métrica Screen Rendering) y Sentry (captura el stack trace en caso de Jank). La nueva API Android 15 Performance Hint permite al desarrollador indicar al sistema qué fotogramas son importantes y recibir advertencias del sistema cuando se aproxima un Jank.
El código en Kotlin utiliza FrameMetricsAggregator para recopilar estadísticas de fotogramas durante una sesión. Tras detener el agregador, se muestra la cantidad de fotogramas perdidos.
class JankAggregator(private val activity: Activity) {
private val aggregator = FrameMetricsAggregator()
fun startCollection() {
aggregator.add(activity.window)
}
fun stopAndReport() {
aggregator.remove()
val result = aggregator.getMetrics()
val totalFrames = result
?.get(FrameMetrics.TOTAL_DURATION)
?.size ?: 0
val jankFrames = result
?.get(FrameMetrics.TOTAL_DURATION)
?.count { it > 16_666_666L} ?: 0
Log.d("JankReport",
"Jank ratio: \${jankFrames * 100 / totalFrames}%")
}
}
La eliminación de Jank requiere una combinación de técnicas según su tipo. Para Layout Jank: sustituir jerarquías profundas por ConstraintLayout/Compose/SwiftUI, usar etiquetas merge, evitar requestLayout en animaciones. Para Draw Jank: usar Debug GPU Overdraw para localizar overdraw 4x+, sustituir drawables pesados por vectores (VectorDrawable/PDF), usar hardware layers con precaución: aceleran el renderizado pero consumen más memoria de GPU. Para Thread Jank: trasladar todas las operaciones de E/S, trabajo con BD y decodificación de Bitmap a hilos en segundo plano, usar Kotlin Coroutines con el Dispatcher adecuado o RxJava con Schedulers.io().
Para GC Jank: minimizar las asignaciones en onDraw() y getView(), usar pools de objetos (ObjectPool), sustituir for-each por for indexado, usar data class inmutables en Kotlin con copy() con cuidado: copy crea un nuevo objeto. Para IPC Jank: inicializar ContentProvider de forma diferida mediante App Startup, trasladar las llamadas Binder a un hilo en segundo plano. Para Rendering Jank: reducir el tamaño de las texturas a la resolución máxima de la pantalla, usar compresión ASTC o ETC2, evitar compilaciones excesivas de shaders (compilar los shaders con antelación). Una solución integral es la ejecución regular de perfiles Perfetto/Instruments en CI y el seguimiento de regresiones de Jank.
El código en Kotlin demuestra la carga asíncrona de datos en pantalla después de reportFullyDrawn, para que el trabajo pesado no bloquee el primer fotograma. La devolución de llamada se invoca después de que el usuario vea la interfaz.
class JankSafeLoader {
suspend fun loadAfterFirstFrame(
activity: Activity
) {
// garantizar que el primer fotograma ya está renderizado
if (Build.VERSION.SDK_INT >= 29) {
activity.reportFullyDrawn()
}
// carga pesada — después del primer fotograma
withContext(Dispatchers.IO) {
val data = fetchHeavyData()
withContext(Dispatchers.Main) {
updateUI(data)
}
}
}
}
Preguntas frecuentes
Jank son fotogramas de renderizado perdidos que se manifiestan como tirones o sacudidas notables en la animación. Ocurre cuando el tiempo de preparación del fotograma supera el presupuesto de tiempo (16.6 ms para 60 FPS).
Layout Jank (requestLayout frecuente), Draw Jank (overdraw), Thread Jank (bloqueo del hilo principal), GC Jank (recolección de basura), IPC Jank (llamadas Binder) y Rendering Jank (sombreadores pesados).
Use Perfetto para el trazado del sistema, GPU Profiling para el análisis de las fases del fotograma y FrameMetricsAggregator para la monitorización en producción. En Android Studio — CPU Profiler con Deep Java Trace.
Mediante Instruments con la plantilla Core Animation o Metal System Trace. Para producción — MetricKit con MXAnimatoryMetric. Programáticamente — CADisplayLink comprobando la diferencia entre timestamp y targetTimestamp.
Según Google, un índice de Jank superior al 3% de las sesiones de desplazamiento (3 de cada 100 desplazamientos contienen un tirón) provoca un aumento del 22% en las reseñas negativas. El índice objetivo es inferior al 0.5% de las sesiones de desplazamiento.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también