¿Por qué la aceleración de hardware no funciona en mi vista?
Estoy usando la biblioteca Rebound de Facebook para replicar las animaciones animadas que se ven en la implementación de los jefes de chat. El problema es que la mayoría de las veces la animación tartamudea. Algunas fotos explicarán mejor esto. Aquí está la animación de las cabezas de chat suave y suave:
- Comportamiento extraño de `drawTextOnPath ()` con aceleración de hardware
- Problemas de compatibilidad de Android 4.0 con Canvas.clipPath
- Captura de pantalla programática de una webview con elementos flash
- Android Force GPU Rendering ¿Cómo habilitar y deshabilitar?
- ¿Cómo utilizar la nueva GPU acelerado avd?
Y aquí está mi intento (observe cómo la animación para la View
blanca omite casi todos los fotogramas):
De vez en cuando funciona sin problemas:
A continuación se muestra el código que estoy usando actualmente (todo el proyecto está en Github si quieres configurarlo rápidamente). Supongo que esto tiene algo que ver con la aceleración de hardware no se habilitó correctamente en mi View
. Hay 2 Spring
s en mi SpringSystem
, una para la "burbuja" (el icono de Android) y otra para el contenido (la View
blanca que se muestra al tocar la burbuja). Cualquier ayuda sobre cómo resolver este problema sería muy apreciada. Gracias.
AndroidManifest.xml
:
<application android:hardwareAccelerated="true" ...> ... </application>
AppService.java
:
// the following code is in AppService#onCreate() // AppService extends android.app.Service // full code at https://github.com/vickychijwani/BubbleNote mContent.setLayerType(View.LAYER_TYPE_HARDWARE, null); final Spring bubbleSpring = system.createSpring(); bubbleSpring.setCurrentValue(1.0); bubbleSpring.addListener(new SpringListener() { @Override public void onSpringUpdate(Spring spring) { float value = (float) spring.getCurrentValue(); params.x = (int) (mPos[0] * value); params.y = (int) (mPos[1] * value); mWindowManager.updateViewLayout(mBubble, params); // fire the second animation when this one is about to end if (spring.isOvershooting() && contentSpring.isAtRest()) { contentSpring.setEndValue(1.0); } } // ... }); final Spring contentSpring = system.createSpring(); contentSpring.setCurrentValue(0.0); contentSpring.addListener(new SpringListener() { @Override public void onSpringUpdate(Spring spring) { // always prints false?! Log.d(TAG, "hardware acc = " + mContent.isHardwareAccelerated()); float value = (float) spring.getCurrentValue(); // clamping is required to prevent flicker float clampedValue = Math.min(Math.max(value, 0.0f), 1.0f); mContent.setScaleX(value); mContent.setScaleY(value); mContent.setAlpha(clampedValue); } // ... });
- Aceleración de hardware en ListView
- Perforar un agujero en una superposición de rectángulo con aceleración HW activada en Ver
- Transiciones de páginas lentas en Android con aceleración de hardware en
- VideoView no se muestra en un fragmento
- ¿Por qué llamar a setScaleX durante el gesto de pinch zoom causa parpadeo?
- Conseguir que los codificadores QualComm funcionen a través de la API de MediaCodec
- Tamaño de fuente demasiado grande para caber en caché
- Android aceleración de hardware TextureView con lockCanvas ()
Lo he deducido al pasar por el código fuente del marco.
TL; DR : agregue WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
a los indicadores de diseño cuando agrega manualmente una View
a una Window
/ WindowManager
; Establecer android:hardwareAccelerated=true
en el manifiesto no funcionará.
Estoy asociando manualmente mi View
al WindowManager
(porque necesito crear mi interfaz de usuario en un Service
para emular cabeceras de chat) así:
// code at https://github.com/vickychijwani/BubbleNote/blob/eb708e3910a7279c5490f614a7150009b59bad0b/app/src/main/java/io/github/vickychijwani/bubblenote/BubbleNoteService.java#L54 mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE); LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE); mBubble = (LinearLayout) inflater.inflate(R.layout.bubble, null, false); // ... final WindowManager.LayoutParams params = new WindowManager.LayoutParams( WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.TYPE_PHONE, WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT); // ... mWindowManager.addView(mBubble, params);
Vamos a cavar …
Bienvenido al marco de Android
Empecé a depurar en View#draw(...)
, luego subí la pila de llamadas a ViewRootImpl#draw(boolean)
. Aquí me encontré con este pedazo de código:
if (!dirty.isEmpty() || mIsAnimating) { if (attachInfo.mHardwareRenderer != null && attachInfo.mHardwareRenderer.isEnabled()) { // Draw with hardware renderer. mIsAnimating = false; mHardwareYOffset = yoff; mResizeAlpha = resizeAlpha; mCurrentDirty.set(dirty); dirty.setEmpty(); attachInfo.mHardwareRenderer.draw(mView, attachInfo, this, animating ? null : mCurrentDirty); } else { // If we get here with a disabled & requested hardware renderer, something went // wrong (an invalidate posted right before we destroyed the hardware surface // for instance) so we should just bail out. Locking the surface with software // rendering at this point would lock it forever and prevent hardware renderer // from doing its job when it comes back. // Before we request a new frame we must however attempt to reinitiliaze the // hardware renderer if it's in requested state. This would happen after an // eglTerminate() for instance. if (attachInfo.mHardwareRenderer != null && !attachInfo.mHardwareRenderer.isEnabled() && attachInfo.mHardwareRenderer.isRequested()) { try { attachInfo.mHardwareRenderer.initializeIfNeeded(mWidth, mHeight, mHolder.getSurface()); } catch (OutOfResourcesException e) { handleOutOfResourcesException(e); return; } mFullRedrawNeeded = true; scheduleTraversals(); return; } if (!drawSoftware(surface, attachInfo, yoff, scalingRequired, dirty)) { return; } } }
En mi caso ViewRootImpl#drawSoftware()
estaba siendo llamado, que utiliza el renderizador de software. Hmm … eso significa que el HardwareRenderer
es null
. Así que fui a buscar el punto de construcción del HardwareRenderer
, que está en ViewRootImpl#enableHardwareAcceleration(WindowManager.LayoutParams)
:
// Try to enable hardware acceleration if requested final boolean hardwareAccelerated = (attrs.flags & WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0; if (hardwareAccelerated) { // ... mAttachInfo.mHardwareRenderer = HardwareRenderer.createGlRenderer(2, translucent); // ... }
¡Ah! ¡Ahí está nuestro culpable!
Volver al problema actual
En este caso, Android no establece automáticamente FLAG_HARDWARE_ACCELERATED
para esta Window
, aunque he definido android:hardwareAccerelated=true
en el manifiesto. Así que la solución es simplemente:
mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE); LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE); mBubble = (LinearLayout) inflater.inflate(R.layout.bubble, null, false); // ... final WindowManager.LayoutParams params = new WindowManager.LayoutParams( WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.TYPE_PHONE, // NOTE WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT); // ... mWindowManager.addView(mBubble, params);
Aunque la animación todavía no es tan suave como Facebook. Me pregunto por qué … (antes de que nadie pida: no, no hay copiosos registros durante la animación, y sí, he intentado con una versión de la compilación)
- Tecla Atrás en Fragmento añadido mediante programación lleva a contenedor vacío
- SaxParser en Android: final inesperado de excepción de documento