¿Qué es el 'jank' y cómo se razona sobre un presupuesto de frame de 16ms (u 8ms)?
Un frame perdido no es un crash y no aparece en un stack trace — es un presupuesto que cumplís o incumplís de forma visible, sesenta (o noventa, o ciento veinte) veces por segundo.
APRENDE HACIENDOPon a prueba tu intuición3 pasos
¿Qué es el 'jank' y cómo se razona sobre un presupuesto de frame de 16ms (u 8ms)?
Usa los ejemplos y la explicación de la lección para comprobar tu idea.
Reflexión personal. No se envía a ningún servicio de IA.#Qué significa "jank" precisamente
El jank es el tartamudeo visible para el usuario que ocurre cuando el sistema no logra producir un nuevo frame a tiempo para el siguiente refresco de la pantalla, así que la pantalla muestra otra vez el frame anterior (o uno incompleto) en lugar del siguiente frame previsto. No es un bug único con una causa única —es el síntoma de cualquier trabajo que exceda el presupuesto de tiempo del frame, y ese presupuesto lo define la tasa de refresco de la pantalla: una pantalla de 60Hz se refresca cada ~16.7ms, una de 90Hz cada ~11.1ms, y una de 120Hz —ya común en teléfonos Android de gama alta y media-alta, y en el ProMotion del iPhone Pro/iPad Pro— cada ~8.3ms. El número que la gente cita como "el presupuesto de frame" no es una constante fija; es el intervalo de refresco de la pantalla objetivo, y la misma lógica de app que entra cómoda en 16ms puede janquear de forma visible en una pantalla de 120Hz donde su presupuesto acaba de reducirse a la mitad.
Crucialmente, no todo el presupuesto es tuyo. Los 16ms (u 8ms) tienen que cubrir el manejo de input, el trabajo de layout/measure y dibujo de tu app, el compositor del sistema operativo haciendo su propio trabajo, y la entrega a la GPU —el código real de tu app tiene que entrar en lo que quede después del overhead propio de la plataforma, que es una razón por la cual una pantalla que "se ve simple" puede igual janquear: el overhead fijo es una fracción más grande de un presupuesto más chico a tasas de refresco más altas.
#Qué hay dentro del trabajo de un frame
Esa es una división ilustrativa, no una especificación fija —las proporciones reales cambian según qué esté haciendo el frame (un scroll se apoya en layout/draw, una animación se apoya en la lógica de app impulsando cambios de propiedades, una lista con imágenes se apoya en decode/draw). El punto de dibujarlo como línea de tiempo es la conclusión en sí misma: cada una de esas etapas compite por los mismos 16ms, y una regresión en cualquier punto de la cadena —una llamada síncrona en el hilo principal, un pase de layout más profundo de lo necesario, un onDraw/pintado personalizado innecesariamente costoso— empuja el total más allá del deadline y todo el frame llega tarde, no solo la etapa que regresionó.
#Por qué "simplemente optimizar todo un poco" es el modelo mental equivocado
El presupuesto de frame reformula el trabajo de performance de "hacer el código rápido" a "hacer que esta etapa termine antes del deadline, usando lo que realmente quede de presupuesto después de las otras etapas". Eso tiene implicaciones reales:
- Una regresión de 2ms es invisible en aislamiento y fatal en conjunto. Si cuatro pantallas distintas agregan cada una, de forma independiente, "solo 2ms" de complejidad de layout a lo largo de varios sprints, ninguna cruza individualmente el umbral en revisión, pero el total acumulado en la pantalla más exigida eventualmente sí lo cruza —por eso el seguimiento de regresiones de tiempo de frame a lo largo del tiempo importa más que una medición puntual.
- El cuello de botella se mueve. Arreglar tu etapa más lenta no garantiza un frame más rápido si otra etapa ya estaba cerca de agotar su parte del presupuesto —simplemente significa que la siguiente etapa más lenta se convierte en el nuevo techo. Este es razonamiento estándar de cuellos de botella, pero es fácil olvidarlo en medio de una optimización y declarar victoria tras arreglar lo primero que encontraste.
- La consistencia le gana a la velocidad bruta. Un frame que confiablemente tarda 15ms cada vez se percibe fluido; una secuencia que alterna entre 10ms y 20ms —mismo promedio— se percibe visiblemente entrecortada, porque la percepción humana es sensible a la varianza en el movimiento, no solo al promedio. Por esto las herramientas reportan percentiles (tiempo de frame p50/p90/p99, o conteos de "frames con jank") en lugar de un tiempo de frame promedio.
#Cómo lo medís y razonás realmente en la práctica
Ambas plataformas exponen esto directamente en lugar de obligarte a inferirlo a partir de promedios de FPS, que esconden justamente la varianza que importa. El Perfetto/GPU Rendering Profile de Android (las barras "Profile GPU Rendering" en Opciones de Desarrollador, o el trace más detallado de Perfetto) descompone un solo frame por etapa —manejo de input, animación, measure/layout, draw, sync y upload, emisión de comandos— así podés ver cuál etapa del pipeline del diagrama de arriba es la que realmente está agotando el presupuesto en una pantalla dada, en lugar de adivinar. El Core Animation / Time Profiler de Instruments en iOS reporta de manera similar el tiempo por frame y resalta los hitches directamente contra el intervalo de refresco real de la pantalla, ajustando correctamente la "línea de presupuesto" para una pantalla ProMotion de 120Hz frente a una de 60Hz.
El encuadre de nivel senior para llevar a una entrevista: el jank es un problema de scheduling contra un deadline duro definido por la pantalla, no un vago problema de "la app está lenta", y cada técnica de optimización que ya conocés —evitar I/O en el hilo principal, reducir overdraw, aplanar jerarquías de vistas, memoizar recomposición costosa— es en realidad solo una forma de achicar una etapa de esta misma carrera recurrente, por frame, contra el reloj.