Aceleración de hardware

La canalización de procesamiento en 2D de Android admite la aceleración de hardware, lo que significa que todas las operaciones de dibujo que se realizan en el lienzo usan la GPU. Debido al aumento de los recursos necesarios para habilitar la aceleración de hardware, tu app consumirá más RAM.

La aceleración de hardware está habilitada de forma predeterminada. Si tu aplicación usa solo elementos componibles estándar, no deberías ver efectos de dibujo adversos si la activas globalmente. Sin embargo, como la aceleración de hardware no es compatible con todas las operaciones de dibujo en 2D, si la activas, es posible que se vean afectadas algunas de tus llamadas a dibujos personalizadas. Los problemas se suelen manifestar como elementos invisibles, excepciones o píxeles renderizados de manera incorrecta. Para solucionar el problema, Android te ofrece la opción de habilitar o inhabilitar la aceleración de hardware a diferentes niveles. Consulta Cómo controlar la aceleración de hardware.

Si tu aplicación realiza operaciones de dibujo personalizadas, pruébala en dispositivos de hardware físicos con la aceleración de hardware activada para detectar posibles errores. En la sección Compatibilidad con operaciones de dibujo, se describen los problemas conocidos relacionados con la aceleración de hardware y se explica cómo solucionarlos.

Consulta también OpenGL con las APIs de Framework.

Cómo controlar la aceleración de hardware

Puedes controlar la aceleración de hardware en estos niveles:

  • Aplicación
  • Actividad
  • Ventana
  • Componible

Nivel de aplicación

En tu archivo de manifiesto de Android, agrega el siguiente atributo a la etiqueta <application> para habilitar la aceleración de hardware en toda la aplicación:

<application android:hardwareAccelerated="true" ...>

Nivel de actividad

Si tu aplicación no se comporta de manera adecuada con la aceleración de hardware activada globalmente, también puedes controlarla para actividades individuales. Si quieres habilitar o inhabilitar la aceleración de hardware a nivel de la actividad, puedes usar el atributo android:hardwareAccelerated para el elemento <activity>. En el siguiente ejemplo, se habilita la aceleración de hardware para toda la aplicación, pero se inhabilita para una actividad:

<application android:hardwareAccelerated="true">
    <activity ... />
    <activity android:hardwareAccelerated="false" />
</application>

Nivel de ventana

Si necesitas un control aún más detallado, puedes habilitar la aceleración de hardware para una ventana específica con el siguiente código:

window.setFlags(
        WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
        WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
)

Nivel componible

En Compose, no hay un interruptor por elemento componible para inhabilitar la aceleración de hardware.

Para renderizar un elemento componible en su propia capa, usa Modifier.graphicsLayer. Esto permite que las propiedades de transformación (como alpha, scaleX, scaleY, translationX, translationY, rotationX, rotationY, rotationZ y transformOrigin) cambien sin volver a ejecutar el código de dibujo del elemento componible. Para obtener el mejor rendimiento, siempre usa la forma lambda del modificador para establecer estas propiedades.

Para forzar explícitamente un búfer fuera de la pantalla para operaciones de dibujo avanzadas, como la combinación personalizada dentro de la capa, usa CompositingStrategy.Offscreen. Para obtener más información, consulta Modificadores de gráficos.

Si tienes una operación de dibujo personalizada que requiere estrictamente la renderización por software, puedes alojar una View heredada con AndroidView y llamar a setLayerType(View.LAYER_TYPE_SOFTWARE, null) en esa vista.

Compatibilidad con operaciones de dibujo

Cuando está acelerada por hardware, la canalización de procesamiento en 2D admite las operaciones de dibujo de Canvas más populares y muchas otras no tan comunes. Se admiten todas las operaciones de dibujo que se utilizan para renderizar aplicaciones incluidas en Android, elementos componibles estándar y efectos visuales avanzados comunes, como reflejos y texturas de mosaico.

En la siguiente tabla, se describe el nivel de compatibilidad de varias operaciones en los diferentes niveles de API:

Primer nivel de API admitido
Lienzo
drawBitmapMesh() (array de colores) 18
drawPicture() 23
drawPosText() 16
drawTextOnPath() 16
drawVertices() 29
setDrawFilter() 16
clipPath() 18
clipRegion() 18
clipRect(Region.Op.XOR) 18
clipRect(Region.Op.Difference) 18
clipRect(Region.Op.ReverseDifference) 18
clipRect() con rotación/perspectiva 18
Paint
setAntiAlias() (para texto) 18
setAntiAlias() (para líneas) 16
setFilterBitmap() 17
setLinearText()
setMaskFilter()
setPathEffect() (para líneas) 28
setShadowLayer() (para elementos que no sean texto) 28
setStrokeCap() (para líneas) 18
setStrokeCap() (para puntos) 19
setSubpixelText() 28
Xfermode
PorterDuff.Mode.DARKEN (búfer de fotogramas) 28
PorterDuff.Mode.LIGHTEN (búfer de fotogramas) 28
PorterDuff.Mode.OVERLAY (búfer de fotogramas) 28
Sombreador
ComposeShader dentro de ComposeShader 28
Sombreadores del mismo tipo dentro de ComposeShader 28
Matriz local en ComposeShader 18

Ajuste de lienzo

La canalización de procesamiento en 2D acelerada por hardware se compiló primero para admitir dibujos sin ajustar, con algunas operaciones de dibujo que disminuyen la calidad de forma significativa a valores de escala más altos. Estas operaciones se implementan como texturas dibujadas a escala 1.0 y transformadas por la GPU. A partir del nivel de API 28, todas las operaciones de dibujo pueden ajustar la escala sin problemas.

En la siguiente tabla, se muestra cuándo se modificó la implementación para manejar correctamente las escalas grandes:

Operación de dibujo que se va a ajustar Primer nivel de API admitido
drawText() 18
drawPosText() 28
drawTextOnPath() 28
Formas simples 17
Formas complejas 28
drawPath() 28
Capa de sombra 28

Si una operación de dibujo de la que dependes no está acelerada por hardware, renderiza el dibujo afectado en un Bitmap (o ImageBitmap) de software fuera de la pantalla y dibuja el resultado. El resto de la IU mantiene la ruta acelerada por hardware.

Sugerencias y trucos

Si cambias a gráficos 2D acelerados por hardware, puedes aumentar el rendimiento de forma instantánea, pero debes diseñar tu aplicación de manera que use la GPU con eficacia. Para ello, sigue estas recomendaciones:

Minimiza la complejidad del diseño y la recomposición
Mantén el árbol de diseño poco profundo y limita la cantidad de recomposiciones. Aplazar las lecturas de estado al alcance más estrecho, de modo que un cambio vuelva a dibujar la región más pequeña posible Por ejemplo, lee el estado animado dentro de Modifier.graphicsLayer { } en lugar de en el cuerpo de un elemento componible. Para obtener más información, consulta Rendimiento de Jetpack Compose.
Evita las superposiciones
No dibujes demasiadas capas una encima de la otra. Quita los elementos de la IU que estén completamente oscurecidos por otros elementos opacos encima de ellos. Si necesitas dibujar varias capas combinadas una encima de la otra, procura fusionarlas en una sola capa. Una buena regla general con el hardware actual es no dibujar más de 2.5 veces el número de píxeles en pantalla por fotograma (los píxeles transparentes en un mapa de bits cuentan).
No crees objetos de procesamiento en métodos de dibujo
Un error común es crear una nueva Paint o un nuevo Path cada vez que se invoca un método de dibujo. Esto obliga al recolector de elementos no utilizados a ejecutarse con más frecuencia y también omite las cachés y las optimizaciones en la canalización de hardware. Para evitar esto, reutiliza y muta tus objetos:
  • Usa métodos estándares: Los métodos DrawScope estándares (como drawRect y drawCircle) ya reutilizan objetos Paint de forma interna sin necesidad de que el desarrollador realice la asignación.
  • Realiza mutaciones en lugar de reasignar: Cuando escribas lógica personalizada, usa path.rewind para borrar un Path existente en lugar de crear una instancia de un Path nuevo.
  • Mantén el estado de manera eficiente: Dentro de un elemento componible, asigna objetos una vez con remember { Path() }. Si compilas extensiones de modificadores personalizadas reutilizables, implementa un Modifier.Node personalizado con DrawModifierNode para asignar y reutilizar los objetos sin generar nuevas asignaciones de montón.
No modifiques las formas con demasiada frecuencia
Las formas complejas, las rutas y los círculos, por ejemplo, se renderizan con máscaras de textura. Cada vez que creas o modificas una ruta, la canalización de hardware crea una máscara nueva, lo cual puede ser costoso.
No modifiques los mapas de bits con demasiada frecuencia
Cada vez que cambias el contenido de un mapa de bits, se vuelve a cargar como una textura de GPU la próxima vez que lo dibujas.
Usa alfa con cuidado
Cuando haces que un elemento componible sea translúcido con Modifier.alpha o las APIs de animación de Compose, por lo general, se renderiza en un búfer fuera de la pantalla, lo que duplica la tasa de relleno requerida. Para evitar la sobrecarga del búfer fuera de la pantalla en el caso de contenido que no se superpone, establece CompositingStrategy.ModulateAlpha. Para las llamadas de dibujo individuales, aplica el canal alfa directamente al comando de dibujo (como con color = Color.Red.copy(alpha = 0.5f)) sin crear una capa.

Recursos adicionales

Mira contenido