【问题标题】:Race condition while showing and hiding FAB with AnimationUtils使用 AnimationUtils 显示和隐藏 FAB 时的竞争条件
【发布时间】:2015-10-22 11:14:33
【问题描述】:

在允许用户通过社交网络登录的 Android 应用中,我使用以下代码显示和隐藏 FAB

public abstract class LoginFragment extends Fragment {

    private FloatingActionButton mFab;
    private Animation mShowFab;
    private Animation mHideFab;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mShowFab = AnimationUtils.makeInAnimation(getContext(), false);
        mShowFab.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {
                mFab.setVisibility(View.VISIBLE);
            }

            @Override
            public void onAnimationEnd(Animation animation) {
            }

            @Override
            public void onAnimationRepeat(Animation animation) {
            }
        });

        mHideFab = AnimationUtils.makeOutAnimation(getContext(), true);
        mHideFab.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {
            }

            @Override
            public void onAnimationEnd(Animation animation) {
                mFab.setVisibility(View.INVISIBLE);
            }

            @Override
            public void onAnimationRepeat(Animation animation) {
            }
        });
    }

    private void showFab(boolean show) {
        boolean visible = mFab.isShown();

        if (show && !visible) {
                mFab.startAnimation(mShowFab);
        } else if (!show && visible) {
                mFab.startAnimation(mHideFab);
        }
    }

这很好用,当我调用上面的showFab 方法足够慢时。

在开始任何动画之前,我会检查当前 FloatingActionButton 的可见性,以便动画只播放一次 - 即使我连续多次调用 showFab(true)

我的问题:

当我的应用中显示LoginFragment 时,我首先向ServiceIntent 发送请求以从SQLite 获取用户数据并调用以下方法将我的UI 设置为“等待”状态:

private void setBusy(boolean busy) {
    mProgressBar.setVisibility(busy ? View.VISIBLE : View.INVISIBLE);
    showFab(!busy);
}

SQLite 的响应几乎立即返回 - 通过LocalBroadcastManager 我再次调用上述方法:setBusy(false)

然后出现错误,FAB 不可见。

如果我用无动画代码替换 FAB 方法,一切正常:

private void showFab(boolean show) {
    mFab.setVisibility(show ? View.VISIBLE : View.INVISIBLE);
}

但是对于动画 - 似乎会出现赛车情况。

作为一种解决方法,我尝试取消这两个动画 - 但这无济于事:

private void showFab(boolean show) {
    mShowFab.cancel();
    mShowFab.reset();

    mHideFab.cancel();
    mHideFab.reset();

    boolean visible = mFab.isShown();

    if (show && !visible) {
            mFab.startAnimation(mShowFab);
    } else if (!show && visible) {
            mFab.startAnimation(mHideFab);
    }
}

请建议在这里可以做什么。

我已经在调试器中多次单步调试我的应用程序。 setBusy(和 showFab)在显示 Fragment 时仅被调用两次,但两次调用都发生得非常快 - 并且 FAB 未显示 -

首次运行:

第二次运行:

更新:

不幸的是,使方法 synchronized 也无济于事 - FAB 保持隐藏状态:

private synchronized void showFab(boolean show) {
    mShowFab.cancel();
    mShowFab.reset();

    mHideFab.cancel();
    mHideFab.reset();

    boolean visible = mFab.isShown();

    if (show && !visible) {
        mFab.startAnimation(mShowFab);
    } else if (!show && visible) {
        mFab.startAnimation(mHideFab);
    }
}

【问题讨论】:

    标签: android animation android-animation floating-action-button animationutils


    【解决方案1】:

    您的每个动画都在单独的线程中运行。正如你所说,这会导致竞争条件。

    这是发生了什么: showFab 以 true 触发,然后以 false 触发。

    • mShowFab onAnimationStart 方法执行并将 FAB 设置为可见。
    • mHideFab onAnimationStart 方法执行并且什么都不做。
    • mShowFab onAnimationEnd 方法执行但什么都不做。
    • mHideFab onAnimationEnd 方法执行并将 FAB 设置为不可见。
    • 你最终会得到一个不可见的 FAB,它应该是可见的。

    第二次运行中的变量表明 mShowFab 动画永远不会运行。

    您应该能够通过监听隐藏动画的结束来解决竞争条件。像这样的:

    private void showFab(boolean show) {
        if (show) {
            // if you have an animation currently running and you want to show the fab 
            if (mFab.getAnimation() != null && !mFab.getAnimation().hasEnded()) {
                // then wait for it to complete and begin the next one
                mFab.getAnimation().setAnimationListener(new Animation.AnimationListener() {
                    @Override
                    public void onAnimationStart(Animation animation) {
    
                    }
    
                    @Override
                    public void onAnimationEnd(Animation animation) {
                        mFab.setVisibility(View.INVISIBLE);
                        mFab.startAnimation(mShowFab);
                    }
    
                    @Override
                    public void onAnimationRepeat(Animation animation) {
    
                    }
                });
            } else {
                mFab.startAnimation(mShowFab);
            }
        } else {
            mFab.startAnimation(mHideFab);
        }
    }
    

    【讨论】:

    • 这似乎有效,谢谢+1。但是,如果您声明动画在单独的线程中运行,那么事情可能会发生变化检查!mFab.getAnimation().hasEnded() 和设置动画侦听器之间。所以我不认为你的代码是一个好的解决方案。
    • 在挖掘Animation 的源代码后,我确信这是线程安全的。 AnimationListener 回调被发布到主线程消息队列,hasEnded() 检查也在主线程上运行。
    • 你不需要在某个时候停止动画吗?如果我使用此代码,我的似乎会无休止地重复
    【解决方案2】:

    首先要指出的是,没有什么比赛车条件更好的了。正确的术语就是竞态条件。请记住这一点,以备将来使用;-)

    我假设您正在使用 BroadcastReceiver 来接收消息,并且您已经创建了对象并告诉它由于动画而在单独的线程中运行。这意味着您的 showFab 方法可以一次调用两次。要处理这个问题,请将方法定义为synchronized

    【讨论】:

    • 不幸的是,使 showFab 方法 synchronized 没有帮助 - FAB 仍然隐藏。那里肯定有其他事情发生。
    【解决方案3】:

    这是我自己的解决方案 -

    首先,在这里使用synchronized 没有帮助,因为动画在同一个线程中运行。

    我的解决方案是引入一个单独的 boolean 变量来保存预期的最终 FAB 状态(可见或不可见):

    private FloatingActionButton mFab;
    private boolean mFabVisible;
    private Animation mShowFab;
    private Animation mHideFab;
    
    private void showFab(boolean show) {
    
        if (show && !mFabVisible) {
            mFab.startAnimation(mShowFab);
        } else if (!show && mFabVisible) {
            mFab.startAnimation(mHideFab);
        }
    
        mFabVisible = show;
    }
    

    另外,我认为另一种可能性可能是两次调用mFab.setVisibility(View.VISIBLE) - 如下面的代码所示。但我还没有真正测试过:

        mShowFab = AnimationUtils.makeInAnimation(getContext(), false);
        mShowFab.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {
                mFab.setVisibility(View.VISIBLE);
            }
    
            @Override
            public void onAnimationEnd(Animation animation) {
                mFab.setVisibility(View.VISIBLE);
            }
    
            @Override
            public void onAnimationRepeat(Animation animation) {
            }
        });
    
        mHideFab = AnimationUtils.makeOutAnimation(getContext(), true);
        mHideFab.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {
            }
    
            @Override
            public void onAnimationEnd(Animation animation) {
                mFab.setVisibility(View.INVISIBLE);
            }
    
            @Override
            public void onAnimationRepeat(Animation animation) {
            }
        });
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-03-06
      • 2020-07-12
      • 1970-01-01
      • 2017-06-11
      • 2011-12-12
      • 2019-02-10
      • 2015-04-28
      相关资源
      最近更新 更多