Why is hardware acceleration not working on my View? Why is hardware acceleration not working on my View? android android

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)