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
Painto un nuevoPathcada 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
DrawScopeestándares (comodrawRectydrawCircle) ya reutilizan objetosPaintde 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.rewindpara borrar unPathexistente en lugar de crear una instancia de unPathnuevo. - 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 unModifier.Nodepersonalizado conDrawModifierNodepara asignar y reutilizar los objetos sin generar nuevas asignaciones de montón.
- Usa métodos estándares: Los métodos
- 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.alphao 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, estableceCompositingStrategy.ModulateAlpha. Para las llamadas de dibujo individuales, aplica el canal alfa directamente al comando de dibujo (como concolor = Color.Red.copy(alpha = 0.5f)) sin crear una capa.