【问题标题】:Why is hardware acceleration not working on my View?为什么硬件加速在我的 View 上不起作用?
【发布时间】:2014-09-07 18:01:54
【问题描述】:

我正在使用Facebook's Rebound library 来复制在他们的聊天头实现中看到的有弹性的动画。问题是,大多数时候动画都会卡顿。几张图片可以更好地解释这一点。这是流畅的聊天头动画:

这是我的尝试(注意白色 View 的动画是如何跳过几乎所有帧的):

偶尔会顺利运行:

下面是我目前正在使用的代码(如果你想快速设置,整个项目是up on Github)。我猜这与我的View 上没有正确启用硬件加速有关。我的SpringSystem 中有2 个Springs,一个用于“气泡”(Android 图标),另一个用于内容(点击气泡时显示的白色View)。任何有关如何解决此问题的帮助将不胜感激。谢谢。

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);
        }

        // ...
    });

【问题讨论】:

  • 首先从 onSpringUpdate 中删除 Log.d,因为它会减慢动画速度
  • @pskink 我知道这一点。我只是在之后登录该日志以检查是否正确启用了 View 的硬件加速。
  • 您可以添加 Log.d 以查看第二个动画是否真的触发了一次
  • @pskink 是的,动画每次都正确触发。我用日志检查了它调试器。

标签: android animation android-animation hardware-acceleration facebook-messenger


【解决方案1】:

我已经通过框架源代码弄清楚了。

TL;DR:当您手动将View 附加到Window / WindowManager 时,将WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED 添加到布局标志;在清单中设置 android:hardwareAccelerated=true 将不起作用。


我是manually attaching my View to the WindowManager(因为我需要在Service 中创建我的用户界面来模拟聊天头),如下所示:

    // 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);

我们去挖吧……

欢迎使用 Android 框架

我从View#draw(...) 开始调试,然后将调用堆栈上升到ViewRootImpl#draw(boolean)。在这里,我遇到了这段代码:

    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;
            }
        }
    }

在我的情况下,ViewRootImpl#drawSoftware() 被调用,它使用软件渲染器。嗯...这意味着HardwareRenderernull。于是我去找HardwareRenderer的构造点,在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);
        // ...
    }

啊哈!这是我们的罪魁祸首!

回到手头的问题

在这种情况下,Android 不会自动为此 Window 设置 FLAG_HARDWARE_ACCELERATED,即使我在清单中设置了 android:hardwareAccerelated=true。所以解决方法很简单:

    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);

虽然动画仍然不如 Facebook 的流畅。我想知道为什么...(在有人问之前:不,动画期间没有大量日志;是的,我已经尝试过发布版本)

【讨论】:

  • 我没有将 FLAG_HARDWARE_ACCELERATED 标志添加到 WindowManager.LayoutParams 但是当我检查 Layout.isHardwareAccelerated() 时它仍然返回 true?
  • 您的清单中有android:hardwareAccelerated=true 吗?我认为这会自动启用整个应用程序的硬件加速。
  • 当然有。这意味着我们不需要像您提到的那样添加 FLAG_HARDWARE_ACCELERATED。
  • 好吧,要么您没有手动将视图附加到您的窗口,要么您使用的是不同版本的 Android,不需要您明确添加 FLAG_HARDWARE_ACCELERATED。没有看到相关代码,我不能说。
  • 与我目前遇到的问题完全相同,但我不确定是否发现有任何不同。
猜你喜欢
  • 2020-03-17
  • 1970-01-01
  • 1970-01-01
  • 2016-07-20
  • 2016-02-29
  • 2020-02-29
  • 1970-01-01
  • 1970-01-01
  • 2020-03-10
相关资源
最近更新 更多