美文网首页Android iOS开发知识库
一个关于RadioGroup的坑

一个关于RadioGroup的坑

作者: chrnie | 来源:发表于2017-08-31 22:22 被阅读333次

    RadioGroup应该算是一个很常用的控件了,用于作为RadioButton的父控件,可以实现单选框。然而最近用了类似flux的单向数据流架构后,再使用RadioGroup立马遇到了一个大坑。

    类flux架构.png

    架构如上图,View的状态由ViewHolder中的变量决定,而ViewHolder中值的修改由用户输入触发控件的各种OnChangeListener改变。这里用到的是RadioGroup.OnChangeListener。

    void onCheckedChanged (RadioGroup group, int checkedId)
    

    onCheckChanged中int checkedId官方给的解释是:the unique identifier of the newly checked radio button。也就是说,在RadioGroup中一旦有RadioButton.checked改变了,就可以通过这个Listener获取到通知。

        public void check(@IdRes int id) {
            // don't even bother
            if (id != -1 && (id == mCheckedId)) {
                return;
            }
    
            if (mCheckedId != -1) {
                setCheckedStateForView(mCheckedId, false);
            }
    
            if (id != -1) {
                setCheckedStateForView(id, true);
            }
    
            setCheckedId(id);
        }
    

    RadioGroup.check函数可以修改子控件的check值,并且在进入函数的第一行就做了去重处理,防止Listener触发check函数,check函数又触发Lisnter导致无限调用地狱。这一切看着是如此完美,然后这就是坑的开始。一旦按照这个架构搭建好之后,如果只是用户触发该RadioGroup中的RadioButton触发值的刷新,这个流程完全没有任何问题。但是使用该架构,为的就是使UI能实时响应ViewHolder中的值更改,随时刷新页面。例如在一个其它的页面也能设置一个这个选项值,导致了Model的更新,Model的更新触发了ViewHolder的更新,ViewHolder的更新触发了RadioGroup.check函数。这时就会出现ANR,按前面的分析,一切都是那么的完美,不科学啊!而且通过单步调试,发现onCheckedChanged中收到的checkedId居然是错误的,百思不得其解啊!
    这时,就需要看看RadioGroup的工作原理了。在RadioGroup.check函数中,setCheckedStateForView(mCheckedId, false);这行调用触发了上一个被checked的RadioButton.check函数。下面是RadioButton.check函数的源码:

        public void setChecked(boolean checked) {
            if (mChecked != checked) {
                mChecked = checked;
                refreshDrawableState();
                notifyViewAccessibilityStateChangedIfNeeded(
                        AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
    
                // Avoid infinite recursions if setChecked() is called from a listener
                if (mBroadcasting) {
                    return;
                }
    
                mBroadcasting = true;
                if (mOnCheckedChangeListener != null) {
                    mOnCheckedChangeListener.onCheckedChanged(this, mChecked);
                }
                if (mOnCheckedChangeWidgetListener != null) {
                    mOnCheckedChangeWidgetListener.onCheckedChanged(this, mChecked);
                }
    
                mBroadcasting = false;            
            }
        }
    

    其中可以看到 mOnCheckedChangeWidgetListener.onCheckedChanged(this, mChecked);这行调用通知了RadioGroup,说我的checked值改变了。我们来看看通知RadioGroup之后,它做了什么。

        private class CheckedStateTracker implements CompoundButton.OnCheckedChangeListener {
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                // prevents from infinite recursion
                if (mProtectFromCheckedChange) {
                    return;
                }
    
                mProtectFromCheckedChange = true;
                if (mCheckedId != -1) {
                    setCheckedStateForView(mCheckedId, false);
                }
                mProtectFromCheckedChange = false;
    
                int id = buttonView.getId();
                setCheckedId(id);
            }
        }
    

    可以看到,里面居然没有使用传递进来的isChecked值!也就是说RadioGroup根本没有检查回调的RadioButton是不是被checked的。紧接着就调用setCheckedId函数,把触发的控件当作了是被checked的对待:

        private void setCheckedId(@IdRes int id) {
            mCheckedId = id;
            if (mOnCheckedChangeListener != null) {
                mOnCheckedChangeListener.onCheckedChanged(this, mCheckedId);
            }
        }
    

    在里面,再次触发了RadioGroup.OnCheckedChangeListener,并且把不是被checked的id当作参数传递了进去。于是乎,这就改变了ViewHolder中的值,接着触发了RadioGroup.check函数,接下来就是一系列的无穷调用...为什么在不使用响应式架构的时候不会出现bug呢,因为以前Listener中的值不会触发RadioGroup.check函数,而在check函数会触发两次Listener,在最后一次会将正确的id值传入Listener中。
    知道了坑在哪之后,解决的方法就是不要直接使用Listener传递的checkedId,还要检查该RadioButton.checked属性是不是true,只有为true时才将值设置给ViewHolder,这样地狱调用也就不会出现了。

    相关文章

      网友评论

        本文标题:一个关于RadioGroup的坑

        本文链接:https://www.haomeiwen.com/subject/jcofjxtx.html