Aceleración de hardware

A partir de Android 3.0 (nivel de API 11), la canalización de procesamiento en 2D de Android admite la aceleración de hardware, es decir, todas las operaciones de dibujo que se realizan en el lienzo de una View usan la GPU. Debido al aumento de los recursos necesarios para habilitar la aceleración de hardware, tu app consumirá más RAM.

Si tu nivel de API objetivo es >= 14, la aceleración de hardware está habilitada de forma predeterminada, aunque también se puede activar de manera explícita. Si tu aplicación usa solo vistas estándar y recursos Drawable, 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 vistas personalizadas o llamadas a dibujos. 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 y RenderScript.

Cómo controlar la aceleración de hardware

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

  • Aplicación
  • Actividad
  • Ventana
  • Ver

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 la aceleración de hardware activada globalmente hace que tu aplicación no se comporte de manera adecuada, puedes controlarla en actividades individuales. Si quieres habilitar o inhabilitar la aceleración de hardware en el nivel de actividad, puedes usar el atributo android:hardwareAccelerated en el elemento <activity>. El siguiente ejemplo habilita la aceleración de hardware para toda la aplicación, pero la 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:

Kotlin

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

Java

getWindow().setFlags(
    WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
    WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);

Nota: Por el momento, no puedes inhabilitar la aceleración de hardware en el nivel de ventana.

Nivel de vista

Puedes inhabilitar la aceleración de hardware para una vista individual en el entorno de ejecución con el siguiente código:

Kotlin

myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null)

Java

myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);

Nota: Por el momento, no puedes habilitar la aceleración de hardware en el nivel de vista. Las capas de vista tienen otras funciones además de inhabilitar la aceleración de hardware. Consulta Capas de vista para obtener más información sobre sus usos.

Cómo determinar si una vista está acelerada por hardware

En ocasiones, conviene saber si una aplicación está acelerada por hardware, en especial para elementos como las vistas personalizadas. Resulta muy útil si tu aplicación realiza muchas operaciones de dibujo personalizado y no todas son enteramente compatibles con la nueva canalización de procesamiento.

Hay dos maneras diferentes de comprobar si la aplicación está acelerada por hardware:

Si debes realizar esta comprobación en tu código de dibujo, usa Canvas.isHardwareAccelerated() en lugar de View.isHardwareAccelerated() cuando sea posible. Cuando una vista está adjuntada a una ventana acelerada por hardware, igual se puede dibujar con un lienzo no acelerado por hardware. Esto ocurre, por ejemplo, cuando se dibuja una vista en un mapa de bits para fines relacionados con el almacenamiento en caché.

Modelos de dibujo de Android

Cuando la aceleración de hardware está habilitada, el framework de Android utiliza un nuevo modelo de dibujo que emplea listas de visualización para renderizar tu aplicación en la pantalla. Para entender completamente las listas de visualización y cómo pueden afectar tu app, es útil entender también cómo Android dibuja las vistas sin aceleración de hardware. En las siguientes secciones, se describen los modelos de dibujo basados en software y acelerados por hardware.

Modelo de dibujo basado en software

En el modelo de dibujo de software, las vistas se dibujan con los dos pasos siguientes:

  1. Invalidar la jerarquía
  2. Dibujar la jerarquía

Cada vez que una aplicación necesita actualizar una parte de su IU, invoca a invalidate() (o una de sus variantes) en cualquier vista que haya cambiado de contenido. Los mensajes de invalidación se propagan hacia arriba por toda la jerarquía de vista para calcular las regiones de la pantalla que se deben volver a dibujar (la región obsoleta). Luego, el sistema Android dibuja cualquier vista en la jerarquía que se cruza con la región obsoleta. Cabe aclarar que este modelo de dibujo presenta dos inconvenientes:

  • Primero, es necesario ejecutar una gran cantidad de código en cada pase de dibujo. Por ejemplo, si tu aplicación llama a invalidate() en un botón y dicho botón se ubica encima de otra vista, el sistema Android vuelve a dibujar esa vista aunque no haya cambiado.
  • El segundo problema es que el modelo de dibujo puede ocultar errores de la aplicación. Como el sistema Android vuelve a dibujar vistas cuando se cruzan con la región obsoleta, también podría hacerlo con una vista a la que le cambiaste el contenido, incluso aunque no hayas llamado a invalidate() en ella. Cuando esto sucede, dependes de que se invalide otra vista para lograr el funcionamiento adecuado. Este comportamiento puede cambiar cada vez que modificas tu aplicación. Por lo tanto, siempre debes llamar a invalidate() en tus vistas personalizadas si vas a modificar datos o estados que afecten el código de dibujo de la vista.

Nota: Las vistas de Android llaman automáticamente a invalidate() cuando cambian sus propiedades, como el color de fondo o el texto en una TextView.

Modelo de dibujo acelerado por hardware

El sistema Android usa invalidate() y draw() para solicitar actualizaciones de pantalla y renderizar vistas, pero maneja el dibujo real de manera diferente. En lugar de ejecutar los comandos de dibujo de inmediato, el sistema Android los registra dentro de las listas de visualización, que contienen los resultados del código de dibujo de la jerarquía de vistas. Otra optimización es que el sistema Android solo necesita registrar y actualizar listas de visualización para las vistas marcadas como obsoletas por una llamada a invalidate(). Las vistas que no se invalidaron se pueden volver a dibujar de un modo sencillo si se vuelve a enviar la lista de visualización registrada anteriormente. El nuevo modelo de dibujo contiene tres etapas:

  1. Invalidar la jerarquía
  2. Registrar y actualizar las listas de visualización
  3. Dibujar las listas de visualización

Con este modelo, no puedes depender de que una vista se cruce con la región obsoleta para ejecutar su método draw(). Para asegurarte de que el sistema Android registre la lista de visualización de una vista, debes llamar a invalidate(). Si olvidas hacer esto, la vista se mostrará igual incluso después de haberse modificado.

El uso de listas de visualización también beneficia el rendimiento de la animación porque, para configurar propiedades específicas, como el canal alfa o la rotación, no es necesario invalidar la vista de destino (se realiza automáticamente). Esta optimización también se aplica a las vistas con listas de visualización (cualquier vista cuando tu aplicación está acelerada por hardware). Por ejemplo, supongamos que hay un LinearLayout que contiene una ListView sobre un Button. La lista de visualización del LinearLayout se ve de esta forma:

  • DrawDisplayList(ListView)
  • DrawDisplayList(Button)

Supongamos ahora que deseas cambiar la opacidad de ListView. Después de invocar a setAlpha(0.5f) en la ListView, la lista de visualización ahora contiene lo siguiente:

  • SaveLayerAlpha(0.5)
  • DrawDisplayList(ListView)
  • Restore
  • DrawDisplayList(Button)

No se ejecutó el complejo código de dibujo de ListView. En cambio, el sistema solo actualizó la lista de visualización del LinearLayout, que es mucho más sencillo. En una aplicación sin la aceleración de hardware habilitada, se vuelve a ejecutar el código de dibujo de la lista y de su superior.

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, los widgets y diseños predeterminados, y los 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

Nota: Las formas "simples" son comandos drawRect(), drawCircle(), drawOval(), drawRoundRect() y drawArc() (con useCenter=false) que se envían con una clase Paint que no tiene un PathEffect ni contiene uniones no predeterminadas (a través de setStrokeJoin()/setStrokeMiter()). Otras instancias de esos comandos de dibujo pertenecen a "complejas" en la tabla anterior.

En el caso de que tu aplicación se vea afectada por alguna de estas limitaciones o funciones faltantes, puedes desactivar la aceleración de hardware solo para la parte afectada de la aplicación si llamas a setLayerType(View.LAYER_TYPE_SOFTWARE, null). De ese modo, puedes seguir aprovechando la aceleración de hardware en el resto de la aplicación. Consulta Cómo controlar la aceleración de hardware para obtener más información sobre cómo habilitar e inhabilitar la aceleración de hardware en diferentes niveles de la aplicación.

Capas de vista

En todas las versiones de Android, las vistas tienen la capacidad de renderizarse en búferes fuera de la pantalla, ya sea a través del uso de la caché de dibujo de una vista o con Canvas.saveLayer(). Los búferes fuera de la pantalla, o capas, tienen varios usos. Puedes usarlos para obtener un mejor rendimiento en el momento de animar vistas complejas o para aplicar efectos de composición. Por ejemplo puedes implementar efectos de atenuación con Canvas.saveLayer() para renderizar temporalmente una vista en una capa y, luego, volver a componerla en la pantalla con un factor de opacidad.

A partir de Android 3.0 (nivel de API 11), tienes más control sobre cómo y cuándo usar capas con el método View.setLayerType(). Esta API toma dos parámetros: el tipo de capa que quieres usar y un objeto Paint opcional que describe cómo se debe componer la capa. Puedes usar el parámetro Paint para aplicar opacidad, filtros de color o modos de combinación especiales a una capa. Una vista puede usar uno de estos tres tipos de capas:

  • LAYER_TYPE_NONE: La vista se renderiza normalmente y no está respaldada por un búfer fuera de la pantalla. Este es el comportamiento predeterminado.
  • LAYER_TYPE_HARDWARE: La vista se renderiza en hardware en una textura de hardware si la aplicación está acelerada por hardware. Si la aplicación no está acelerada por hardware, este tipo de capa se comporta igual que LAYER_TYPE_SOFTWARE.
  • LAYER_TYPE_SOFTWARE: La vista se renderiza en software en un mapa de bits.

El tipo de capa que usas depende de tu objetivo:

  • Rendimiento: Utiliza un tipo de capa de hardware para renderizar una vista en una textura de hardware. Una vez que una vista se renderice en una capa, su código de dibujo no tendrá que ejecutarse hasta que la vista llame a invalidate(). Algunas animaciones, como las animaciones alfa, se pueden aplicar directamente sobre la capa, algo muy eficiente para que haga la GPU.
  • Efectos visuales: Utiliza un tipo de capa de hardware o software y una Paint para aplicar tratamientos visuales especiales a una vista. Por ejemplo, puedes dibujar una vista en blanco y negro con un ColorMatrixColorFilter.
  • Compatibilidad: Usa un tipo de capa de software para forzar una vista a fin de que se renderice en software. Si una vista que está acelerada por hardware (por ejemplo, si toda tu aplicación tiene aceleración de hardware) tiene problemas de procesamiento, este es un método sencillo para resolver las limitaciones de la canalización de procesamiento de hardware.

Animaciones y capas de vista

Las capas de hardware pueden ofrecer animaciones más rápidas y fluidas cuando tu aplicación tiene aceleración de hardware. No siempre es posible ejecutar una animación a 60 fotogramas por segundo cuando se animan vistas complejas que envían muchas operaciones de dibujo. Puedes solucionar esto si usas capas de hardware para renderizar la vista en una textura de hardware. La textura de hardware se puede utilizar para animar la vista, lo que elimina la necesidad de que la vista se vuelva a dibujar de manera constante cuando se la anima. La vista no se vuelve a dibujar, a menos que cambies sus propiedades, lo que llama a invalidate(), o que llames a invalidate() de forma manual. Si estás ejecutando una animación en la aplicación y no obtienes los resultados uniformes que deseas, puedes habilitar las capas de hardware en tus vistas animadas.

Cuando una vista está respaldada por una capa de hardware, algunas de sus propiedades se controlan por la forma en que la capa se compone en la pantalla. Será eficiente configurar estas propiedades porque no requieren que la vista se invalide y se vuelva a dibujar. La siguiente lista de propiedades afecta la forma en que se compone la capa. Llamar al método set para cualquiera de estas propiedades genera una invalidación óptima y que no se vuelva a dibujar la vista de destino:

  • alpha: Cambia la opacidad de la capa.
  • x, y, translationX, translationY: Cambia la posición de la capa.
  • scaleX, scaleY: Cambia el tamaño de la capa.
  • rotation, rotationX, rotationY: Cambia la orientación de la capa en el espacio 3D.
  • pivotX, pivotY: Cambia el origen de las transformaciones de la capa.

Estas propiedades son los nombres que se usan para animar una vista con un ObjectAnimator. Si quieres acceder a estas propiedades, llama al método set o get apropiados. Por ejemplo, para modificar la propiedad alfa, llama a setAlpha(). En el siguiente fragmento de código, se muestra la forma más eficaz de girar una vista en 3D alrededor del eje Y:

Kotlin

view.setLayerType(View.LAYER_TYPE_HARDWARE, null)
ObjectAnimator.ofFloat(view, "rotationY", 180f).start()

Java

view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
ObjectAnimator.ofFloat(view, "rotationY", 180).start();

Debido a que las capas de hardware consumen memoria de video, es muy recomendable habilitarlas solo durante la animación y luego inhabilitarlas cuando haya finalizado. Puedes hacer esto con objetos de escucha de la animación:

Kotlin

view.setLayerType(View.LAYER_TYPE_HARDWARE, null)
ObjectAnimator.ofFloat(view, "rotationY", 180f).apply {
    addListener(object : AnimatorListenerAdapter() {
        override fun onAnimationEnd(animation: Animator) {
            view.setLayerType(View.LAYER_TYPE_NONE, null)
        }
    })
    start()
}

Java

view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "rotationY", 180);
animator.addListener(new AnimatorListenerAdapter() {
    @Override
    public void onAnimationEnd(Animator animation) {
        view.setLayerType(View.LAYER_TYPE_NONE, null);
    }
});
animator.start();

Para obtener más información sobre la animación de propiedades, consulta Animación de propiedades.

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:

Reduce la cantidad de vistas en tu aplicación
Mientras más vistas tenga que dibujar el sistema, más lento será. Esto también se aplica a la canalización de procesamiento de software. Reducir las vistas es una de las formas más fáciles de optimizar tu IU.
Evita las superposiciones
No dibujes demasiadas capas una encima de la otra. Quita las vistas que estén completamente oscurecidas por otras vistas opacas encima de ellas. 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.
No modifiques las formas con demasiada frecuencia
Las formas complejas, las trayectorias y los círculos, por ejemplo, se renderizan con máscaras de textura. Cada vez que creas o modificas una trayectoria, 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 una vista sea translúcida con setAlpha(), AlphaAnimation o ObjectAnimator, se renderiza en un búfer fuera de la pantalla, lo que duplica la tasa de relleno requerida. Cuando apliques alfa en vistas muy grandes, procura configurar el tipo de capa de la vista como LAYER_TYPE_HARDWARE.