Why is hardware acceleration not working on my View?
I've figured it out by going through the framework source code.
TL;DR: add WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
to the layout flags when you manually attach a View
to a Window
/ WindowManager
; setting android:hardwareAccelerated=true
in the manifest won't work.
I'm manually attaching my View
to the WindowManager
(because I need to create my UI in a Service
to emulate chat heads) like so:
// 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);
Let's go digging...
Welcome to the Android framework
I started debugging at View#draw(...)
, then went up the call stack to ViewRootImpl#draw(boolean)
. Here I came across this piece of code:
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; } } }
In my case ViewRootImpl#drawSoftware()
was being called, which uses the software renderer. Hmm... that means the HardwareRenderer
is null
. So I went searching for the point of construction of the HardwareRenderer
, which is in 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); // ... }
Aha! There's our culprit!
Back to the problem at hand
In this case Android does not automatically set FLAG_HARDWARE_ACCELERATED
for this Window
, even though I've set android:hardwareAccerelated=true
in the manifest. So the fix is simply:
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);
Although the animation is still not as smooth as Facebook's. I wonder why... (before anyone asks: no, there are no copious logs during the animation; and yes, I've tried with a release build)