Why is hardware acceleration not working on my View?

后端 未结 1 937
半阙折子戏
半阙折子戏 2020-12-29 11:33

I\'m using Facebook\'s Rebound library to replicate the bouncy animations seen in their chat heads implementation. The problem is, most of the time the animation stutters. A

相关标签:
1条回答
  • 2020-12-29 11:55

    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)

    0 讨论(0)
提交回复
热议问题